diff --git a/.chglog/CHANGELOG.tpl.md b/.chglog/CHANGELOG.tpl.md new file mode 100644 index 000000000..beb340ad6 --- /dev/null +++ b/.chglog/CHANGELOG.tpl.md @@ -0,0 +1,67 @@ + + + +{{ if .Versions -}} + +# Unreleased + +{{ if .Unreleased.CommitGroups -}} +{{ range .Unreleased.CommitGroups -}} +## {{ .Title }} + +{{ range .Commits -}} +{{ if and (not (hasPrefix .Subject "changelog rebuild")) (not (hasPrefix .Subject "layer docs update")) (not (hasPrefix .Subject "bump version to")) -}} +* {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} +{{ end -}} +{{ end }} +{{ end -}} +{{ end -}} +{{ end -}} + +{{ range .Versions }} + +## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }} +{{ range .CommitGroups -}} + +## {{ .Title }} + +{{ range .Commits -}} +{{ if and (not (hasPrefix .Subject "changelog rebuild")) (not (hasPrefix .Subject "layer docs update")) (not (hasPrefix .Subject "bump version to")) -}} +* {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} +{{ end -}} +{{ end }} +{{ end -}} + +{{- if .RevertCommits -}} +## Reverts +{{ range .RevertCommits -}} +* {{ .Revert.Header }} +{{ end }} +{{ end -}} + +{{- if .MergeCommits -}} +## Pull Requests + +{{ range .MergeCommits -}} +* {{ .Header }} +{{ end }} +{{ end -}} + +{{- if .NoteGroups -}} +{{ range .NoteGroups -}} +## {{ .Title }} +{{ range .Notes }} +{{ .Body }} +{{ end }} +{{ end -}} +{{ end -}} +{{ end -}} + +{{- if .Versions }} +[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD +{{ range .Versions -}} +{{ if .Tag.Previous -}} +[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }} +{{ end -}} +{{ end -}} +{{ end -}} diff --git a/.chglog/config.yml b/.chglog/config.yml new file mode 100644 index 000000000..4b78ec16e --- /dev/null +++ b/.chglog/config.yml @@ -0,0 +1,37 @@ +style: github +template: CHANGELOG.tpl.md +info: + title: CHANGELOG + repository_url: https://github.com/aws-powertools/powertools-lambda-java +options: + commits: + filters: + Type: + - feat + - fix + - perf + - refactor + - docs + - chore + - revert + commit_groups: + title_maps: + feat: Features + fix: Bug Fixes + perf: Performance Improvements + refactor: Code Refactoring + docs: Documentation + chore: Maintenance + revert: Regression + header: + pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$" + pattern_maps: + - Type + - Scope + - Subject + notes: + keywords: + - BREAKING CHANGE + # issues: + # prefix: + # - # diff --git a/.github/DISCUSSION_TEMPLATE/rfcs.yml b/.github/DISCUSSION_TEMPLATE/rfcs.yml new file mode 100644 index 000000000..55909514f --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/rfcs.yml @@ -0,0 +1,107 @@ +title: "RFC: " +body: + - type: markdown + attributes: + value: | + Thank you for submitting a RFC. Please add as many details as possible to help further enrich this design. + - type: input + id: relation + attributes: + label: Is this related to an existing feature request or issue? + description: Please share a link, if applicable + - type: dropdown + id: area + attributes: + label: Which area does this RFC relate to? + options: + - Tracer + - Logger + - Metrics + - Parameters + - Large Messages + - Batch Processing + - Validation + - Idempotency + - Custom Resources + - Serialization + - Other + validations: + required: true + - type: textarea + id: summary + attributes: + label: Summary + description: Please provide an overview in one or two paragraphs + validations: + required: true + - type: textarea + id: problem + attributes: + label: Use case + description: Please share the use case and motivation behind this proposal + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposal + description: Please explain the design in detail, so anyone familiar with the project could implement it + placeholder: What the user experience looks like before and after this design? + validations: + required: true + - type: textarea + id: scope + attributes: + label: Out of scope + description: Please explain what should be considered out of scope in your proposal + validations: + required: true + - type: textarea + id: challenges + attributes: + label: Potential challenges + description: Nothing is perfect. Please share what common challenges, edge cases, unresolved areas, and suggestions on how to mitigate them + validations: + required: true + - type: textarea + id: integrations + attributes: + label: Dependencies and Integrations + description: If applicable, please share whether this feature has additional dependencies, and how it might integrate with other utilities available + validations: + required: false + - type: textarea + id: alternatives + attributes: + label: Alternative solutions + description: Please describe what alternative solutions to this use case, if any + render: Markdown + validations: + required: false + - type: checkboxes + id: acknowledgment + attributes: + label: Acknowledgment + options: + - label: This RFC meets [Powertools for AWS Lambda (Java) Tenets](https://docs.powertools.aws.dev/lambda/java/latest/#tenets) + required: true + - label: Should this be considered in other Powertools for AWS Lambda languages? i.e. [Python](https://github.com/aws-powertools/powertools-lambda-python/), [TypeScript](https://github.com/aws-powertools/powertools-lambda-typescript/), and [.NET](https://github.com/aws-powertools/powertools-lambda-dotnet/) + required: false + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: After creating an RFC, please wait until it is reviewed and signed-off by a maintainer before implementing it. This will reduce amount of rework and the chance that a pull request gets rejected. + + Metadata information for admin purposes, please leave them empty. + + * RFC PR: + * Approved by: '' + * Reviewed by: '' + - type: input + id: notes + attributes: + label: Future readers + description: Please not edit this field + value: "Please react with 👍 and your use case to help us understand customer demand." \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index c1b7490eb..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug, triage -assignees: '' - ---- - -<!--- Provide a general summary of the issue in the Title above --> -<!--- How has this issue affected you? What are you trying to accomplish? --> - -**What were you trying to accomplish?** - -## Expected Behavior -<!--- If you're describing a bug, tell us what should happen --> -<!--- If you're suggesting a change/improvement, tell us how it should work --> - -## Current Behavior -<!--- If describing a bug, tell us what happens instead of the expected behavior --> -<!--- If suggesting a change/improvement, explain the difference from current behavior --> - -## Possible Solution -<!--- Not obligatory, but suggest a fix/reason for the bug, --> -<!--- or ideas how to implement the addition or change --> - -## Steps to Reproduce (for bugs) -<!--- Provide a link to a live example, or an unambiguous set of steps to --> -<!--- reproduce this bug. Include code to reproduce, if relevant --> -1. -2. -3. -4. - -## Environment - -* **Powertools version used**: -* **Packaging format (Layers, Maven/Gradle)**: -* **AWS Lambda function runtime:** -* **Debugging logs** - -> [How to enable debug mode](https://awslabs.github.io/aws-lambda-powertools-java/#debug-mode)** - -```text -# paste logs here -``` diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..2b0ae71e8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,82 @@ +name: Bug report +description: Report a reproducible bug to help us improve +title: "Bug: TITLE" +type: "Bug" +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: | + Thank you for submitting a bug report. Please add as much information as possible to help us reproduce, and remove any potential sensitive data. + + Please become familiar with [our definition of bug](https://docs.powertools.aws.dev/lambda/java/processes/maintainers/#is-that-a-bug). + - type: textarea + id: expected_behaviour + attributes: + label: Expected Behaviour + description: Please share details on the behaviour you expected + validations: + required: true + - type: textarea + id: current_behaviour + attributes: + label: Current Behaviour + description: Please share details on the current issue + validations: + required: true + - type: textarea + id: code_snippet + attributes: + label: Code snippet + description: Please share a code snippet to help us reproduce the issue + render: java + validations: + required: true + - type: textarea + id: solution + attributes: + label: Possible Solution + description: If known, please suggest a potential resolution + validations: + required: false + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: Please share how we might be able to reproduce this issue + validations: + required: true + - type: input + id: version + attributes: + label: Powertools for AWS Lambda (Java) version + placeholder: "latest, 2.0.1" + value: latest + validations: + required: true + - type: dropdown + id: runtime + attributes: + label: AWS Lambda function runtime + options: + - "Java 8" + - "Java 11" + - "Java 17" + - "Java 21" + - "provided.al2023" + validations: + required: true + - type: textarea + id: logs + attributes: + label: Debugging logs + description: If available, please share [debugging logs](https://docs.powertools.aws.dev/lambda/lambda/#debug-mode) + render: java + validations: + required: false + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..01a8d495b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a question + url: https://github.com/aws-powertools/powertools-lambda-java/discussions/new + about: Ask a general question about Powertools for AWS Lambda + - name: Join Community Discord Server + url: https://discord.gg/B8zZKbbyET + about: "Check out the #java channel" diff --git a/.github/ISSUE_TEMPLATE/documentation-improvements.md b/.github/ISSUE_TEMPLATE/documentation-improvements.md deleted file mode 100644 index 8341ae4e0..000000000 --- a/.github/ISSUE_TEMPLATE/documentation-improvements.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Documentation improvements -about: Suggest a documentation update -title: '' -labels: documentation -assignees: '' - ---- - -**What were you initially searching for in the docs?** -<!-- Please help us understand how you looked for information that was either not available or unclear --> - -**Is this related to an existing part of the documentation? Please share a link** - -**Describe how we could make it clearer** - -**If you have a proposed update, please share it here** diff --git a/.github/ISSUE_TEMPLATE/documentation_improvements.yml b/.github/ISSUE_TEMPLATE/documentation_improvements.yml new file mode 100644 index 000000000..e750d5192 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_improvements.yml @@ -0,0 +1,50 @@ +name: Documentation improvements +description: Suggest a documentation update to improve everyone's experience +title: "Docs: TITLE" +labels: ["documentation", "triage"] +body: + - type: markdown + attributes: + value: | + Thank you for helping us improve everyone's experience. We review documentation updates on a case by case basis. + - type: textarea + id: search_area + attributes: + label: What were you searching in the docs? + description: Please help us understand how you looked for information that was either unclear or not available + validations: + required: true + - type: input + id: area + attributes: + label: Is this related to an existing documentation section? + description: Please share a link, if applicable + validations: + required: false + - type: textarea + id: idea + attributes: + label: How can we improve? + description: Please share your thoughts on how we can improve this experience + validations: + required: true + - type: textarea + id: suggestion + attributes: + label: Got a suggestion in mind? + description: Please suggest a proposed update + validations: + required: false + - type: checkboxes + id: acknowledgment + attributes: + label: Acknowledgment + options: + - label: I understand the final update might be different from my proposed suggestion, or refused. + required: true + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index b837b7ad5..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: feature-request, triage -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] --> - -**Describe the solution you'd like** -<!-- A clear and concise description of what you want to happen. --> - -**Describe alternatives you've considered** -<!-- A clear and concise description of any alternative solutions or features you've considered. --> - -**Additional context** -<!-- Add any other context or screenshots about the feature request here. --> diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..6aaef0718 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,52 @@ +name: Feature request +description: Suggest an idea for Powertools for AWS Lambda +title: "Feature request: TITLE" +labels: ["feature-request", "triage"] +body: + - type: markdown + attributes: + value: | + Thank you for taking the time to suggest an idea to the Powertools for AWS Lambda (Java) project. + - type: textarea + id: problem + attributes: + label: Use case + description: Please help us understand your use case or problem you're facing + validations: + required: true + - type: textarea + id: suggestion + attributes: + label: Solution/User Experience + description: Please share what a good solution would look like to this use case + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternative solutions + description: Please describe what alternative solutions to this use case, if any + render: Markdown + validations: + required: false + - type: checkboxes + id: acknowledgment + attributes: + label: Acknowledgment + options: + - label: This feature request meets [Powertools for AWS Lambda (Java) Tenets](https://docs.powertools.aws.dev/lambda/java/latest/#tenets) + required: true + - label: Should this be considered in other Powertools for AWS Lambda languages? i.e. [Python](https://github.com/aws-powertools/powertools-lambda-python/), [TypeScript](https://github.com/aws-powertools/powertools-lambda-typescript/), and [.NET](https://github.com/aws-powertools/powertools-lambda-dotnet/) + required: false + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: After creating an issue, please wait until it is triaged and confirmed by a maintainer before implementing it. This will reduce amount of rework and the chance that a pull request gets rejected. + - type: input + id: notes + attributes: + label: Future readers + description: Please not edit this field + value: "Please react with 👍 and your use case to help us understand customer demand." \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/maintenance.yml b/.github/ISSUE_TEMPLATE/maintenance.yml new file mode 100644 index 000000000..1a84ed7ef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/maintenance.yml @@ -0,0 +1,67 @@ +name: Maintenance +description: Suggest an activity to help address tech debt, governance, and anything internal +title: "Maintenance: TITLE" +labels: ["internal", "triage"] +body: + - type: markdown + attributes: + value: | + Thank you for taking the time to help us improve operational excellence. + + *Future readers*: Please react with 👍 and your use case to help us understand customer demand. + - type: textarea + id: activity + attributes: + label: Summary + description: Please provide an overview in one or two paragraphs + validations: + required: true + - type: textarea + id: importance + attributes: + label: Why is this needed? + description: Please help us understand the value so we can prioritize it accordingly + validations: + required: true + - type: dropdown + id: area + attributes: + label: Which area does this relate to? + multiple: true + options: + - Automation + - Governance + - Tests + - Tracer + - Logger + - Metrics + - Parameters + - SQS Large Message Handling + - SQS Batch Processing + - Validation + - Idempotency + - Custom Resources + - Serialization + - Other + - type: textarea + id: suggestion + attributes: + label: Solution + description: If available, please share what a good solution would look like + validations: + required: false + - type: checkboxes + id: acknowledgment + attributes: + label: Acknowledgment + options: + - label: This request meets [Powertools for AWS Lambda (Java) Tenets](https://docs.powertools.aws.dev/lambda-java/#tenets) + required: true + - label: Should this be considered in other Powertools for AWS Lambda languages? i.e. [Python](https://github.com/aws-powertools/powertools-lambda-python/), [TypeScript](https://github.com/aws-powertools/powertools-lambda-typescript/) + required: false + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/rfc.md b/.github/ISSUE_TEMPLATE/rfc.md deleted file mode 100644 index 1db27058d..000000000 --- a/.github/ISSUE_TEMPLATE/rfc.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: RFC -about: Feature design and proposals -title: 'RFC: ' -labels: RFC, triage -assignees: '' - ---- - -## Key information - -* RFC PR: (leave this empty) -* Related issue(s), if known: -* Area: (i.e. Tracer, Metrics, Logger, etc.) -* Meet [tenets](https://awslabs.github.io/aws-lambda-powertools-java/#tenets): (Yes/no) - -## Summary -[summary]: #summary - -> One paragraph explanation of the feature. - -## Motivation -[motivation]: #motivation - -> Why are we doing this? What use cases does it support? What is the expected outcome? - -## Proposal -[proposal]: #proposal - -> This is the bulk of the RFC. - -> Explain the design in enough detail for somebody familiar with Powertools to understand it, and for somebody familiar with the implementation to implement it. - -> This should get into specifics and corner-cases, and include examples of how the feature is used. Any new terminology should be defined here. - -## Drawbacks -[drawbacks]: #drawbacks - -> Why should we *not* do this? - -> Do we need additional dependencies? Impact performance/package size? - -## Rationale and alternatives -[rationale-and-alternatives]: #rationale-and-alternatives - -* **What other designs have been considered? Why not them?** -* **What is the impact of not doing this?** - -## Unresolved questions -[unresolved-questions]: #unresolved-questions - -> Optional, stash area for topics that need further development e.g. TBD diff --git a/.github/ISSUE_TEMPLATE/share_your_work.yml b/.github/ISSUE_TEMPLATE/share_your_work.yml new file mode 100644 index 000000000..f0f879225 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/share_your_work.yml @@ -0,0 +1,56 @@ +name: I Made This (showcase your work) +description: Share what you did with Powertools for AWS Lambda (Java) 💞💞. Blog post, workshops, presentation, sample apps, etc. +title: "[I Made This]: <TITLE>" +labels: ["community-content", "triage"] +body: + - type: markdown + attributes: + value: Thank you for helping spread the word out on Powertools, truly! + - type: input + id: content + attributes: + label: Link to your material + description: | + Please share the original link to your material. + + *Note: Short links will be expanded when added to Powertools for AWS Lambda (Java) documentation* + validations: + required: true + - type: textarea + id: description + attributes: + label: Description + description: Describe in one paragraph what's in it for them (readers) + validations: + required: true + - type: input + id: author + attributes: + label: Preferred contact + description: What's your preferred contact? We'll list it next to this content + validations: + required: true + - type: input + id: author-social + attributes: + label: (Optional) Social Network + description: If different from preferred contact, what's your preferred contact for social interactions? + validations: + required: false + - type: textarea + id: notes + attributes: + label: (Optional) Additional notes + description: | + Any notes you might want to share with us related to this material. + + *Note: These notes are explicitly to Powertools for AWS Lambda (Java) maintainers. It will not be added to the community resources page.* + validations: + required: false + - type: checkboxes + id: acknowledgment + attributes: + label: Acknowledgment + options: + - label: I understand this content may be removed from Powertools for AWS Lambda (Java) documentation if it doesn't conform with the [Code of Conduct](https://aws.github.io/code-of-conduct) + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/support_powertools.yml b/.github/ISSUE_TEMPLATE/support_powertools.yml new file mode 100644 index 000000000..9067d47ec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support_powertools.yml @@ -0,0 +1,64 @@ +name: Support Powertools for AWS Lambda (Java) (become a reference) +description: Add your organization's name or logo to the Powertools for AWS Lambda (Java) documentation +title: "[Support Powertools for AWS Lambda (Java)]: <your organization name>" +labels: ["customer-reference", "triage"] +body: + - type: markdown + attributes: + value: | + Thank you for becoming a reference customer. Your support means a lot to us. It also helps new customers to know who's using it. + + If you would like us to also display your organization's logo, please share a link in the `Company logo` field. + - type: input + id: organization + attributes: + label: Organization Name + description: Please share the name of your organization + placeholder: ACME + validations: + required: true + - type: input + id: name + attributes: + label: Your Name + description: Please share your name + validations: + required: true + - type: input + id: job + attributes: + label: Your current position + description: Please share your current position at your company + validations: + required: true + - type: input + id: logo + attributes: + label: (Optional) Company logo + description: Company logo you want us to display. You also allow us to resize for optimal placement in the documentation. + validations: + required: false + - type: textarea + id: use_case + attributes: + label: (Optional) Use case + description: How are you using Powertools for AWS Lambda (Java) today? *features, etc.* + validations: + required: false + - type: checkboxes + id: other_languages + attributes: + label: Also using other Powertools for AWS Lambda languages? + options: + - label: Java + required: false + - label: TypeScript + required: false + - label: .NET + required: false + - type: markdown + attributes: + value: | + *By raising a Support Powertools for AWS Lambda (Python) issue, you are granting AWS permission to use your company's name (and/or logo) for the limited purpose described here. You are also confirming that you have authority to grant such permission.* + + *You can opt-out at any time by commenting or reopening this issue.* \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/tech_debt.yml b/.github/ISSUE_TEMPLATE/tech_debt.yml new file mode 100644 index 000000000..56cd4b8c7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/tech_debt.yml @@ -0,0 +1,60 @@ +name: Technical debt +description: Suggest an activity to help address technical debt. +title: "Tech debt: TITLE" +labels: ["tech-debt", "triage"] +body: + - type: markdown + attributes: + value: Thank you for taking the time to help us proactively improve delivery velocity, safely. + - type: textarea + id: importance + attributes: + label: Why is this needed? + description: Please help us understand the value so we can prioritize it accordingly + validations: + required: true + - type: dropdown + id: area + attributes: + label: Which area does this relate to? + multiple: true + options: + - Tests + - Static typing + - Tracer + - Logger + - Metrics + - Middleware factory + - Parameters + - Batch processing + - Validation + - Event Source Data Classes + - Parser + - Idempotency + - Feature flags + - JMESPath functions + - Streaming + - Automation + - Other + - type: textarea + id: suggestion + attributes: + label: Suggestion + description: If available, please share what a good solution would look like + validations: + required: false + - type: checkboxes + id: acknowledgment + attributes: + label: Acknowledgment + options: + - label: This request meets [Powertools for AWS Lambda (Python) Tenets](https://docs.powertools.aws.dev/lambda/python/latest/#tenets) + required: true + - label: Should this be considered in other Powertools for AWS Lambda languages? i.e. [Python](https://github.com/aws-powertools/powertools-lambda-python/), [TypeScript](https://github.com/aws-powertools/powertools-lambda-typescript/), and [.NET](https://github.com/aws-powertools/powertools-lambda-dotnet/) + required: false + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 94eb32733..f30703bb4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,25 +1,28 @@ -**Issue #, if available:** +## Summary -## Description of changes: +### Changes -<!--- One or two sentences as a summary of what's being changed --> +> Please provide a summary of what's being changed -**Checklist** +<!-- What is this PR solving? Write a clear description or reference the issue(s) it addresses. --> -<!--- Leave unchecked if your change doesn't seem to apply --> +> Please add the issue number below, if no issue is present the PR might get blocked and not be reviewed -* [ ] [Meet tenets criteria](https://awslabs.github.io/aws-lambda-powertools-java/#tenets) -* [ ] Update tests -* [ ] Update docs -* [ ] PR title follows [conventional commit semantics]() +**Issue number:** -## Breaking change checklist +<!------- +Before creating the pull request, please make sure you do the following: -<!--- Ignore if it's not a breaking change --> +- Read the Contributing Guidelines at https://github.com/aws-powertools/powertools-lambda-java/blob/main/CONTRIBUTING.md#sending-a-pull-request +- Check that there isn't already a PR that addresses the same issue. If you find a duplicate, please leave a comment under the existing PR so we can discuss how to move forward +- Check that the change meets the project's tenets https://docs.powertools.aws.dev/lambda/java/latest/#tenets +- Add a PR title that follows the conventional commit semantics - https://www.conventionalcommits.org/en/v1.0.0/ +- If relevant, add tests that prove that the change is effective and works +- Whenever relevant, make sure to comment functions/methods/types and make appropriate changes to the documentation +-------> -**RFC issue #**: - -* [ ] Migration process documented -* [ ] Implement warnings (if it can live side by side) +--- By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. + +**Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. \ No newline at end of file diff --git a/.github/actions/gradle/action.yml b/.github/actions/gradle/action.yml new file mode 100644 index 000000000..e69de29bb diff --git a/.github/actions/restore/action.yml b/.github/actions/restore/action.yml new file mode 100644 index 000000000..e69de29bb diff --git a/.github/actions/seal/action.yml b/.github/actions/seal/action.yml new file mode 100644 index 000000000..079496c8c --- /dev/null +++ b/.github/actions/seal/action.yml @@ -0,0 +1,78 @@ +name: Seal and hash source code +description: | + Seals and creates a SHA256SUM of an artifact for storage + + Process: + 1. Create a unique name based on environment details + 2. Compress work directory or specified path + 3. Hash compressed file + 4. Upload archive using `actions/upload-artifact` + + Usage: + ```yml + - id: seal + name: Seal + uses: .github/actions/seal + with: + prefix: foo + ``` + +inputs: + prefix: + description: Prefix to use when exporting artifact + required: true +outputs: + hash: + description: SHA256SUM hash of compressed files + value: ${{ steps.hash.outputs.hash }} + artifact_name: + description: Artifact name + value: ${{ steps.artifact_name.outputs.artifact_name }} + +runs: + using: composite + steps: + - id: adjust_path + name: Adjust path + shell: bash + run: echo "${{ github.action_path }}" >> $GITHUB_PATH + + - id: artifact_name + name: Export final artifact name + env: + GITHUB_RUN_ID: ${{ github.run_id }} + ARTIFACT_PREFIX: ${{ inputs.prefix }} + shell: bash + run: | + echo "artifact_name=${ARTIFACT_PREFIX}-${GITHUB_RUN_ID}" >> "$GITHUB_OUTPUT" + + - id: compress + name: Create tarball for entire source + env: + ARTIFACT_NAME: ${{ steps.artifact_name.outputs.artifact_name }} + shell: bash + run: | + tar --exclude-vcs -cvf "${ARTIFACT_NAME}".tar * + + - id: hash + name: Hash + env: + ARTIFACT_NAME: ${{ steps.artifact_name.outputs.artifact_name }} + shell: bash + run: | + echo "hash=$(openssl dgst -sha256 -binary "${{ ARTIFACT_NAME }}".tar | openssl enc -base64)" >> "$GITHUB_OUTPUT" + + - name: Upload artifacts + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 + with: + if-no-files-found: error + name: ${{ steps.artifact_name.outputs.artifact_name }} + path: ${{ steps.artifact_name.outputs.artifact_name }}.tar + retention-days: 1 + + - name: Remove archive + env: + ARTIFACT_NAME: ${{ steps.artifact_name.outputs.artifact_name }} + shell: bash + run: | + rm -f "${ARTIFACT_NAME}.tar" \ No newline at end of file diff --git a/.github/actions/version/action.yml b/.github/actions/version/action.yml new file mode 100644 index 000000000..f0f0516ee --- /dev/null +++ b/.github/actions/version/action.yml @@ -0,0 +1,53 @@ +name: Version Java Project +description: | + Versions the maven project using an input + + Process: + 1. Grab current version from project.version variable from maven + 2. Set new version using maven-versions-plugin + + Usage: + ```yml + - id: version + name: version + uses: .github/actions/version + with: + new_version: 1.20.0 + snapshot: 'false' + ``` + +inputs: + new_version: + description: New package version, expressed as SemVer (1.x.y) + required: true + snapshot: + description: New version is a SNAPSHOT release + required: true + default: 'false' + +outputs: + old_version: + description: Current version of project + value: ${{ steps.current_version.outputs.current_version}} + +runs: + using: composite + steps: + - id: current_version + name: Get current version + shell: bash + run: | + echo "current_version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_OUTPUT + + - id: replace_version + name: Replace current version + shell: bash + run: | + mvn versions:set -DnewVersion=${{ inputs.new_version }} -DprocessAllModules=true -DallowSnapshots=true + + - id: asset_version + name: Replace version for assets + if: ${{ inputs.snapshot == 'false' }} + shell: bash + run: | + grep "${{ steps.current_version.outputs.current_version }}" -r . --include build.gradle --include build.gradle.kts --include mkdocs.yml --include README.md -l | xargs sed -i 's#${{ steps.current_version.outputs.current_version }}#${{ inputs.new_version }}#' \ No newline at end of file diff --git a/.github/auto_assign-issues.yml b/.github/auto_assign-issues.yml deleted file mode 100644 index 652e18ad5..000000000 --- a/.github/auto_assign-issues.yml +++ /dev/null @@ -1,8 +0,0 @@ -addAssignees: true - -# The list of users to assign to new issues. -# If empty or not provided, the repository owner is assigned -assignees: - - msailes - - pankajagrawal16 - - stevehouel diff --git a/.github/dependabot.yml b/.github/dependabot.yml index caf24d1f9..caa9934ca 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,22 @@ version: 2 updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: chore + + - package-ecosystem: docker + directories: + - "/powertools-e2e-tests/src/test/resources/docker" + - "/docs" + - "/examples/**" + schedule: + interval: daily + commit-message: + prefix: chore + - package-ecosystem: "maven" directory: "/" schedule: diff --git a/.github/dependency-review-config.yml b/.github/dependency-review-config.yml new file mode 100644 index 000000000..2ea218503 --- /dev/null +++ b/.github/dependency-review-config.yml @@ -0,0 +1,32 @@ +allow-licenses: + - 'Apache-1.1' + - 'Apache-2.0' + - 'ISC' + - 'MIT' + - 'MIT-0' + - 'MIT-CMU' + - 'MIT-enna' + - 'MIT-feh' + - 'MIT-Festival' + - 'MIT-Modern-Variant' + - 'MIT-open-group' + - 'MIT-testregex' + - 'MIT-Wu' + - 'BSD-1-Clause' + - 'BSD-2-Clause' + - 'BSD-2-Clause-Views' + - 'BSD-3-Clause' + - 'BSD-3-Clause-Attribution' + - 'BSD-3-Clause-Clear' + - 'BSD-3-Clause-flex' + - 'BSD-3-Clause-HP' + - 'BSD-3-Clause-LBNL' + - 'BSD-3-Clause-Modification' + - 'BSD-3-Clause-No-Military-License' + - 'BSD-3-Clause-No-Nuclear-License' + - 'BSD-3-Clause-No-Nuclear-License-2014' + - 'BSD-3-Clause-No-Nuclear-Warranty' + - 'BSD-3-Clause-Open-MPI' + # TT: D290816995 + - 'UPL-1.0' +comment-summary-in-pr: on-failure diff --git a/.github/pmd-ruleset.xml b/.github/pmd-ruleset.xml new file mode 100644 index 000000000..1bc5f3020 --- /dev/null +++ b/.github/pmd-ruleset.xml @@ -0,0 +1,647 @@ +<?xml version="1.0"?> +<ruleset name="dogfood7" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + <description>Rules to check Powertools for Lambda</description> + <!-- + Originally copied from: https://github.com/pmd/build-tools/blob/main/src/main/resources/net/sourceforge/pmd/pmd-dogfood-config.xml + --> + + <!-- + Note: Eventually, this ruleset should include all rules and exclude those, + which we know are explicitly decided as not applicable to PMD itself. + This is the most encompassing form of RuleSet. + + For starting, we add rule by rule and use an incremental approach. + See [#361](https://github.com/pmd/pmd/issues/361). + + The original dogfood ruleset is available at: + https://github.com/pmd/pmd/blob/f9ef2c8c4bc23f1349597b827c8f072b7cade8a5/pmd-core/src/main/resources/rulesets/internal/dogfood.xml + --> + + <!-- <rule ref="category/java/bestpractices.xml/AbstractClassWithoutAbstractMethod" /> --> + <!-- <rule ref="category/java/bestpractices.xml/AccessorClassGeneration" /> --> + <!-- <rule ref="category/java/bestpractices.xml/AccessorMethodGeneration" /> --> + <!-- <rule ref="category/java/bestpractices.xml/ArrayIsStoredDirectly" /> --> + <!-- <rule ref="category/java/bestpractices.xml/AvoidPrintStackTrace" /> --> + <!-- <rule ref="category/java/bestpractices.xml/AvoidReassigningParameters" /> --> + <!-- <rule ref="category/java/bestpractices.xml/AvoidStringBufferField" /> --> + <rule ref="category/java/bestpractices.xml/AvoidUsingHardCodedIP"> + <priority>1</priority> + </rule> + <rule ref="category/java/bestpractices.xml/CheckResultSet"> + <priority>1</priority> + </rule> + <rule ref="category/java/bestpractices.xml/ConstantsInInterface"> + <priority>1</priority> + </rule> + <rule ref="category/java/bestpractices.xml/DefaultLabelNotLastInSwitch"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/bestpractices.xml/ForLoopCanBeForeach" /> --> + <!-- <rule ref="category/java/bestpractices.xml/GuardLogStatement" /> --> + <!-- <rule ref="category/java/bestpractices.xml/JUnit4SuitesShouldUseSuiteAnnotation" /> --> + <!-- <rule ref="category/java/bestpractices.xml/UnitTestShouldUseAfterAnnotation" /> --> + <!-- <rule ref="category/java/bestpractices.xml/UnitTestShouldUseBeforeAnnotation" /> --> + <!-- <rule ref="category/java/bestpractices.xml/UnitTestShouldUseTestAnnotation" /> --> + <!-- <rule ref="category/java/bestpractices.xml/UnitTestAssertionsShouldIncludeMessage" /> --> + <!-- <rule ref="category/java/bestpractices.xml/UnitTestContainsTooManyAsserts" /> --> + <!-- <rule ref="category/java/bestpractices.xml/UnitTestShouldIncludeAssert" /> --> + <!-- <rule ref="category/java/bestpractices.xml/JUnitUseExpected" /> --> + <rule ref="category/java/bestpractices.xml/LooseCoupling"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/bestpractices.xml/MethodReturnsInternalArray" /> --> + <rule ref="category/java/bestpractices.xml/MissingOverride"> + <priority>1</priority> + </rule> + <rule ref="category/java/bestpractices.xml/OneDeclarationPerLine"> + <priority>1</priority> + </rule> + <rule ref="category/java/bestpractices.xml/LiteralsFirstInComparisons"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/bestpractices.xml/PreserveStackTrace" /> --> + <rule ref="category/java/bestpractices.xml/PrimitiveWrapperInstantiation"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/bestpractices.xml/ReplaceEnumerationWithIterator" /> --> + <!-- <rule ref="category/java/bestpractices.xml/ReplaceHashtableWithMap" /> --> + <!-- <rule ref="category/java/bestpractices.xml/ReplaceVectorWithList" /> --> + <!-- <rule ref="category/java/bestpractices.xml/SimplifiableTestAssertion" /> --> + <rule ref="category/java/bestpractices.xml/NonExhaustiveSwitch"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/bestpractices.xml/SystemPrintln" /> --> + <rule ref="category/java/bestpractices.xml/UnusedFormalParameter"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/bestpractices.xml/UnusedImports" /> --> + <rule ref="category/java/bestpractices.xml/UnusedLocalVariable"> + <priority>1</priority> + </rule> + <rule ref="category/java/bestpractices.xml/UnusedPrivateField"> + <priority>1</priority> + </rule> + <rule ref="category/java/bestpractices.xml/UnusedPrivateMethod"> + <priority>1</priority> + </rule> + <rule ref="category/java/bestpractices.xml/UseCollectionIsEmpty"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/bestpractices.xml/UseVarargs" /> --> + + <!-- <rule ref="category/java/codestyle.xml/AtLeastOneConstructor" /> --> + <!-- <rule ref="category/java/codestyle.xml/AvoidDollarSigns" /> --> + <!-- <rule ref="category/java/codestyle.xml/AvoidFinalLocalVariable" /> --> + <rule ref="category/java/codestyle.xml/AvoidProtectedFieldInFinalClass"> + <priority>1</priority> + </rule> + <rule ref="category/java/codestyle.xml/AvoidProtectedMethodInFinalClassNotExtending"> + <priority>1</priority> + </rule> + <rule ref="category/java/codestyle.xml/AvoidUsingNativeCode"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/codestyle.xml/BooleanGetMethodName" /> --> + <!-- <rule ref="category/java/codestyle.xml/CallSuperInConstructor" /> --> + <!-- <rule ref="category/java/codestyle.xml/ClassNamingConventions" /> --> + <!-- <rule ref="category/java/codestyle.xml/CommentDefaultAccessModifier" /> --> + <!-- <rule ref="category/java/codestyle.xml/ConfusingTernary" /> --> + <rule ref="category/java/codestyle.xml/ControlStatementBraces"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/codestyle.xml/DefaultPackage" /> --> + <!-- <rule ref="category/java/codestyle.xml/DontImportJavaLang" /> --> + <!-- <rule ref="category/java/codestyle.xml/DuplicateImports" /> --> + <!-- <rule ref="category/java/codestyle.xml/EmptyMethodInAbstractClassShouldBeAbstract" /> --> + <rule ref="category/java/codestyle.xml/ExtendsObject"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/codestyle.xml/FieldDeclarationsShouldBeAtStartOfClass" /> --> + <!-- <rule ref="category/java/codestyle.xml/FieldNamingConventions" /> --> + <rule ref="category/java/codestyle.xml/ForLoopShouldBeWhileLoop"> + <priority>1</priority> + </rule> + <rule ref="category/java/codestyle.xml/FormalParameterNamingConventions"> + <priority>1</priority> + </rule> + + <!-- <rule ref="category/java/codestyle.xml/GenericsNaming" /> --> + <rule ref="category/java/codestyle.xml/IdenticalCatchBranches"> + <priority>3</priority> + </rule> + <!-- <rule ref="category/java/codestyle.xml/LinguisticNaming" /> --> + <!-- <rule ref="category/java/codestyle.xml/LocalHomeNamingConvention" /> --> + <!-- <rule ref="category/java/codestyle.xml/LocalInterfaceSessionNamingConvention" /> --> + <!-- <rule ref="category/java/codestyle.xml/LocalVariableCouldBeFinal" /> --> + <!-- <rule ref="category/java/codestyle.xml/LocalVariableNamingConventions" /> --> + <!-- <rule ref="category/java/codestyle.xml/LongVariable" /> --> + <!-- <rule ref="category/java/codestyle.xml/MDBAndSessionBeanNamingConvention" /> --> + <!-- <rule ref="category/java/codestyle.xml/MethodArgumentCouldBeFinal" /> --> + <!-- <rule ref="category/java/codestyle.xml/MethodNamingConventions" /> --> + <!-- <rule ref="category/java/codestyle.xml/NoPackage" /> --> + <!-- <rule ref="category/java/codestyle.xml/OnlyOneReturn" /> --> + <!-- <rule ref="category/java/codestyle.xml/PackageCase" /> --> + <!-- <rule ref="category/java/codestyle.xml/PrematureDeclaration" /> --> + <!-- <rule ref="category/java/codestyle.xml/RemoteInterfaceNamingConvention" /> --> + <!-- <rule ref="category/java/codestyle.xml/RemoteSessionInterfaceNamingConvention" /> --> + <!-- <rule ref="category/java/codestyle.xml/ShortClassName" /> --> + <!-- <rule ref="category/java/codestyle.xml/ShortMethodName" /> --> + <!-- <rule ref="category/java/codestyle.xml/ShortVariable" /> --> + <!-- <rule ref="category/java/codestyle.xml/TooManyStaticImports" /> --> + <rule ref="category/java/codestyle.xml/UnnecessaryAnnotationValueElement"> + <priority>1</priority> + </rule> + <rule ref="category/java/codestyle.xml/UnnecessaryConstructor"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/codestyle.xml/UnnecessaryFullyQualifiedName" /> --> + <rule ref="category/java/codestyle.xml/UnnecessaryLocalBeforeReturn"> + <priority>1</priority> + </rule> + <rule ref="category/java/codestyle.xml/UnnecessaryModifier"> + <priority>1</priority> + </rule> + <rule ref="category/java/codestyle.xml/UnnecessaryReturn"> + <priority>1</priority> + </rule> + <rule ref="category/java/codestyle.xml/UseDiamondOperator"> + <priority>1</priority> + </rule> + <rule ref="category/java/codestyle.xml/UselessParentheses"> + <priority>1</priority> + </rule> + <rule ref="category/java/codestyle.xml/UselessQualifiedThis"> + <priority>1</priority> + </rule> + + <!-- <rule ref="category/java/design.xml/AbstractClassWithoutAnyMethod" /> --> + <!-- <rule ref="category/java/design.xml/AvoidCatchingGenericException" /> --> + <!-- <rule ref="category/java/design.xml/AvoidDeeplyNestedIfStmts" /> --> + <!-- <rule ref="category/java/design.xml/AvoidRethrowingException" /> --> + <!-- <rule ref="category/java/design.xml/AvoidThrowingNewInstanceOfSameException" /> --> + <!-- <rule ref="category/java/design.xml/AvoidThrowingNullPointerException" /> --> + <!-- <rule ref="category/java/design.xml/AvoidThrowingRawExceptionTypes" /> --> + <rule ref="category/java/design.xml/ClassWithOnlyPrivateConstructorsShouldBeFinal"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/design.xml/CollapsibleIfStatements" /> --> + <!-- <rule ref="category/java/design.xml/CouplingBetweenObjects" /> --> + <!-- <rule ref="category/java/design.xml/CyclomaticComplexity" /> --> + <!-- <rule ref="category/java/design.xml/DataClass" /> --> + <!-- <rule ref="category/java/design.xml/DoNotExtendJavaLangError" /> --> + <!-- <rule ref="category/java/design.xml/ExceptionAsFlowControl" /> --> + <!-- <rule ref="category/java/design.xml/ExcessiveClassLength" /> --> + <!-- <rule ref="category/java/design.xml/ExcessiveImports" /> --> + <!-- <rule ref="category/java/design.xml/ExcessiveMethodLength" /> --> + <!-- <rule ref="category/java/design.xml/ExcessiveParameterList" /> --> + <!-- <rule ref="category/java/design.xml/ExcessivePublicCount" /> --> + <rule ref="category/java/design.xml/FinalFieldCouldBeStatic"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/design.xml/GodClass" /> --> + <!-- <rule ref="category/java/design.xml/ImmutableField" /> --> + <!-- <rule ref="category/java/design.xml/LawOfDemeter" /> --> + <rule ref="category/java/design.xml/LogicInversion"> + <priority>1</priority> + </rule> + <!-- Don't let the Language specific APIs leak out --> + <!-- + <rule ref="category/java/design.xml/LoosePackageCoupling"> + <properties> + <property name="packages"><value>net.sourceforge.pmd.lang,net.sourceforge.pmd.lang.java,net.sourceforge.pmd.lang.jsp,net.sourceforge.pmd.lang.ecmascript,net.sourceforge.pmd.lang.cpp</value></property> + <property name="classes"> + <value>net.sourceforge.pmd.lang.Language,net.sourceforge.pmd.lang.LanguageVersion,net.sourceforge.pmd.lang.LanguageVersionDiscoverer,net.sourceforge.pmd.lang.LanguageVersionHandler,net.sourceforge.pmd.lang.Parser,net.sourceforge.pmd.lang.ast.Node</value> + </property> + </properties> + </rule> + --> + <!-- <rule ref="category/java/design.xml/ModifiedCyclomaticComplexity" /> --> + <!-- <rule ref="category/java/design.xml/NcssConstructorCount" /> --> + <!-- <rule ref="category/java/design.xml/NcssCount" /> --> + <!-- <rule ref="category/java/design.xml/NcssMethodCount" /> --> + <!-- <rule ref="category/java/design.xml/NcssTypeCount" /> --> + <!-- <rule ref="category/java/design.xml/NPathComplexity" /> --> + <!-- <rule ref="category/java/design.xml/SignatureDeclareThrowsException" /> --> + <rule ref="category/java/design.xml/SimplifiedTernary"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/design.xml/SimplifyBooleanExpressions" /> --> + <rule ref="category/java/design.xml/SimplifyBooleanReturns"> + <priority>1</priority> + </rule> + <rule ref="category/java/design.xml/SimplifyConditional"> + <priority>1</priority> + </rule> + <rule ref="category/java/design.xml/SingularField"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/design.xml/StdCyclomaticComplexity" /> --> + <!-- <rule ref="category/java/design.xml/SwitchDensity" /> --> + <!-- <rule ref="category/java/design.xml/TooManyFields" /> --> + <!-- <rule ref="category/java/design.xml/TooManyMethods" /> --> + <rule ref="category/java/design.xml/UselessOverridingMethod"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/design.xml/UseObjectForClearerAPI" /> --> + <rule ref="category/java/design.xml/UseUtilityClass"> + <priority>1</priority> + </rule> + + <!-- <rule ref="category/java/documentation.xml/CommentContent" /> --> + <!-- <rule ref="category/java/documentation.xml/CommentRequired" /> --> + <!-- <rule ref="category/java/documentation.xml/CommentSize" /> --> + <rule ref="category/java/documentation.xml/UncommentedEmptyConstructor"> + <priority>1</priority> + </rule> + <rule ref="category/java/documentation.xml/UncommentedEmptyMethodBody"> + <priority>1</priority> + </rule> + + <rule ref="category/java/errorprone.xml/AssignmentInOperand"> + <priority>1</priority> + <properties> + <property name="allowWhile" value="true"/> + </properties> + </rule> + <rule ref="category/java/errorprone.xml/AssignmentToNonFinalStatic"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/AvoidAccessibilityAlteration" /> --> + <!-- <rule ref="category/java/errorprone.xml/AvoidAssertAsIdentifier" /> --> + <rule ref="category/java/errorprone.xml/AvoidBranchingStatementAsLastInLoop"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/AvoidCallingFinalize" /> --> + <!-- <rule ref="category/java/errorprone.xml/AvoidCatchingNPE" /> --> + <rule ref="category/java/errorprone.xml/AvoidCatchingThrowable"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/AvoidDecimalLiteralsInBigDecimalConstructor"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/AvoidDuplicateLiterals" /> --> + <!-- <rule ref="category/java/errorprone.xml/AvoidEnumAsIdentifier" /> --> + <!-- <rule ref="category/java/errorprone.xml/AvoidFieldNameMatchingMethodName" /> --> + <!-- <rule ref="category/java/errorprone.xml/AvoidFieldNameMatchingTypeName" /> --> + <rule ref="category/java/errorprone.xml/AvoidInstanceofChecksInCatchClause"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/AvoidLiteralsInIfCondition" /> --> + <!-- <rule ref="category/java/errorprone.xml/AvoidLosingExceptionInformation" /> --> + <rule ref="category/java/errorprone.xml/AvoidMultipleUnaryOperators"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/AvoidUsingOctalValues"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/BeanMembersShouldSerialize" /> --> + <rule ref="category/java/errorprone.xml/BrokenNullCheck"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/CallSuperFirst" /> --> + <!-- <rule ref="category/java/errorprone.xml/CallSuperLast" /> --> + <rule ref="category/java/errorprone.xml/CheckSkipResult"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/ClassCastExceptionWithToArray"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/CloneMethodMustBePublic"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/CloneMethodMustImplementCloneable"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/CloneMethodReturnTypeMustMatchClassName"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/CloseResource"> + <priority>1</priority> + <properties> + <property name="closeTargets" value="close,IOUtils.closeQuietly,IOUtil.closeQuietly" /> + </properties> + </rule> + <rule ref="category/java/errorprone.xml/CompareObjectsWithEquals"> + <priority>1</priority> + <properties> + <property name="typesThatCompareByReference" value="java.lang.Enum,java.lang.Class,net.sourceforge.pmd.lang.ast.Node,net.sourceforge.pmd.lang.ast.GenericToken"/> + </properties> + </rule> + <rule ref="category/java/errorprone.xml/ComparisonWithNaN"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/ConstructorCallsOverridableMethod" /> --> + <!-- <rule ref="category/java/errorprone.xml/DataflowAnomalyAnalysis" /> --> + <rule ref="category/java/errorprone.xml/DoNotCallGarbageCollectionExplicitly"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/DoNotCallSystemExit" /> --> + <!-- <rule ref="category/java/errorprone.xml/DoNotExtendJavaLangThrowable" /> --> + <!-- <rule ref="category/java/errorprone.xml/DoNotHardCodeSDCard" /> --> + <!-- <rule ref="category/java/errorprone.xml/DoNotThrowExceptionInFinally" /> --> + <rule ref="category/java/errorprone.xml/DontImportSun"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/DontUseFloatTypeForLoopIndices"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/EmptyCatchBlock"> + <priority>1</priority> + <properties> + <property name="allowCommentedBlocks" value="true" /> + </properties> + </rule> + <!-- <rule ref="category/java/errorprone.xml/EmptyFinalizer" /> --> + <!-- <rule ref="category/java/errorprone.xml/EmptyFinallyBlock" /> --> + <!-- <rule ref="category/java/errorprone.xml/EmptyIfStmt" /> --> + <!-- <rule ref="category/java/errorprone.xml/EmptyInitializer" /> --> + <!-- <rule ref="category/java/errorprone.xml/EmptyStatementBlock" /> --> + <!-- <rule ref="category/java/errorprone.xml/EmptyStatementNotInLoop" /> --> + <!-- <rule ref="category/java/errorprone.xml/EmptySwitchStatements" /> --> + <!-- <rule ref="category/java/errorprone.xml/EmptySynchronizedBlock" /> --> + <!-- <rule ref="category/java/errorprone.xml/EmptyTryBlock" /> --> + <!-- <rule ref="category/java/errorprone.xml/EmptyWhileStmt" /> --> + <rule ref="category/java/errorprone.xml/EqualsNull"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/FinalizeDoesNotCallSuperFinalize" /> --> + <!-- <rule ref="category/java/errorprone.xml/FinalizeOnlyCallsSuperFinalize" /> --> + <!-- <rule ref="category/java/errorprone.xml/FinalizeOverloaded" /> --> + <!-- <rule ref="category/java/errorprone.xml/FinalizeShouldBeProtected" /> --> + <rule ref="category/java/errorprone.xml/IdempotentOperations"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/ImplicitSwitchFallThrough"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/ImportFromSamePackage" /> --> + <rule ref="category/java/errorprone.xml/InstantiationToGetClass"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/InvalidSlf4jMessageFormat" /> --> + <rule ref="category/java/errorprone.xml/JumbledIncrementer"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/JUnitSpelling" /> --> + <!-- <rule ref="category/java/errorprone.xml/JUnitStaticSuite" /> --> + <!-- <rule ref="category/java/errorprone.xml/LoggerIsNotStaticFinal" /> --> + <!-- <rule ref="category/java/errorprone.xml/MethodWithSameNameAsEnclosingClass" /> --> + <rule ref="category/java/errorprone.xml/MisplacedNullCheck"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/MissingSerialVersionUID" /> --> + <rule ref="category/java/errorprone.xml/MissingStaticMethodInNonInstantiatableClass"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/MoreThanOneLogger" /> --> + <rule ref="category/java/errorprone.xml/NonCaseLabelInSwitch"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/NonStaticInitializer"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/NullAssignment" /> --> + <rule ref="category/java/errorprone.xml/OverrideBothEqualsAndHashcode"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/ProperCloneImplementation"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/ProperLogger" /> --> + <rule ref="category/java/errorprone.xml/ReturnEmptyCollectionRatherThanNull"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/ReturnEmptyCollectionRatherThanNull" /> --> + <rule ref="category/java/errorprone.xml/ReturnFromFinallyBlock"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/SimpleDateFormatNeedsLocale" /> --> + <rule ref="category/java/errorprone.xml/SingleMethodSingleton"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/SingletonClassReturningNewInstance"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/StaticEJBFieldShouldBeFinal" /> --> + <!-- <rule ref="category/java/errorprone.xml/StringBufferInstantiationWithChar" /> --> + <!-- <rule ref="category/java/errorprone.xml/SuspiciousEqualsMethodName" /> --> + <!-- <rule ref="category/java/errorprone.xml/SuspiciousHashcodeMethodName" /> --> + <rule ref="category/java/errorprone.xml/SuspiciousOctalEscape"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/TestClassWithoutTestCases" /> --> + <rule ref="category/java/errorprone.xml/UnconditionalIfStatement"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/UnnecessaryBooleanAssertion" /> --> + <!-- <rule ref="category/java/errorprone.xml/UnnecessaryCaseChange" /> --> + <rule ref="category/java/errorprone.xml/UnnecessaryConversionTemporary"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/UnusedNullCheckInEquals"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/UseCorrectExceptionLogging" /> --> + <!-- <rule ref="category/java/errorprone.xml/UseEqualsToCompareStrings" /> --> + <rule ref="category/java/errorprone.xml/UselessOperationOnImmutable"> + <priority>1</priority> + </rule> + <rule ref="category/java/errorprone.xml/UseLocaleWithCaseConversions"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/errorprone.xml/UseProperClassLoader" /> --> + + <!-- <rule ref="category/java/multithreading.xml/AvoidSynchronizedAtMethodLevel" /> --> + <rule ref="category/java/multithreading.xml/AvoidThreadGroup"> + <priority>1</priority> + </rule> + <rule ref="category/java/multithreading.xml/AvoidUsingVolatile"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/multithreading.xml/DoNotUseThreads" /> --> + <rule ref="category/java/multithreading.xml/DontCallThreadRun"> + <priority>1</priority> + </rule> + <rule ref="category/java/multithreading.xml/DoubleCheckedLocking"> + <priority>1</priority> + </rule> + <rule ref="category/java/multithreading.xml/NonThreadSafeSingleton"> + <priority>1</priority> + </rule> + <rule ref="category/java/multithreading.xml/UnsynchronizedStaticFormatter"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/multithreading.xml/UseConcurrentHashMap" /> --> + <rule ref="category/java/multithreading.xml/UseNotifyAllInsteadOfNotify"> + <priority>1</priority> + </rule> + + <!-- <rule ref="category/java/performance.xml/AddEmptyString" /> --> + <!-- <rule ref="category/java/performance.xml/AppendCharacterWithChar" /> --> + <!-- <rule ref="category/java/performance.xml/AvoidArrayLoops" /> --> + <rule ref="category/java/performance.xml/AvoidFileStream"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/performance.xml/AvoidInstantiatingObjectsInLoops" /> --> + <rule ref="category/java/performance.xml/BigIntegerInstantiation"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/performance.xml/ConsecutiveAppendsShouldReuse" /> --> + <!-- <rule ref="category/java/performance.xml/ConsecutiveLiteralAppends" /> --> + <!-- <rule ref="category/java/performance.xml/InefficientEmptyStringCheck" /> --> + <!-- <rule ref="category/java/performance.xml/InefficientStringBuffering" /> --> + <!-- <rule ref="category/java/performance.xml/InsufficientStringBufferDeclaration" /> --> + <rule ref="category/java/performance.xml/OptimizableToArrayCall"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/performance.xml/RedundantFieldInitializer" /> --> + <!-- <rule ref="category/java/performance.xml/SimplifyStartsWith" /> --> + <!-- <rule ref="category/java/performance.xml/StringInstantiation" /> --> + <!-- <rule ref="category/java/performance.xml/StringToString" /> --> + <rule ref="category/java/performance.xml/TooFewBranchesForSwitch"> + <priority>1</priority> + </rule> + <!-- <rule ref="category/java/performance.xml/UnnecessaryWrapperObjectCreation" /> --> + <!-- <rule ref="category/java/performance.xml/UseArrayListInsteadOfVector" /> --> + <!-- <rule ref="category/java/performance.xml/UseArraysAsList" /> --> + <!-- <rule ref="category/java/performance.xml/UseIndexOfChar" /> --> + <!-- <rule ref="category/java/performance.xml/UselessStringValueOf" /> --> + <!-- <rule ref="category/java/performance.xml/UseStringBufferForStringAppends" /> --> + <!-- <rule ref="category/java/performance.xml/UseStringBufferLength" /> --> + + <!-- <rule ref="category/java/security.xml/InsecureCryptoIv" /> --> + + + <!-- Note: These are the custom rule ported to PMD 7 --> + + <!-- PMD specific custom rules --> + <rule name="UseInstanceofToCompareClasses" + language="java" + since="5.0" + message="replace o.getClass().equals(MyClass.class) with o instanceof MyClass" + class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"> + <description>replace o.getClass().equals(MyClass.class) with o instanceof MyClass. Make sure MyClass doesn't have descendants</description> + <priority>1</priority> + <properties> + <property name="xpath"> + <value> + <![CDATA[ +//MethodCall[pmd-java:matchesSig("_#equals(java.lang.Object)")] + [MethodCall[pmd-java:matchesSig("_#getClass()")]] + [ArgumentList/ClassLiteral] +]]> + </value> + </property> + </properties> + </rule> + + <rule name="ReversedUseInstanceofToCompareClasses" + language="java" + since="5.0" + message="replace MyClass.class.equals(o.getClass()) with o instanceof MyClass" + class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"> + <description>replace MyClass.class.equals(o.getClass()) with o instanceof MyClass. Make sure MyClass doesn't have descendants</description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> + <![CDATA[ +//MethodCall[pmd-java:matchesSig("_#equals(java.lang.Object)")] + [ClassLiteral] + [ArgumentList/MethodCall[pmd-java:matchesSig("_#getClass()")]] +]]> + </value> + </property> + </properties> + </rule> + + <rule name="DontCallSuperVisitWhenUsingRuleChain" + language="java" + message="Don't call super.visit() when using the rulechain" + typeResolution="true" + class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"> + <description>Calling super.visit breaks the rulechain, by starting a full visitor run from the passed node downwards. Add all needed nodes to the rulechain instead.</description> + <priority>1</priority> + <properties> + <property name="xpath"> + <value> + <![CDATA[ +( +(: java rule chain rule :) +//ClassDeclaration[pmd-java:typeIs('net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule')] +| +(: generic rule that implements buildTargetSelector :) +//ClassDeclaration[pmd-java:typeIs('net.sourceforge.pmd.lang.rule.AbstractRule')] + [*/MethodDeclaration[@Name = 'buildTargetSelector']] +) + (: but calling super.visit! :) + [*/MethodDeclaration//MethodCall[SuperExpression][@MethodName = 'visit']] +]]> + </value> + </property> + </properties> + </rule> + + <rule name="AlwaysCallSuperWhenNotUsingRuleChain" + language="java" + message="Always call super.visit() when not using rulechain" + typeResolution="true" + class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"> + <description>Just returning without calling super stops visiting of nested nodes like inner classes.</description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> + <![CDATA[ +//ClassDeclaration + [ExtendsList/ClassType[pmd-java:typeIs('net.sourceforge.pmd.lang.rule.AbstractRule')]] + [not(ExtendsList/ClassType[pmd-java:typeIs('net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule')])] + [not(*/MethodDeclaration[@Name = 'buildTargetSelector'])] + [*/MethodDeclaration[@Name = 'visit'][not(.//MethodCall[@MethodName = 'visit']/SuperExpression)]] +]]> + </value> + </property> + </properties> + </rule> + + <!-- Idea from https://github.com/pmd/pmd/pull/3609#discussion_r748292071 --> + <rule name="ReuseInvocationMatcher" + language="java" + message="Reuse InvocationMatcher" + typeResolution="true" + class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"> + <description>Share the invocation matcher and not create a new one every time</description> + <priority>1</priority> + <properties> + <property name="xpath"> + <value> + <![CDATA[ +//MethodCall[pmd-java:matchesSig("net.sourceforge.pmd.lang.java.types.InvocationMatcher#matchesCall(_)")] + [MethodCall[pmd-java:matchesSig("net.sourceforge.pmd.lang.java.types.InvocationMatcher#parse(java.lang.String)")]] +]]> + </value> + </property> + </properties> + </rule> + + <rule name="DoNotUseJavaUtilLogging" + language="java" + since="7.0.0" + message="Use slf4j: LoggerFactory.getLogger(MyClass.class)" + class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"> + <description>Use slf4j: LoggerFactory.getLogger(MyClass.class)</description> + <priority>1</priority> + <properties> + <property name="xpath"> + <value> + <![CDATA[ +//ClassDeclaration[//ImportDeclaration[@ImportedName = 'java.util.logging.Logger']] +]]> + </value> + </property> + </properties> + </rule> +</ruleset> diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml deleted file mode 100644 index f3cecfad4..000000000 --- a/.github/workflows/auto-merge.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Auto merge if dependabot PR - -on: - workflow_run: - workflows: ["Build"] - types: [completed] - -jobs: - merge-me: - name: Merge me! - runs-on: ubuntu-latest - if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' && github.actor == 'dependabot[bot]' - steps: - - uses: actions/checkout@v2 - - uses: ahmadnassri/action-workflow-run-wait@v1 - with: - timeout: 300000 - - name: 'Download artifact' - uses: actions/github-script@v3.1.0 - with: - script: | - var artifacts = await github.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: ${{github.event.workflow_run.id }}, - }); - var matchArtifact = artifacts.data.artifacts.filter((artifact) => { - return artifact.name == "pr" - })[0]; - var download = await github.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: matchArtifact.id, - archive_format: 'zip', - }); - var fs = require('fs'); - fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data)); - - run: unzip pr.zip - - name: Merge me! - uses: actions/github-script@v3 - with: - script: | - var fs = require('fs'); - var issue_number = Number(fs.readFileSync('./NR')); - - github.pulls.createReview({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - pull_number: issue_number, - event: 'APPROVE' - }) - github.pulls.merge({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - pull_number: issue_number, - merge_method: 'squash' - }) - github-token: ${{ secrets.AUTOMERGE }} \ No newline at end of file diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 1cd9446be..a94ace711 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -1,32 +1,88 @@ -name: Build Docs +# Build Latest Docs +# +# Description: +# Builds the latest docs and stores them in S3 to be served by our docs platform +# +# The workflow allows us to build to the main location (/lambda/java/) and to an alias +# (i.e. /lambda/java/preview/) if needed +# +# Triggers: +# - workflow_dispatch +# +# Inputs: +# alias – subdirectory to store the docs in for previews or in progress work on: - pull_request: - branches: - - master - paths: - - 'docs/**' - push: - branches: - - master - paths: - - 'docs/**' + workflow_dispatch: + inputs: + version: + description: "Version to build and publish docs (1.28.0, develop)" + required: true + type: string + +name: Build Latest Docs +run-name: Build Latest Docs - ${{ inputs.version }} jobs: docs: runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + environment: Docs steps: - - uses: actions/checkout@v1 - - name: Set up Python - uses: actions/setup-python@v1 + - name: Checkout Repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + with: + fetch-depth: 0 + - name: Build + run: | + mkdir -p dist + docker build -t squidfunk/mkdocs-material ./docs/ + docker run --rm -t -v ${PWD}:/docs squidfunk/mkdocs-material build + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 with: - python-version: "3.8" - - name: Capture branch and tag - id: branch_name + aws-region: us-east-1 + role-to-assume: ${{ secrets.AWS_DOCS_ROLE_ARN }} + - name: Deploy Docs (Version) + env: + VERSION: ${{ inputs.version }} + ALIAS: "latest" + run: | + aws s3 sync \ + site/ \ + s3://${{ secrets.AWS_DOCS_BUCKET }}/lambda-java/${{ env.VERSION }}/ + - name: Deploy Docs (Alias) + env: + VERSION: ${{ inputs.version }} + ALIAS: "latest" run: | - echo "SOURCE_BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV - echo "SOURCE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - - name: Build docs website + aws s3 sync \ + site/ \ + s3://${{ secrets.AWS_DOCS_BUCKET }}/lambda-java/${{ env.ALIAS }}/ + - name: Deploy Docs (Version JSON) + env: + VERSION: ${{ inputs.version }} + ALIAS: "latest" + # We originally used "mike" from PyPi to manage versions for us, but since we moved to S3, we can't use it to manage versions any more. + # Instead, we're using some shell script that manages the versions. + # + # Operations: + # 1. Download the versions.json file from S3 + # 2. Find any reference to the alias and delete it from the versions file + # 3. This is voodoo (don't use JQ): + # - we assign the input as $o and the new version/alias as $n, + # - we check if the version number exists in the file already (for republishing docs) + # - if it's an alias (stage/latest/*) or old version, we do nothing and output $o (original input) + # - if it's a new version number, we add it at position 0 in the array. + # 4. Once done, we'll upload it back to S3. run: | - echo "GIT_PYTHON_REFRESH=quiet" - make build-docs-website \ No newline at end of file + aws s3 cp \ + s3://${{ secrets.AWS_DOCS_BUCKET }}/lambda-java/versions.json \ + versions_old.json + jq 'del(.[].aliases[] | select(. == "${{ env.ALIAS }}"))' < versions_old.json > versions_proc.json + jq '. as $o | [{"title": "${{ env.VERSION }}", "version": "${{ env.VERSION }}", "aliases": ["${{ env.ALIAS }}"] }] as $n | $n | if .[0].title | test("[a-z]+") or any($o[].title == $n[0].title;.) then [($o | .[] | select(.title == $n[0].title).aliases += $n[0].aliases | . )] else $n + $o end' < versions_proc.json > versions.json + aws s3 cp \ + versions.json \ + s3://${{ secrets.AWS_DOCS_BUCKET }}/lambda-java/versions.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index e59564080..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Build - -on: - pull_request: - branches: - - master - paths: - - 'powertools-cloudformation/**' - - 'powertools-core/**' - - 'powertools-logging/**' - - 'powertools-sqs/**' - - 'powertools-tracing/**' - - 'powertools-validation/**' - - 'powertools-parameters/**' - - 'powertools-metrics/**' - - 'powertools-test-suite/**' - - 'pom.xml' - - '.github/workflows/**' - push: - branches: - - master - paths: - - 'powertools-cloudformation/**' - - 'powertools-core/**' - - 'powertools-logging/**' - - 'powertools-sqs/**' - - 'powertools-tracing/**' - - 'powertools-validation/**' - - 'powertools-parameters/**' - - 'powertools-metrics/**' - - 'powertools-test-suite/**' - - 'pom.xml' - - '.github/workflows/**' -jobs: - build: - runs-on: ubuntu-latest - strategy: - max-parallel: 4 - matrix: - # test against latest update of each major Java version, as well as specific updates of LTS versions: - java: [8, 8.0.192, 11.0.x, 11.0.3, 12, 13, 15, 16, 17 ] - name: Java ${{ matrix.java }} - env: - OS: ${{ matrix.os }} - JAVA: ${{ matrix.java-version }} - AWS_REGION: eu-west-1 - steps: - - uses: actions/checkout@v2 - - name: Setup java - uses: actions/setup-java@v2 - with: - distribution: 'zulu' - java-version: ${{ matrix.java }} - - name: Build with Maven - run: mvn -Pbuild-without-spotbugs -B package --file pom.xml - savepr: - runs-on: ubuntu-latest - name: Save PR number if running on PR by dependabot - if: github.actor == 'dependabot[bot]' - steps: - - name: Create Directory and save issue - run: | - mkdir -p ./pr - echo ${{ github.event.number }} - echo ${{ github.event.number }} > ./pr/NR - - uses: actions/upload-artifact@v2 - name: Updload artifact - with: - name: pr - path: pr/ diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml new file mode 100644 index 000000000..339d6fab8 --- /dev/null +++ b/.github/workflows/check-build.yml @@ -0,0 +1,147 @@ +# Check Build +# +# Description: +# Runs the build for every java version we support +# +# Triggers: +# - pull_request: when a PR is sent to us +# - push: when code is pushed to a specified branch +# +# Notes: +# Builds against Java 11, 17, and 21 which are the supported versions. + +on: + workflow_dispatch: + pull_request: + paths: + - 'powertools-batch/**' + - 'powertools-core/**' + - 'powertools-cloudformation/**' + - 'powertools-common/**' + - 'powertools-e2e-tests/**' + - 'powertools-idempotency/**' + - 'powertools-large-messages/**' + - 'powertools-logging/**' + - 'powertools-metrics/**' + - 'powertools-kafka/**' + - 'powertools-parameters/**' + - 'powertools-serialization/**' + - 'powertools-sqs/**' + - 'powertools-tracing/**' + - 'powertools-tracing/**' + - 'powertools-validation/**' + - 'examples/**' + - 'pom.xml' + - 'examples/pom.xml' + - '.github/workflows/**' + push: + branches: + - main + paths: + - 'powertools-batch/**' + - 'powertools-core/**' + - 'powertools-cloudformation/**' + - 'powertools-common/**' + - 'powertools-e2e-tests/**' + - 'powertools-idempotency/**' + - 'powertools-large-messages/**' + - 'powertools-logging/**' + - 'powertools-metrics/**' + - 'powertools-kafka/**' + - 'powertools-parameters/**' + - 'powertools-serialization/**' + - 'powertools-sqs/**' + - 'powertools-tracing/**' + - 'powertools-tracing/**' + - 'powertools-validation/**' + - 'pom.xml' + - 'examples/**' + - 'examples/pom.xml' + - '.github/workflows/**' + +name: Build +permissions: + contents: read +run-name: Build - ${{ github.event_name }} + +jobs: + java-build: + runs-on: ubuntu-latest + strategy: + matrix: + java: + - 11 + - 17 + - 21 + - 25 + steps: + - id: checkout + name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - name: Setup Java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e + with: + distribution: corretto + java-version: ${{ matrix.java }} + cache: maven + - id: build-maven + name: Build (Maven) + run: | + mvn -B -q install --file pom.xml + + graalvm-build: + runs-on: aws-powertools_ubuntu-latest_8-core + steps: + - id: checkout + name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1 + with: + files: | + powertools-*/** + pom.xml + - name: Setup GraalVM + uses: graalvm/setup-graalvm@790e28947b79a9c09c3391c0f18bf8d0f102ed69 # v1.4.4 + with: + java-version: "21" + distribution: "graalvm" + cache: maven + - id: graalvm-native-test + name: GraalVM Native Test + if: steps.changed-files.outputs.any_changed == 'true' + env: + CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} + run: | + # Build the entire project first to ensure test-jar dependencies are available + echo "::group::Building project dependencies" + mvn -B -q install -DskipTests + echo "::endgroup::" + + echo "Changes detected in powertools modules: $CHANGED_FILES" + + # Find modules with graalvm-native profile and run tests + find . -name "pom.xml" -path "./powertools-*" | while read module; do + if grep -q "<id>graalvm-native</id>" "$module"; then + module_dir=$(dirname "$module") + module_name=$(basename "$module_dir") + + # Check if this specific module or common dependencies changed + if echo "$CHANGED_FILES" | grep -q "$module_name/" || \ + echo " $CHANGED_FILES " | grep -q " pom.xml " || \ + echo "$CHANGED_FILES" | grep -q "powertools-common/"; then + echo "::group::Building $module_name with GraalVM" + echo "Changes detected in $module_name - running GraalVM tests" + echo "Regenerating GraalVM metadata for $module_dir" + mvn -B -q -f "$module" -Pgenerate-graalvm-files clean test + echo "Running GraalVM native tests for $module_dir" + mvn -B -q -f "$module" -Pgraalvm-native test + echo "::endgroup::" + else + echo "No changes detected in $module_name - skipping GraalVM tests" + fi + fi + done diff --git a/.github/workflows/check-e2e.yml b/.github/workflows/check-e2e.yml new file mode 100644 index 000000000..378d48a60 --- /dev/null +++ b/.github/workflows/check-e2e.yml @@ -0,0 +1,106 @@ +# Run E2E tests for a branch +# +# Description: +# Runs E2E tests for a specified branch +# +# Triggers: +# - push +# +# Secrets: +# - E2E.AWS_IAM_ROLE + +on: + workflow_dispatch: + + push: + branches: + - main + paths: # add other modules when there are under e2e tests + - 'powertools-batch/**' + - 'powertools-core/**' + - 'powertools-cloudformation/**' + - 'powertools-common/**' + - 'powertools-e2e-tests/**' + - 'powertools-idempotency/**' + - 'powertools-large-messages/**' + - 'powertools-logging/**' + - 'powertools-metrics/**' + - 'powertools-parameters/**' + - 'powertools-serialization/**' + - 'powertools-sqs/**' + - 'powertools-tracing/**' + - 'powertools-tracing/**' + - 'powertools-validation/**' + - 'pom.xml' + +name: E2E Tests +run-name: E2E Tests - ${{ github.event_name }} + +permissions: + contents: read + +jobs: + e2e: + name: End-to-end Tests (Java ${{ matrix.java }}) + runs-on: ubuntu-latest + permissions: + id-token: write + environment: E2E + strategy: + fail-fast: false + max-parallel: 4 + matrix: + java: + - 11 + - 17 + - 21 + - 25 + + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - name: Setup java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 + with: + distribution: 'corretto' + java-version: ${{ matrix.java }} + cache: maven + - name: Setup AWS credentials + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1 + with: + role-to-assume: ${{ secrets.AWS_IAM_ROLE }} + aws-region: us-east-1 + - name: Run e2e test with Maven + env: + JAVA_VERSION: ${{ matrix.java }} + run: mvn -DskipTests -ntp install --file pom.xml && mvn -Pe2e -B -ntp verify --file powertools-e2e-tests/pom.xml + + e2e-graal: + name: End-to-end GraalVM Tests (Java ${{ matrix.java }}) + runs-on: ubuntu-latest + permissions: + id-token: write + environment: E2E + strategy: + fail-fast: false + max-parallel: 1 + matrix: + java: + - 25 + + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - name: Setup java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 + with: + distribution: 'corretto' + java-version: ${{ matrix.java }} + cache: maven + - name: Setup AWS credentials + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1 + with: + role-to-assume: ${{ secrets.AWS_IAM_ROLE }} + aws-region: us-east-1 + - name: Run e2e-graal test with Maven + env: + JAVA_VERSION: ${{ matrix.java }} + run: mvn -DskipTests -ntp install --file pom.xml && mvn -Pe2e-graal -B -ntp verify --file powertools-e2e-tests/pom.xml diff --git a/.github/workflows/check-pmd.yml b/.github/workflows/check-pmd.yml new file mode 100644 index 000000000..7e7dce429 --- /dev/null +++ b/.github/workflows/check-pmd.yml @@ -0,0 +1,42 @@ +# Runs PMD for a Pull Request +# +# Description: +# Runs PMD (pmd.github.io) for a pull request and daily. +# This does not error on failure yet, our rules are too strong and would fail on every run +# +# Triggers: +# - pull_request +# - workflow_dispatch +# - cron: every day at 12:00PM + +on: + pull_request: + workflow_dispatch: + schedule: + - cron: '0 12 * * *' # Run daily at 12:00 UTC + +name: PMD +run-name: PMD - ${{ github.event_name }} + +permissions: + contents: read + +jobs: + pmd_analyse: + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + steps: + - name: Checkout Repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - name: Setup Java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 + with: + java-version: 21 + distribution: corretto + cache: maven + - uses: pmd/pmd-github-action@d9c1f3c5940cbf5923f1354e83fa858b4496ebaa # v2.0.0 + with: + rulesets: '.github/pmd-ruleset.xml' + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/check-spotbugs.yml b/.github/workflows/check-spotbugs.yml new file mode 100644 index 000000000..c5c8197f9 --- /dev/null +++ b/.github/workflows/check-spotbugs.yml @@ -0,0 +1,51 @@ +# Check for Spotbug errors +# +# Description: +# Runs Spotbugs for a pull request. +# This does not error on failure yet, our rules are too strong and would fail on every run +# +# Triggers: +# - pull_request +on: + pull_request: + branches: + - main + paths: + - 'powertools-batch/**' + - 'powertools-core/**' + - 'powertools-cloudformation/**' + - 'powertools-common/**' + - 'powertools-e2e-tests/**' + - 'powertools-idempotency/**' + - 'powertools-large-messages/**' + - 'powertools-logging/**' + - 'powertools-metrics/**' + - 'powertools-kafka/**' + - 'powertools-parameters/**' + - 'powertools-serialization/**' + - 'powertools-sqs/**' + - 'powertools-tracing/**' + - 'powertools-tracing/**' + - 'powertools-validation/**' + - 'powertools-test-suite/**' + - 'pom.xml' + - '.github/workflows/**' + +name: SpotBugs +run-name: SpotBugs + +permissions: + contents: read + +jobs: + codecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - name: Setup Java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 + with: + distribution: 'corretto' + java-version: 21 + - name: Build with Maven for spotbugs check to mark build as fail if voilations found + run: mvn -Pbuild-with-spotbugs -B install --file pom.xml -DskipTests -Dmaven.javadoc.skip=true -Dspotbugs.failOnError=true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 69aef263c..000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Docs - -on: - release: - types: - - published - workflow_dispatch: {} - -jobs: - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: "3.8" - - name: Capture branch and tag - id: branch_name - run: | - echo "SOURCE_BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV - echo "SOURCE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - - name: Build docs website - run: make build-docs-website - - name: Deploy all docs - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./dist diff --git a/.github/workflows/post_release.js b/.github/workflows/post_release.js deleted file mode 100644 index 648236421..000000000 --- a/.github/workflows/post_release.js +++ /dev/null @@ -1,112 +0,0 @@ -const STAGED_LABEL = "status/staged-next-release"; - -/** - * Fetch issues using GitHub REST API - * - * @param {object} gh_client - Pre-authenticated REST client (Octokit) - * @param {string} org - GitHub Organization - * @param {string} repository - GitHub repository - * @param {string} state - GitHub issue state (open, closed) - * @param {string} label - Comma-separated issue labels to fetch - * @return {Object[]} issues - Array of issues matching params - * @see {@link https://octokit.github.io/rest.js/v18#usage|Octokit client} - */ -const fetchIssues = async ({ - gh_client, - org, - repository, - state = "open", - label = STAGED_LABEL, - }) => { - - try { - const { data: issues } = await gh_client.rest.issues.listForRepo({ - owner: org, - repo: repository, - state: state, - labels: label, - }); - - return issues; - - } catch (error) { - console.error(error); - throw new Error("Failed to fetch issues") - } - -}; - -/** - * Notify new release and close staged GitHub issue - * - * @param {object} gh_client - Pre-authenticated REST client (Octokit) - * @param {string} owner - GitHub Organization - * @param {string} repository - GitHub repository - * @param {string} release_version - GitHub Release version - * @see {@link https://octokit.github.io/rest.js/v18#usage|Octokit client} - */ -const notifyRelease = async ({ - gh_client, - owner, - repository, - release_version, - }) => { - const release_url = `https://github.com/${owner}/${repository}/releases/tag/v${release_version}`; - - const issues = await fetchIssues({ - gh_client: gh_client, - org: owner, - repository: repository, - }); - - issues.forEach(async (issue) => { - console.info(`Updating issue number ${issue.number}`); - - const comment = `This is now released under [${release_version}](${release_url}) version!`; - try { - await gh_client.rest.issues.createComment({ - owner: owner, - repo: repository, - body: comment, - issue_number: issue.number, - }); - } catch (error) { - console.error(error); - throw new Error(`Failed to update issue ${issue.number} about ${release_version} release`) - } - - - // Close issue and remove staged label; keep existing ones - const labels = issue.labels - .filter((label) => label.name != STAGED_LABEL) - .map((label) => label.name); - - try { - await gh_client.rest.issues.update({ - repo: repository, - owner: owner, - issue_number: issue.number, - state: "closed", - labels: labels, - }); - } catch (error) { - console.error(error); - throw new Error("Failed to close issue") - } - - console.info(`Issue number ${issue.number} closed and updated`); - }); -}; - -// context: https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts -module.exports = async ({ github, context }) => { - const { RELEASE_TAG_VERSION } = process.env; - console.log(`Running post-release script for ${RELEASE_TAG_VERSION} version`); - - await notifyRelease({ - gh_client: github, - owner: context.repo.owner, - repository: context.repo.repo, - release_version: RELEASE_TAG_VERSION, - }); -}; \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 3f8d49a2d..000000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Publish package to the Maven Central Repository -on: - release: - types: - - published - workflow_dispatch: {} -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Maven Central Repository - uses: actions/setup-java@v2 - with: - distribution: 'zulu' - java-version: 8 - server-id: ossrh - server-username: MAVEN_USERNAME - server-password: MAVEN_PASSWORD - gpg-private-key: ${{ secrets.GPG_SIGNING_KEY }} # Value of the GPG private key to import - gpg-passphrase: GPG_PASSPHRASE # env variable for GPG private key passphrase - - name: Set release notes tag - run: | - RELEASE_TAG_VERSION=${{ github.event.release.tag_name }} - echo "RELEASE_TAG_VERSION=${RELEASE_TAG_VERSION:1}" >> $GITHUB_ENV - - name: Publish package - run: mvn -P sign,build-without-spotbugs clean deploy -DskipTests - env: - MAVEN_USERNAME: ${{ secrets.OSSRH_JIRA_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_JIRA_PASSWORD }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - - name: Close issues related to this release - uses: actions/github-script@v5 - with: - script: | - const post_release = require('.github/workflows/post_release.js') - await post_release({github, context, core}) diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index e627dfd3c..39d453ced 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -1,15 +1,27 @@ -name: Release Drafter +# Generates release notes +# +# Description: +# Generates release notes based on pull request history. This is based on the config +# stored in .github/release-drafter.yml +# +# Triggers: +# - push: main on: push: - # branches to consider in the event; optional, defaults to all - branches: - - master + branches: [ main ] + +name: Release Drafter +run-name: Release Drafter jobs: - update_release_draft: - runs-on: ubuntu-latest + update_release: + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write steps: - - uses: release-drafter/release-drafter@v5 + - name: Relase Drafter + uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release-prep.yml b/.github/workflows/release-prep.yml deleted file mode 100644 index a2f8c0817..000000000 --- a/.github/workflows/release-prep.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Prepare for maven central release -on: - workflow_dispatch: - inputs: - targetRelease: - description: 'Release number to upgrade to. For example X.X.X. Follow Semantic Versioning when deciding on next version.' - required: true - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Get current date - id: date - run: echo "::set-output name=date::$(date +'%Y-%m-%d')" - - name: Set current release version env variable - run: | - echo "CURRENT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV - - name: Find and Replace ${{ env.CURRENT_VERSION }} with ${{ github.event.inputs.targetRelease }} in mkdocs.yml - uses: jacobtomlinson/gha-find-replace@v2 - with: - find: 'version: ${{ env.CURRENT_VERSION }}' - replace: 'version: ${{ github.event.inputs.targetRelease }}' - regex: false - include: "mkdocs.yml" - - name: Find and Replace ${{ env.CURRENT_VERSION }} with ${{ github.event.inputs.targetRelease }} in main pom.xml - uses: jacobtomlinson/gha-find-replace@v2 - with: - find: ${{ env.CURRENT_VERSION }} - replace: ${{ github.event.inputs.targetRelease }} - regex: false - include: "pom.xml" - - name: Find and Replace ${{ env.CURRENT_VERSION }} with ${{ github.event.inputs.targetRelease }} in modules pom.xml - uses: jacobtomlinson/gha-find-replace@v2 - with: - find: ${{ env.CURRENT_VERSION }} - replace: ${{ github.event.inputs.targetRelease }} - regex: false - include: "**/*pom.xml" - - name: Find and Replace ${{ env.CURRENT_VERSION }} with ${{ github.event.inputs.targetRelease }} in build.gradle - uses: jacobtomlinson/gha-find-replace@v2 - with: - find: ${{ env.CURRENT_VERSION }} - replace: ${{ github.event.inputs.targetRelease }} - regex: false - include: "**/*build.gradle" - - name: Find and Replace ${{ env.CURRENT_VERSION }} with ${{ github.event.inputs.targetRelease }} in README.md - uses: jacobtomlinson/gha-find-replace@v2 - with: - find: ${{ env.CURRENT_VERSION }} - replace: ${{ github.event.inputs.targetRelease }} - regex: false - include: "README.md" - - name: Create changelog placeholder for ${{ github.event.inputs.targetRelease }} - uses: jacobtomlinson/gha-find-replace@v2 - with: - find: '## [Unreleased]' - replace: | - ## [Unreleased] - - ## [${{ github.event.inputs.targetRelease }}] - ${{ steps.date.outputs.date }} - - <PLEASE REMEBER TO UPDATE CHANGE LOG> - - regex: false - include: CHANGELOG.md - - name: Create Release Pull Request - uses: peter-evans/create-pull-request@v3 - with: - commit-message: chore:prep release ${{ github.event.inputs.targetRelease }} - token: ${{ secrets.RELEASE }} - signoff: false - branch: prep-release-${{ github.event.inputs.targetRelease }} - delete-branch: true - title: chore:Prep release ${{ github.event.inputs.targetRelease }} - body: | - This is automated release prep. Remember to update [CHANGELOG.md](https://github.com/awslabs/aws-lambda-powertools-java/blob/prep-release-${{ github.event.inputs.targetRelease }}/CHANGELOG.md) to capture changes in this release. Please review changes carefully before merging. - - * [ ] Updated CHANGELOG.md \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..630b91321 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,329 @@ +# Release +# +# Description: +# Creates a release for the project +# +# 1. Runs a setup job to set needed variables (build_matrix & version) +# 2. Versions to the project and stores as an artifact +# 3. Run quality checks +# 4. Build +# 5. Publish to Maven Central +# 6. Create PR +# 7. Publish docs +# +# Inputs: +# - version (string): SemVer of the new release (X.Y.Z) +# - snapshot (bool): If it's a snapshot release, this skips versioning assets like docs +# - skip_checks (bool): Don't run quality checks if it's an emergency release +# - skip_publish (bool): Don't publish to maven central +# - continue_on_error (bool): Don't fail the workflow if a quality check fails +# +# Triggers: +# - workflow_dispatch +# +# Secrets: +# - RELEASE.GPG_SIGNING_KEY +# - RELEASE.OSSRH_JIRA_USERNAME +# - RELEASE.OSSRH_JIRA_PASSWORD +# - RELEASE.GPG_PASSPHRASE +# - DOCS.AWS_DOCS_ROLE_ARN +# - DOCS.AWS_DOCS_BUCKET + +on: + workflow_dispatch: + inputs: + version: + type: string + description: Semver version to release + snapshot: + type: boolean + description: Create snapshot release + default: false + skip_checks: + type: boolean + description: Skip quality checks + default: false + skip_publish: + type: boolean + description: Skip publish to Maven Central + default: false + continue_on_error: + type: boolean + description: Continue to build if there's an error in quality checks + default: false + +name: Release +run-name: Release – ${{ inputs.version }} + +permissions: + contents: read + +env: + RELEASE_COMMIT: ${{ github.sha }} + RELEASE_TAG_VERSION: ${{ inputs.version }} + +jobs: + setup: + runs-on: ubuntu-latest + outputs: + version: ${{ format('{0}{1}', steps.version_release.outputs.version, steps.version_snapshot.outputs.version) }} + build_matrix: ${{ format('{0}{1}', steps.build_matrix_v1.outputs.build_matrix, steps.build_matrix_v2.outputs.build_matrix) }} + steps: + - id: version_snapshot + if: ${{ inputs.snapshot }} + name: Version + run: | + echo version="$(grep -q "SNAPSHOT" <<< "${{ inputs.version }}" && echo "${{ inputs.version }}" || echo "${{ inputs.version }}-SNAPSHOT")" >> "$GITHUB_OUTPUT" + - id: version_release + if: ${{ !inputs.snapshot }} + name: Version + run: | + echo version="${{ inputs.version }}" >> "$GITHUB_OUTPUT" + - id: base + name: Base + run: | + echo build_version=$(test ${{ github.ref_name }} == "main" && echo "v2" || echo "v1") >> $GITHUB_OUTPUT + - id: build_matrix_v1 + name: Build matrix (v1) + if: ${{ steps.base.outputs.build_version == 'v1' }} + run: | + echo build_matrix='["8", "11", "17", "21"]' >> "$GITHUB_OUTPUT" + - id: build_matrix_v2 + name: Build matrix (v2) + if: ${{ steps.base.outputs.build_version == 'v2' }} + run: | + echo build_matrix='["11", "17", "21"]'>> "$GITHUB_OUTPUT" + + version_seal: + runs-on: ubuntu-latest + needs: + - setup + outputs: + source_hash: ${{ steps.upload_source.outputs.artifact-digest }} + steps: + - id: checkout + name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - id: version + name: version + uses: ./.github/actions/version + with: + new_version: ${{ needs.setup.outputs.version }} + snapshot: ${{ inputs.snapshot}} + - id: upload_source + name: Upload artifacts + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + if-no-files-found: error + name: source + path: | + * + !.git/* + include-hidden-files: true + retention-days: 1 + + quality: + runs-on: aws-powertools_ubuntu-latest_8-core + needs: + - version_seal + if: ${{ inputs.skip_checks == false }} + permissions: + contents: write + id-token: write + steps: + - id: download_source + name: Download artifacts + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v4.6.1 + with: + name: source + - name: Setup Java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e + with: + distribution: corretto + java-version: 21 + cache: maven + # non-exhuastive, but gives a fair indication if the final build will succeed, tests will run when we build later + - name: Run unit tests + run: mvn -B test --file pom.xml + continue-on-error: ${{ inputs.continue_on_error }} + - name: Run Spotbugs + run: mvn -Pbuild-with-spotbugs -B install --file pom.xml -DskipTests -Dmaven.javadoc.skip=true -Dspotbugs.failOnError=true + continue-on-error: ${{ inputs.continue_on_error }} + - uses: pmd/pmd-github-action@d9c1f3c5940cbf5923f1354e83fa858b4496ebaa # v2.0.0 + with: + rulesets: '.github/pmd-ruleset.xml' + token: ${{ secrets.GITHUB_TOKEN }} + uploadSarifReport: false + + build: + runs-on: aws-powertools_ubuntu-latest_8-core + needs: + - setup + - quality + - version_seal + if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} + strategy: + matrix: + java: ${{ fromJson(needs.setup.outputs.build_matrix) }} + steps: + - id: download_source + name: Download artifacts + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v4.6.1 + with: + name: source + - name: Setup Java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e + with: + distribution: corretto + java-version: ${{ matrix.java }} + cache: maven + - id: build-maven + name: Build (Maven) + run: | + mvn -B install --file pom.xml + + publish: + runs-on: aws-powertools_ubuntu-latest_8-core + if: ${{ github.repository == 'aws-powertools/powertools-lambda-java' && inputs.skip_publish == false && always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} + needs: + - build + environment: Release + steps: + - id: download_source + name: Download artifacts + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v4.6.1 + with: + name: source + - name: Setup Java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e + with: + distribution: corretto + java-version: 21 + cache: maven + gpg-private-key: ${{ secrets.GPG_SIGNING_KEY }} + gpg-passphrase: GPG_PASSPHRASE + server-id: central + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + - name: Publish package + run: mvn -Prelease clean deploy -DskipTests + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + + create_pr: + runs-on: ubuntu-latest + if: ${{ inputs.snapshot == false && always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} + needs: + - build + - publish + permissions: + pull-requests: write + contents: write + steps: + - id: checkout + name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + ref: ${{ env.RELEASE_COMMIT }} + - id: download_source + name: Download artifacts + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v4.6.1 + with: + name: source + - id: setup-git + name: Git client setup and refresh tip + run: | + git config user.name "Powertools for AWS Lambda (Java) Bot" + git config user.email "151832416+aws-powertools-bot@users.noreply.github.com" + git config pull.rebase true + git config remote.origin.url >&- + - id: tag + name: Create tag + run: | + git tag -a v${{ inputs.version }} -m "Release v${{ inputs.version }}" + git push origin v${{ inputs.version }} + - id: branch + name: Create branch and update change log + run: | + git checkout -b ci-${{ github.run_id }} + docker run -v "${PWD}":/workdir quay.io/git-chglog/git-chglog@sha256:c791b1e8264387690cce4ce32e18b4f59ca3ffd8d55cb4093dc6de74529493f4 > CHANGELOG.md + git commit -am "chore(ci): bump version to ${{ inputs.version }}" + git push origin ci-${{ github.run_id }} + - id: create_pr + name: Create PR + run: | + gh pr create \ + --title "chore(ci): bump version to ${{ inputs.version }}" \ + --body "This is an automated PR created from the following workflow: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + docs: + runs-on: ubuntu-latest + if: ${{ inputs.snapshot == false }} + needs: + - create_pr + permissions: + contents: read + id-token: write + environment: Docs + steps: + - id: checkout + name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + # Checkout PR branch to make sure we build the version-bumped docs + ref: ci-${{ github.run_id }} + - name: Build + run: | + mkdir -p dist + docker build -t squidfunk/mkdocs-material ./docs/ + docker run --rm -t -v ${PWD}:/docs squidfunk/mkdocs-material build + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 + with: + aws-region: us-east-1 + role-to-assume: ${{ secrets.AWS_DOCS_ROLE_ARN }} + - name: Deploy Docs (Version) + env: + VERSION: ${{ inputs.version }} + ALIAS: 'latest' + run: | + aws s3 sync \ + site/ \ + s3://${{ secrets.AWS_DOCS_BUCKET }}/lambda-java/${{ env.VERSION }}/ + - name: Deploy Docs (Alias) + env: + VERSION: ${{ inputs.version }} + ALIAS: 'latest' + run: | + aws s3 sync \ + site/ \ + s3://${{ secrets.AWS_DOCS_BUCKET }}/lambda-java/${{ env.ALIAS }}/ + - name: Deploy Docs (Version JSON) + env: + VERSION: ${{ inputs.version }} + ALIAS: 'latest' + # We originally used "mike" from PyPi to manage versions for us, but since we moved to S3, we can't use it to manage versions any more. + # Instead, we're using some shell script that manages the versions. + # + # Operations: + # 1. Download the versions.json file from S3 + # 2. Find any reference to the alias and delete it from the versions file + # 3. This is voodoo (don't use JQ): + # - we assign the input as $o and the new version/alias as $n, + # - we check if the version number exists in the file already (for republishing docs) + # - if it's an alias (stage/latest/*) or old version, we do nothing and output $o (original input) + # - if it's a new version number, we add it at position 0 in the array. + # 4. Once done, we'll upload it back to S3. + run: | + aws s3 cp \ + s3://${{ secrets.AWS_DOCS_BUCKET }}/lambda-java/versions.json \ + versions_old.json + jq 'del(.[].aliases[] | select(. == "${{ env.ALIAS }}"))' < versions_old.json > versions_proc.json + jq '. as $o | [{"title": "${{ env.VERSION }}", "version": "${{ env.VERSION }}", "aliases": ["${{ env.ALIAS }}"] }] as $n | $n | if .[0].title | test("[a-z]+") or any($o[].title == $n[0].title;.) then [($o | .[] | select(.title == $n[0].title).aliases += $n[0].aliases | . )] else $n + $o end' < versions_proc.json > versions.json + aws s3 cp \ + versions.json \ + s3://${{ secrets.AWS_DOCS_BUCKET }}/lambda-java/versions.json diff --git a/.github/workflows/security-dependencies-check.yml b/.github/workflows/security-dependencies-check.yml new file mode 100644 index 000000000..6729fd304 --- /dev/null +++ b/.github/workflows/security-dependencies-check.yml @@ -0,0 +1,31 @@ +# Dependency checks +# +# Description: +# Verifies that dependencies are compatible with our project +# by checking licenses and their security posture +# +# Triggers: +# - pull_request + +on: + pull_request: + +name: Verify Dependencies +run-name: Verify Dependencies – ${{ github.event_name }} + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Checkout Repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - name: Verify Contents + uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 + with: + config-file: './.github/dependency-review-config.yml' diff --git a/.github/workflows/security-scorecard.yml b/.github/workflows/security-scorecard.yml new file mode 100644 index 000000000..b91e78c69 --- /dev/null +++ b/.github/workflows/security-scorecard.yml @@ -0,0 +1,57 @@ +# Runs OSSF +# +# Description: +# Runs OpenSSF Scorecard scan on the project +# +# Triggers: +# - branch_protection_rule +# - cron: 09:00AM +# - push +# - workflow_dispatch +# +# Secrets: +# - Security.SCORECARD_TOKEN + +on: + branch_protection_rule: + schedule: + - cron: "0 9 * * *" + push: + branches: [main] + workflow_dispatch: {} + +name: OpenSSF Scorecard +run-name: OpenSSF Scorecard + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + environment: Security + permissions: + security-events: write + id-token: write + steps: + - name: Checkout Repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + - name: Run Analysis + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + repo_token: ${{ secrets.SCORECARD_TOKEN }} + - name: Upload Results + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + - name: Upload to Code-Scanning + uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v3.29.5 + with: + sarif_file: results.sarif diff --git a/.github/workflows/spotbugs.yml b/.github/workflows/spotbugs.yml deleted file mode 100644 index 8976c5042..000000000 --- a/.github/workflows/spotbugs.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: SpotBugs - -on: - pull_request: - branches: - - master - paths: - - 'powertools-cloudformation/**' - - 'powertools-core/**' - - 'powertools-logging/**' - - 'powertools-sqs/**' - - 'powertools-tracing/**' - - 'powertools-validation/**' - - 'powertools-parameters/**' - - 'powertools-metrics/**' - - 'powertools-test-suite/**' - - 'pom.xml' - - '.github/workflows/**' -jobs: - codecheck: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Setup java JDK 1.8 - uses: actions/setup-java@v2 - with: - distribution: 'zulu' - java-version: 8 - # https://github.com/jwgmeligmeyling/spotbugs-github-action/issues/6 - # https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/ - # Avoid complexity of git action with publishing report. Just build with spotbugs profile. -# - name: Build with Maven for spotbugs check to gather reports -# run: mvn -Pbuild-with-spotbugs -B install --file pom.xml -DskipTests -Dmaven.javadoc.skip=true -Dspotbugs.failOnError=false -# - uses: jwgmeligmeyling/spotbugs-github-action@master -# with: -# path: '**/spotbugsXml.xml' -# # Can be simplified post this issue is fixed https://github.com/jwgmeligmeyling/spotbugs-github-action/issues/9 - - name: Build with Maven for spotbugs check to mark build as fail if voilations found - run: mvn -Pbuild-with-spotbugs -B install --file pom.xml -DskipTests -Dmaven.javadoc.skip=true -Dspotbugs.failOnError=true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 20f4c17fa..eb2ea4f18 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ hs_err_pid* # Maven build target/ +native-libs/ ###################### # IntelliJ @@ -27,7 +28,6 @@ target/ classes/ out/ - ###################### # Eclipse ###################### @@ -81,6 +81,8 @@ Desktop.ini ###################### /bin/ /deploy/ +/dist/ +/site/ ###################### # Logs @@ -96,6 +98,7 @@ Desktop.ini docs/node_modules docs/.cache +.cache docs/public /example/.aws-sam/ /example/HelloWorldFunction/.aws-sam/ @@ -104,3 +107,14 @@ example/HelloWorldFunction/.gradle example/HelloWorldFunction/build /example/.gradle/ /example/.java-version +.gradle +build/ +.terraform* +terraform.tfstate* + +# LLMs +.kiro/ +.claude/ +.amazonq/ +.github/instructions + diff --git a/.mvn/README b/.mvn/README new file mode 100644 index 000000000..a851f5e55 --- /dev/null +++ b/.mvn/README @@ -0,0 +1,2 @@ +This is here purely so that we can get the root directory using maven.multiModuleProjectDirectory + diff --git a/.sonarcloud.properties b/.sonarcloud.properties new file mode 100644 index 000000000..d9a4f79cf --- /dev/null +++ b/.sonarcloud.properties @@ -0,0 +1,17 @@ +# +# Copyright 2023 Amazon.com, Inc. or its affiliates. +# 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 +# http://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. +# +# +sonar.exclusions=examples/**/*,powertools-e2e-tests/handlers/**/* + +# Ignore code duplicates in the examples +sonar.cpd.exclusions=examples/**/*,powertools-e2e-tests/**/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a369183e..a07df5d3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,115 +1,1115 @@ -# Changelog +<!-- changelog is partially generated, so it doesn't follow headings and required structure, so we disable it. --> +<!-- markdownlint-disable --> -All notable changes to this project will be documented in this file. +<a name="unreleased"></a> +# Unreleased -This project follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format for changes and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Documentation +* **logger:** Fix logging environment variables names in documentation ([#2161](https://github.com/aws-powertools/powertools-lambda-java/issues/2161)) +## Features -## [Unreleased] +* add CRaC priming support to powertools-kafka module ([#2145](https://github.com/aws-powertools/powertools-lambda-java/issues/2145)) +* **metrics:** introduce Metrics.flushMetrics ([#2154](https://github.com/aws-powertools/powertools-lambda-java/issues/2154)) + +## Maintenance + +* bump aws.sdk.version from 2.35.6 to 2.35.7 ([#2190](https://github.com/aws-powertools/powertools-lambda-java/issues/2190)) +* bump com.networknt:json-schema-validator from 1.5.8 to 1.5.9 ([#2189](https://github.com/aws-powertools/powertools-lambda-java/issues/2189)) +* bump sam/build-java21 ([#2195](https://github.com/aws-powertools/powertools-lambda-java/issues/2195)) +* bump squidfunk/mkdocs-material in /docs ([#2194](https://github.com/aws-powertools/powertools-lambda-java/issues/2194)) +* bump com.github.spotbugs:spotbugs-maven-plugin ([#2192](https://github.com/aws-powertools/powertools-lambda-java/issues/2192)) +* bump software.amazon.awscdk:aws-cdk-lib from 2.214.0 to 2.220.0 ([#2191](https://github.com/aws-powertools/powertools-lambda-java/issues/2191)) +* bump io.github.ascopes:protobuf-maven-plugin ([#2193](https://github.com/aws-powertools/powertools-lambda-java/issues/2193)) +* bump aws.xray.recorder.version from 2.19.0 to 2.20.0 ([#2185](https://github.com/aws-powertools/powertools-lambda-java/issues/2185)) +* bump aws.sdk.version from 2.33.2 to 2.33.5 ([#2132](https://github.com/aws-powertools/powertools-lambda-java/issues/2132)) +* bump org.apache.maven.plugins:maven-javadoc-plugin ([#2186](https://github.com/aws-powertools/powertools-lambda-java/issues/2186)) +* bump org.assertj:assertj-core from 3.27.4 to 3.27.6 ([#2184](https://github.com/aws-powertools/powertools-lambda-java/issues/2184)) +* bump aws.sdk.version from 2.34.9 to 2.35.6 ([#2183](https://github.com/aws-powertools/powertools-lambda-java/issues/2183)) +* bump actions/dependency-review-action from 4.8.0 to 4.8.1 ([#2180](https://github.com/aws-powertools/powertools-lambda-java/issues/2180)) +* bump github/codeql-action from 3.30.5 to 4.30.8 ([#2179](https://github.com/aws-powertools/powertools-lambda-java/issues/2179)) +* bump aws-actions/configure-aws-credentials from 5.0.0 to 5.1.0 ([#2177](https://github.com/aws-powertools/powertools-lambda-java/issues/2177)) +* bump com.google.protobuf:protobuf-java from 4.32.0 to 4.32.1 ([#2175](https://github.com/aws-powertools/powertools-lambda-java/issues/2175)) +* bump aws.sdk.version from 2.34.5 to 2.34.9 ([#2174](https://github.com/aws-powertools/powertools-lambda-java/issues/2174)) +* bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0 ([#2172](https://github.com/aws-powertools/powertools-lambda-java/issues/2172)) +* bump org.apache.maven.plugins:maven-artifact-plugin ([#2171](https://github.com/aws-powertools/powertools-lambda-java/issues/2171)) +* Add User-Agent execution interceptors ([#2166](https://github.com/aws-powertools/powertools-lambda-java/issues/2166)) +* bump org.apache.kafka:kafka-clients from 4.0.0 to 4.1.0 ([#2134](https://github.com/aws-powertools/powertools-lambda-java/issues/2134)) +* bump graalvm/setup-graalvm from 1.3.6 to 1.4.1 ([#2168](https://github.com/aws-powertools/powertools-lambda-java/issues/2168)) +* bump ossf/scorecard-action from 2.4.2 to 2.4.3 ([#2165](https://github.com/aws-powertools/powertools-lambda-java/issues/2165)) +* bump squidfunk/mkdocs-material in /docs ([#2164](https://github.com/aws-powertools/powertools-lambda-java/issues/2164)) +* bump log4j.version from 2.25.1 to 2.25.2 ([#2160](https://github.com/aws-powertools/powertools-lambda-java/issues/2160)) +* bump org.apache.maven.plugins:maven-failsafe-plugin ([#2159](https://github.com/aws-powertools/powertools-lambda-java/issues/2159)) +* bump actions/dependency-review-action from 4.7.3 to 4.8.0 ([#2158](https://github.com/aws-powertools/powertools-lambda-java/issues/2158)) +* bump github/codeql-action from 3.30.1 to 3.30.5 ([#2157](https://github.com/aws-powertools/powertools-lambda-java/issues/2157)) +* bump io.github.ascopes:protobuf-maven-plugin from 3.9.0 to 3.10.0 ([#2155](https://github.com/aws-powertools/powertools-lambda-java/issues/2155)) +* bump com.amazonaws:aws-lambda-java-runtime-interface-client ([#2149](https://github.com/aws-powertools/powertools-lambda-java/issues/2149)) +* bump aws.sdk.version from 2.33.2 to 2.34.5 ([#2156](https://github.com/aws-powertools/powertools-lambda-java/issues/2156)) +* bump org.codehaus.mojo:versions-maven-plugin ([#2148](https://github.com/aws-powertools/powertools-lambda-java/issues/2148)) +* bump squidfunk/mkdocs-material in /docs ([#2144](https://github.com/aws-powertools/powertools-lambda-java/issues/2144)) +* bump tj-actions/changed-files from 46.0.5 to 47.0.0 ([#2143](https://github.com/aws-powertools/powertools-lambda-java/issues/2143)) +* bump sam/build-java21 ([#2141](https://github.com/aws-powertools/powertools-lambda-java/issues/2141)) +* bump com.amazonaws:aws-lambda-java-core from 1.3.0 to 1.4.0 ([#2135](https://github.com/aws-powertools/powertools-lambda-java/issues/2135)) +* **deps:** Use mockito 5.20.0 ([#2181](https://github.com/aws-powertools/powertools-lambda-java/issues/2181)) +* **docs:** Add AWS docs meta tags ([#2170](https://github.com/aws-powertools/powertools-lambda-java/issues/2170)) + + +<a name="v2.4.0"></a> +## [v2.4.0] - 2025-09-09 +## Bug Fixes + +* **ci:** Update branch protection output ([#2053](https://github.com/aws-powertools/powertools-lambda-java/issues/2053)) + +## Documentation + +* Add AWS copyright footer. ([#2119](https://github.com/aws-powertools/powertools-lambda-java/issues/2119)) +* Update docs introduction +* Rename wrong POWERTOOLS_DISABLE_METRICS to correct POWERTOOLS_METRICS_DISABLED environment variable. ([#2043](https://github.com/aws-powertools/powertools-lambda-java/issues/2043)) +* update readme ([#2045](https://github.com/aws-powertools/powertools-lambda-java/issues/2045)) + +## Features + +* Support CRaC priming of powertools validation ([#2081](https://github.com/aws-powertools/powertools-lambda-java/issues/2081)) +* **graalvm:** GraalVM support for powertools-cloudformation ([#2090](https://github.com/aws-powertools/powertools-lambda-java/issues/2090)) +* **graalvm:** GraalVM support for Idempotency utility ([#2080](https://github.com/aws-powertools/powertools-lambda-java/issues/2080)) +* **logging:** Log buffering support for Logj42 and Logback ([#2103](https://github.com/aws-powertools/powertools-lambda-java/issues/2103)) + +## Maintenance + +* bump dev.aspectj:aspectj-maven-plugin from 1.13.1 to 1.14.1 ([#2099](https://github.com/aws-powertools/powertools-lambda-java/issues/2099)) +* bump dev.aspectj:aspectj-maven-plugin from 1.14 to 1.14.1 ([#2037](https://github.com/aws-powertools/powertools-lambda-java/issues/2037)) +* bump github/codeql-action from 3.29.8 to 3.29.9 ([#2038](https://github.com/aws-powertools/powertools-lambda-java/issues/2038)) +* bump org.apache.maven.plugins:maven-deploy-plugin ([#2040](https://github.com/aws-powertools/powertools-lambda-java/issues/2040)) +* bump org.yaml:snakeyaml from 2.4 to 2.5 ([#2111](https://github.com/aws-powertools/powertools-lambda-java/issues/2111)) +* bump io.github.ascopes:protobuf-maven-plugin from 3.8.1 to 3.9.0 ([#2114](https://github.com/aws-powertools/powertools-lambda-java/issues/2114)) +* bump aws.sdk.version from 2.32.31 to 2.33.1 ([#2115](https://github.com/aws-powertools/powertools-lambda-java/issues/2115)) +* bump graalvm/setup-graalvm from 1.3.5 to 1.3.6 ([#2116](https://github.com/aws-powertools/powertools-lambda-java/issues/2116)) +* bump software.amazon.awscdk:aws-cdk-lib from 2.213.0 to 2.214.0 ([#2117](https://github.com/aws-powertools/powertools-lambda-java/issues/2117)) +* bump aws-actions/configure-aws-credentials from 4.3.1 to 5.0.0 ([#2120](https://github.com/aws-powertools/powertools-lambda-java/issues/2120)) +* bump com.github.spotbugs:spotbugs-maven-plugin ([#2125](https://github.com/aws-powertools/powertools-lambda-java/issues/2125)) +* bump github/codeql-action from 3.30.0 to 3.30.1 ([#2126](https://github.com/aws-powertools/powertools-lambda-java/issues/2126)) +* bump squidfunk/mkdocs-material in /docs ([#2127](https://github.com/aws-powertools/powertools-lambda-java/issues/2127)) +* bump jackson.version from 2.19.2 to 2.20 ([#2097](https://github.com/aws-powertools/powertools-lambda-java/issues/2097)) +* bump aws.sdk.version from 2.32.18 to 2.32.21 ([#2041](https://github.com/aws-powertools/powertools-lambda-java/issues/2041)) +* bump aws.sdk.version from 2.32.26 to 2.32.31 ([#2098](https://github.com/aws-powertools/powertools-lambda-java/issues/2098)) +* bump github/codeql-action from 3.29.11 to 3.30.0 ([#2106](https://github.com/aws-powertools/powertools-lambda-java/issues/2106)) +* bump software.amazon.awscdk:aws-cdk-lib from 2.212.0 to 2.213.0 ([#2100](https://github.com/aws-powertools/powertools-lambda-java/issues/2100)) +* bump org.apache.maven.plugins:maven-compiler-plugin ([#2094](https://github.com/aws-powertools/powertools-lambda-java/issues/2094)) +* bump actions/checkout from 4.2.2 to 5.0.0 ([#2087](https://github.com/aws-powertools/powertools-lambda-java/issues/2087)) +* bump org.apache.logging.log4j:log4j-transform-maven-shade-plugin-extensions ([#2088](https://github.com/aws-powertools/powertools-lambda-java/issues/2088)) +* bump io.github.ascopes:protobuf-maven-plugin from 3.8.0 to 3.8.1 ([#2085](https://github.com/aws-powertools/powertools-lambda-java/issues/2085)) +* bump com.github.spotbugs:spotbugs-maven-plugin ([#2084](https://github.com/aws-powertools/powertools-lambda-java/issues/2084)) +* bump sam/build-java21 ([#2083](https://github.com/aws-powertools/powertools-lambda-java/issues/2083)) +* bump aws.sdk.version from 2.32.30 to 2.32.31 ([#2093](https://github.com/aws-powertools/powertools-lambda-java/issues/2093)) +* bump actions/dependency-review-action from 4.7.2 to 4.7.3 ([#2092](https://github.com/aws-powertools/powertools-lambda-java/issues/2092)) +* bump aws.sdk.version from 2.32.28 to 2.32.30 ([#2089](https://github.com/aws-powertools/powertools-lambda-java/issues/2089)) +* bump software.amazon.awscdk:aws-cdk-lib from 2.210.0 to 2.211.0 ([#2042](https://github.com/aws-powertools/powertools-lambda-java/issues/2042)) +* bump aws.sdk.version from 2.32.21 to 2.32.22 ([#2046](https://github.com/aws-powertools/powertools-lambda-java/issues/2046)) +* bump com.google.protobuf:protobuf-java from 4.31.1 to 4.32.0 ([#2050](https://github.com/aws-powertools/powertools-lambda-java/issues/2050)) +* bump aws.sdk.version from 2.32.23 to 2.32.25 ([#2054](https://github.com/aws-powertools/powertools-lambda-java/issues/2054)) +* bump squidfunk/mkdocs-material in /docs ([#2074](https://github.com/aws-powertools/powertools-lambda-java/issues/2074)) +* bump github/codeql-action from 3.29.10 to 3.29.11 ([#2073](https://github.com/aws-powertools/powertools-lambda-java/issues/2073)) +* bump log4j.version from 2.25.1 to 2.25.1 ([#2072](https://github.com/aws-powertools/powertools-lambda-java/issues/2072)) +* bump org.apache.maven.plugins:maven-shade-plugin ([#2071](https://github.com/aws-powertools/powertools-lambda-java/issues/2071)) +* bump org.graalvm.buildtools:native-maven-plugin ([#2070](https://github.com/aws-powertools/powertools-lambda-java/issues/2070)) +* bump com.amazonaws:aws-lambda-java-runtime-interface-client ([#2069](https://github.com/aws-powertools/powertools-lambda-java/issues/2069)) +* bump aws.sdk.version from 2.32.2 to 2.32.28 ([#2068](https://github.com/aws-powertools/powertools-lambda-java/issues/2068)) +* bump actions/setup-java from 4.7.1 to 5.0.0 ([#2067](https://github.com/aws-powertools/powertools-lambda-java/issues/2067)) +* bump software.amazon.awscdk:aws-cdk-lib from 2.211.0 to 2.212.0 ([#2066](https://github.com/aws-powertools/powertools-lambda-java/issues/2066)) +* bump org.apache.maven.plugins:maven-javadoc-plugin ([#2065](https://github.com/aws-powertools/powertools-lambda-java/issues/2065)) +* bump aws.sdk.version from 2.32.25 to 2.32.27 ([#2064](https://github.com/aws-powertools/powertools-lambda-java/issues/2064)) +* bump aws.sdk.version from 2.32.22 to 2.32.23 ([#2048](https://github.com/aws-powertools/powertools-lambda-java/issues/2048)) +* bump squidfunk/mkdocs-material in /docs ([#2058](https://github.com/aws-powertools/powertools-lambda-java/issues/2058)) +* bump org.apache.maven.plugins:maven-javadoc-plugin ([#2059](https://github.com/aws-powertools/powertools-lambda-java/issues/2059)) +* bump io.github.ascopes:protobuf-maven-plugin from 3.7.0 to 3.8.0 ([#2057](https://github.com/aws-powertools/powertools-lambda-java/issues/2057)) +* bump actions/checkout from 4.2.2 to 5.0.0 ([#2036](https://github.com/aws-powertools/powertools-lambda-java/issues/2036)) +* bump actions/dependency-review-action from 4.7.1 to 4.7.2 ([#2055](https://github.com/aws-powertools/powertools-lambda-java/issues/2055)) +* bump sam/build-java21 ([#2075](https://github.com/aws-powertools/powertools-lambda-java/issues/2075)) +* bump aws.sdk.version from 2.32.19 to 2.32.26 ([#2060](https://github.com/aws-powertools/powertools-lambda-java/issues/2060)) +* bump github/codeql-action from 3.29.9 to 3.29.10 ([#2056](https://github.com/aws-powertools/powertools-lambda-java/issues/2056)) +* **ci:** Add powertools-e2e-tests/handlers as module to capture it in GitHub actions version upgrades. ([#2063](https://github.com/aws-powertools/powertools-lambda-java/issues/2063)) +* **ci:** Fix bug where docs were released with old version during release workflow. ([#2076](https://github.com/aws-powertools/powertools-lambda-java/issues/2076)) +* **ci:** Run unit tests for GraalVM as well during build. ([#2047](https://github.com/aws-powertools/powertools-lambda-java/issues/2047)) +* **ci:** Remove non-PR triggers for verify dependencies workflow. ([#2044](https://github.com/aws-powertools/powertools-lambda-java/issues/2044)) +* **ci:** Fix circular dependency in dynamodb-local and maven packaging phases. ([#2129](https://github.com/aws-powertools/powertools-lambda-java/issues/2129)) +* **ci:** Do not use Mockito SNAPSHOT version for release. ([#2137](https://github.com/aws-powertools/powertools-lambda-java/issues/2137)) +* **ci:** Set mockito SNAPSHOT version only for Graal profiles. ([#2138](https://github.com/aws-powertools/powertools-lambda-java/issues/2138)) +* **gitignore:** add .kiro, .claude, .amazonq to prevent deletion ([#2078](https://github.com/aws-powertools/powertools-lambda-java/issues/2078)) + + +<a name="v2.3.0"></a> +## [v2.3.0] - 2025-08-12 +## Documentation + +* **examples:** Add Bazel example for core utilities ([#2022](https://github.com/aws-powertools/powertools-lambda-java/issues/2022)) +* **examples:** Add Logging and Tracing to idempotency example with correct configuration. ([#1993](https://github.com/aws-powertools/powertools-lambda-java/issues/1993)) +* **examples:** Enable end to end tracing for SQS batch example. ([#1995](https://github.com/aws-powertools/powertools-lambda-java/issues/1995)) + +## Features + +* Support CRaC priming of powertools metrics and idempotency-dynamodb ([#1861](https://github.com/aws-powertools/powertools-lambda-java/issues/1861)) + +## Maintenance + +* bump github/codeql-action from 3.29.4 to 3.29.5 ([#1992](https://github.com/aws-powertools/powertools-lambda-java/issues/1992)) +* bump org.assertj:assertj-core from 3.27.3 to 3.27.4 ([#2031](https://github.com/aws-powertools/powertools-lambda-java/issues/2031)) +* bump software.amazon.awscdk:aws-cdk-lib from 2.208.0 to 2.210.0 ([#2030](https://github.com/aws-powertools/powertools-lambda-java/issues/2030)) +* bump aws.sdk.version from 2.32.18 to 2.32.19 ([#2029](https://github.com/aws-powertools/powertools-lambda-java/issues/2029)) +* bump co.elastic.logging:logback-ecs-encoder from 1.6.0 to 1.7.0 ([#2028](https://github.com/aws-powertools/powertools-lambda-java/issues/2028)) +* bump com.github.spotbugs:spotbugs-maven-plugin from 4.8.4.0 to 4.9.3.2 ([#2010](https://github.com/aws-powertools/powertools-lambda-java/issues/2010)) +* bump com.amazonaws:aws-lambda-java-runtime-interface-client ([#2026](https://github.com/aws-powertools/powertools-lambda-java/issues/2026)) +* bump github/codeql-action from 3.29.7 to 3.29.8 ([#2027](https://github.com/aws-powertools/powertools-lambda-java/issues/2027)) +* bump org.crac:crac from 1.4.0 to 1.5.0 ([#2025](https://github.com/aws-powertools/powertools-lambda-java/issues/2025)) +* bump aws.sdk.version from 2.32.6 to 2.32.18 ([#2024](https://github.com/aws-powertools/powertools-lambda-java/issues/2024)) +* bump org.junit.jupiter:junit-jupiter from 5.11.1 to 5.13.4 ([#2023](https://github.com/aws-powertools/powertools-lambda-java/issues/2023)) +* bump org.codehaus.mojo:exec-maven-plugin from 3.3.0 to 3.5.1 ([#2015](https://github.com/aws-powertools/powertools-lambda-java/issues/2015)) +* bump aws.sdk.version from 2.32.10 to 2.32.16 ([#2014](https://github.com/aws-powertools/powertools-lambda-java/issues/2014)) +* bump io.github.ascopes:protobuf-maven-plugin from 3.6.1 to 3.7.0 ([#2016](https://github.com/aws-powertools/powertools-lambda-java/issues/2016)) +* bump actions/download-artifact from 4.3.0 to 5.0.0 ([#2017](https://github.com/aws-powertools/powertools-lambda-java/issues/2017)) +* bump squidfunk/mkdocs-material in /docs ([#1984](https://github.com/aws-powertools/powertools-lambda-java/issues/1984)) +* bump org.apache.maven.plugins:maven-surefire-plugin ([#2013](https://github.com/aws-powertools/powertools-lambda-java/issues/2013)) +* bump aws-actions/configure-aws-credentials from 4.2.1 to 4.3.1 ([#2011](https://github.com/aws-powertools/powertools-lambda-java/issues/2011)) +* bump software.amazon.awscdk:aws-cdk-lib from 2.162.1 to 2.208.0 ([#1990](https://github.com/aws-powertools/powertools-lambda-java/issues/1990)) +* **ci:** Make E2E tests compatible with latest CDK lib version. Improve retry implementation. ([#2008](https://github.com/aws-powertools/powertools-lambda-java/issues/2008)) +* **ci:** Improve reliability of retries in TracingE2ET ([#2018](https://github.com/aws-powertools/powertools-lambda-java/issues/2018)) -## [1.10.2] - 2022-01-07 -* **Tracing**: Ability to override object mapper used for serializing method response as trace metadata when enabled. This provides users ability to customize how and what you want to capture as metadata from method response object. [#698](https://github.com/awslabs/aws-lambda-powertools-java/pull/698) +<a name="v2.2.1"></a> +## [v2.2.1] - 2025-07-29 +## Bug Fixes -## [1.10.1] - 2022-01-06 +* **parameters:** Correctly check for empty values in AppConfig Parameters Provider. ([#1982](https://github.com/aws-powertools/powertools-lambda-java/issues/1982)) + +## Maintenance + +* bump dependabot/fetch-metadata from 2.3.0 to 2.4.0 ([#1954](https://github.com/aws-powertools/powertools-lambda-java/issues/1954)) +* bump github/codeql-action from 3.29.3 to 3.29.4 ([#1978](https://github.com/aws-powertools/powertools-lambda-java/issues/1978)) +* bump org.apache.logging.log4j:log4j-transform-maven-shade-plugin-extensions ([#1977](https://github.com/aws-powertools/powertools-lambda-java/issues/1977)) +* bump aws.sdk.version from 2.31.78 to 2.32.6 ([#1976](https://github.com/aws-powertools/powertools-lambda-java/issues/1976)) +* bump com.amazonaws:aws-lambda-java-events from 3.16.0 to 3.16.1 ([#1975](https://github.com/aws-powertools/powertools-lambda-java/issues/1975)) +* bump com.networknt:json-schema-validator from 1.5.1 to 1.5.8 ([#1974](https://github.com/aws-powertools/powertools-lambda-java/issues/1974)) +* bump ossf/scorecard-action from 2.4.0 to 2.4.2 ([#1950](https://github.com/aws-powertools/powertools-lambda-java/issues/1950)) +* bump org.apache.maven.plugins:maven-compiler-plugin ([#1972](https://github.com/aws-powertools/powertools-lambda-java/issues/1972)) +* bump actions/download-artifact from 4.2.1 to 4.3.0 ([#1967](https://github.com/aws-powertools/powertools-lambda-java/issues/1967)) +* bump aws-actions/configure-aws-credentials from 2.2.0 to 4.2.1 ([#1965](https://github.com/aws-powertools/powertools-lambda-java/issues/1965)) +* bump actions/dependency-review-action from 4.5.0 to 4.7.1 ([#1968](https://github.com/aws-powertools/powertools-lambda-java/issues/1968)) +* bump actions/checkout from 3.5.3 to 4.2.2 ([#1963](https://github.com/aws-powertools/powertools-lambda-java/issues/1963)) +* bump sam/build-java21 ([#1962](https://github.com/aws-powertools/powertools-lambda-java/issues/1962)) +* bump squidfunk/mkdocs-material in /docs ([#1961](https://github.com/aws-powertools/powertools-lambda-java/issues/1961)) +* bump actions/upload-artifact from 4.5.0 to 4.6.2 ([#1953](https://github.com/aws-powertools/powertools-lambda-java/issues/1953)) +* bump github/codeql-action from 3.27.9 to 3.29.3 ([#1958](https://github.com/aws-powertools/powertools-lambda-java/issues/1958)) +* bump actions/setup-java from 3.11.0 to 4.7.1 ([#1957](https://github.com/aws-powertools/powertools-lambda-java/issues/1957)) +* **ci:** Add Docker paths via globs to dependabot and update Dockerfiles to pin sha256 ([#1960](https://github.com/aws-powertools/powertools-lambda-java/issues/1960)) +* **ci:** Remove osv workflow. ([#1973](https://github.com/aws-powertools/powertools-lambda-java/issues/1973)) +* **ci:** add new dependabot package ecosystems ([#1948](https://github.com/aws-powertools/powertools-lambda-java/issues/1948)) +* **ci:** Add GraalVM E2E tests and GH workflows ([#1945](https://github.com/aws-powertools/powertools-lambda-java/issues/1945)) -* **Logging**: Upgrade Log4j to version 2.17.1 for [CVE-2021-44832](https://nvd.nist.gov/vuln/detail/CVE-2021-44832) -## [1.10.0] - 2021-12-27 +<a name="v2.2.0"></a> +## [v2.2.0] - 2025-07-15 +## Bug Fixes -* **Logging**: Modern log4j configuration to customise structured logging. Refer [docs](https://awslabs.github.io/aws-lambda-powertools-java/core/logging/#upgrade-to-jsontemplatelayout-from-deprecated-lambdajsonlayout-configuration-in-log4j2xml) to start using new config. [#670](https://github.com/awslabs/aws-lambda-powertools-java/pull/670) -* **SQS Batch**: Support batch size greater than 10. [#667](https://github.com/awslabs/aws-lambda-powertools-java/pull/667) +* **examples:** Fix GraalVM metadata after common runtime client changes ([#1935](https://github.com/aws-powertools/powertools-lambda-java/issues/1935)) -## [1.9.0] - 2021-12-21 +## Features -* **Logging**: Upgrade Log4j to version 2.17.0 for [CVE-2021-45105](https://nvd.nist.gov/vuln/detail/CVE-2021-45105) -* **Tracing**: add `Service` annotation. [#654](https://github.com/awslabs/aws-lambda-powertools-java/issues/654) +* **batch:** add support for batch execution in parallel with custom Executor ([#1900](https://github.com/aws-powertools/powertools-lambda-java/issues/1900)) +* **serialization:** Add GraalVM metadata configuration ([#1905](https://github.com/aws-powertools/powertools-lambda-java/issues/1905)) -## [1.8.2] - 2021-12-15 +## Maintenance + +* update issue, PR, and discussion templates ([#1915](https://github.com/aws-powertools/powertools-lambda-java/issues/1915)) +* **ci:** remove v2 dependabot configuration. Restore OSSF scorecard workflow. ([#1924](https://github.com/aws-powertools/powertools-lambda-java/issues/1924)) +* **ci:** Update branch protection rules ([#1914](https://github.com/aws-powertools/powertools-lambda-java/issues/1914)) -## Security -* Upgrading Log4j to version 2.16.0 for [CVE-2021-45046](https://nvd.nist.gov/vuln/detail/CVE-2021-45046) +<a name="v2.1.1"></a> +## [v2.1.1] - 2025-06-20 +## Bug Fixes -## [1.8.1] - 2021-12-10 +* **kafka:** Handle message indices in proto data also for Glue Schema Registry ([#1907](https://github.com/aws-powertools/powertools-lambda-java/issues/1907)) -## Security +## Maintenance -* Upgrading Log4j to version 2.15.0 for [CVE-2021-44228](https://nvd.nist.gov/vuln/detail/CVE-2021-44228) -## [1.8.0] - 2021-11-05 -### Added +<a name="v2.1.0"></a> +## [v2.1.0] - 2025-06-19 +## Bug Fixes -* **Powertools Cloudformation module (NEW)**: New module simplifying [AWS Lambda-backed custom resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html) written in Java. [#560](https://github.com/awslabs/aws-lambda-powertools-java/pull/560) -* **SQS Large message processing**: Ability to override the default `S3Client` use to fetch payload from S3. [#602](https://github.com/awslabs/aws-lambda-powertools-java/pull/602) +* **ci:** Add maven project description to Kafka utility. ([#1903](https://github.com/aws-powertools/powertools-lambda-java/issues/1903)) +* **kafka:** Add support for confluent message indices. ([#1902](https://github.com/aws-powertools/powertools-lambda-java/issues/1902)) +* **metrics:** Do not flush when no metrics were added to avoid printing root-level _aws dict ([#1891](https://github.com/aws-powertools/powertools-lambda-java/issues/1891)) -### Regression +## Documentation -* **Logging**: `@Logging` annotation now works with `@Tracing` annotation on `RequestStreamHandler` when used in `logEvent` mode. [#567](https://github.com/awslabs/aws-lambda-powertools-java/pull/567) +* Announce deprecation of v1 +* Version documentation ([#1878](https://github.com/aws-powertools/powertools-lambda-java/issues/1878)) + +## Features + +* **kafka:** New Kafka utility ([#1898](https://github.com/aws-powertools/powertools-lambda-java/issues/1898)) ## Maintenance -* **deps**: Bump third party dependencies to the latest versions. +* **ci:** Update workflows to make v2 the default ([#1888](https://github.com/aws-powertools/powertools-lambda-java/issues/1888)) -## [1.7.3] - 2021-09-14 -* **SQS Batch processing**: Ability to move non retryable message to configured dead letter queue(DLQ). [#500](https://github.com/awslabs/aws-lambda-powertools-java/pull/500) +<a name="v2.0.0"></a> +## [v2.0.0] - 2025-06-12 +## Maintenance -## [1.7.2] - 2021-08-03 -* **Powertools All Modules**: Upgrade to the latest(1.14.0) aspectj-maven-plugin which also supports Java 9 and newer versions. -Users no longer need to depend on [com.nickwongdev](https://mvnrepository.com/artifact/com.nickwongdev/aspectj-maven-plugin/1.12.6) as a workaround. [#489](https://github.com/awslabs/aws-lambda-powertools-java/pull/489) -* **Logging**: Performance optimisation to improve cold start. [#484](https://github.com/awslabs/aws-lambda-powertools-java/pull/484) -* **SQS Batch processing/Large message**: Module now lazy loads default SQS client. [#484](https://github.com/awslabs/aws-lambda-powertools-java/pull/484) -## [1.7.1] - 2021-07-06 +<a name="v2.0.0-RC1"></a> +## [v2.0.0-RC1] - 2025-06-11 +## Bug Fixes -* **Powertools All Modules**: Fix static code analysis violations done via [spotbugs](https://github.com/spotbugs/spotbugs) ([#458](https://github.com/awslabs/aws-lambda-powertools-java/pull/458)). +* workflow paths for examples v2 builds +* add aspectj-rt to batch e2e ([#1410](https://github.com/aws-powertools/powertools-lambda-java/issues/1410)) +* **ci:** Fix failing E2E tests and temporarily exclude TracingE2E ([#1847](https://github.com/aws-powertools/powertools-lambda-java/issues/1847)) +* **ci:** add user/pass to javasetup ([#1832](https://github.com/aws-powertools/powertools-lambda-java/issues/1832)) +* **ci:** Update control flow to allow for better skipping of things ([#1831](https://github.com/aws-powertools/powertools-lambda-java/issues/1831)) +* **ci:** Checkout repo on doc release ([#1869](https://github.com/aws-powertools/powertools-lambda-java/issues/1869)) +* **logging:** Prevent accidental overwriting of reserved keys via structured arguments +* **logging:** Escape double-quotes when serializing strings into JSON. ([#1845](https://github.com/aws-powertools/powertools-lambda-java/issues/1845)) +* **v2:** Fix params builder to provide default transformation manager ([#1549](https://github.com/aws-powertools/powertools-lambda-java/issues/1549)) -## [1.7.0] - 2021-07-05 +## Documentation -### Added +* v2 documentation maintenance fixing formatting and dependency issues as well as adding roadmap and llms.txt ([#1819](https://github.com/aws-powertools/powertools-lambda-java/issues/1819)) +* **metrics:** Add upgrade guide for re-designed Metrics utility ([#1868](https://github.com/aws-powertools/powertools-lambda-java/issues/1868)) +* **v2:** Create upgrade guide and versioning policy ([#1856](https://github.com/aws-powertools/powertools-lambda-java/issues/1856)) -* **Logging**: Support for extracting Correlation id using `@Logging` annotation via `correlationIdPath` attribute and `setCorrelationId()` method in `LoggingUtils`([#448](https://github.com/awslabs/aws-lambda-powertools-java/pull/448)). -* **Logging**: New `clearState` attribute on `@Logging` annotation to clear previously added custom keys upon invocation([#453](https://github.com/awslabs/aws-lambda-powertools-java/pull/453)). +## Features + +* advanced logging ([#1539](https://github.com/aws-powertools/powertools-lambda-java/issues/1539)) +* upgraded embedded metrics library for high resolution metrics ([#1550](https://github.com/aws-powertools/powertools-lambda-java/issues/1550)) +* **cfn-custom-resource:** Add optional 'reason' field for detailed failure reporting ([#1810](https://github.com/aws-powertools/powertools-lambda-java/issues/1810)) +* **idempotency:** Add support for ReturnValuesOnConditionCheckFailure in Idempotency. ([#1821](https://github.com/aws-powertools/powertools-lambda-java/issues/1821)) +* **idempotency:** Add response hook feature ([#1814](https://github.com/aws-powertools/powertools-lambda-java/issues/1814)) +* **metrics:** New metrics module implementation with support for Metrics providers and usage without annotations ([#1863](https://github.com/aws-powertools/powertools-lambda-java/issues/1863)) +* **v2:** Add GraalVM reachability metadata for core utilities ([#1753](https://github.com/aws-powertools/powertools-lambda-java/issues/1753)) +* **v2:** parallel batch processing ([#1620](https://github.com/aws-powertools/powertools-lambda-java/issues/1620)) +* **v2:** batch validation with partial failure ([#1621](https://github.com/aws-powertools/powertools-lambda-java/issues/1621)) +* **v2:** publish snapshots ([#1655](https://github.com/aws-powertools/powertools-lambda-java/issues/1655)) +* **v2:** GraalVM support for parameters module ([#1824](https://github.com/aws-powertools/powertools-lambda-java/issues/1824)) +* **v2:** new logging module ([#1435](https://github.com/aws-powertools/powertools-lambda-java/issues/1435)) +* **v2:** Validation failures return 400s ([#1489](https://github.com/aws-powertools/powertools-lambda-java/issues/1489)) ## Maintenance -* **deps**: Bump third party dependencies to the latest versions. +* Support spotbugs running anywhere ([#1537](https://github.com/aws-powertools/powertools-lambda-java/issues/1537)) +* V2 update from main ([#1365](https://github.com/aws-powertools/powertools-lambda-java/issues/1365)) +* remove Java 8 from v2 examples ([#1531](https://github.com/aws-powertools/powertools-lambda-java/issues/1531)) +* fix end 2 end build ([#1534](https://github.com/aws-powertools/powertools-lambda-java/issues/1534)) +* cleanup poms and reduce warning noise ([#1535](https://github.com/aws-powertools/powertools-lambda-java/issues/1535)) +* [V2] rename 'core' module to 'common' ([#1364](https://github.com/aws-powertools/powertools-lambda-java/issues/1364)) +* update v2 ([#1409](https://github.com/aws-powertools/powertools-lambda-java/issues/1409)) +* remove aspectj-rt from the library ([#1408](https://github.com/aws-powertools/powertools-lambda-java/issues/1408)) +* Start V2 branch ([#1346](https://github.com/aws-powertools/powertools-lambda-java/issues/1346)) +* **automation:** Update automation workflows ([#1779](https://github.com/aws-powertools/powertools-lambda-java/issues/1779)) ([#1830](https://github.com/aws-powertools/powertools-lambda-java/issues/1830)) +* **ci:** Set snapshot repository to "central" server ID +* **ci:** Publish to Maven Central instead of OSSRH instance ([#1858](https://github.com/aws-powertools/powertools-lambda-java/issues/1858)) +* **v2:** Merge down from main ([#1574](https://github.com/aws-powertools/powertools-lambda-java/issues/1574)) +* **v2:** Split parameters module up by parameter provider ([#1403](https://github.com/aws-powertools/powertools-lambda-java/issues/1403)) +* **v2:** Fix IaC lint ([#1576](https://github.com/aws-powertools/powertools-lambda-java/issues/1576)) +* **v2:** e2e tests ([#1571](https://github.com/aws-powertools/powertools-lambda-java/issues/1571)) +* **v2:** clean examples ([#1495](https://github.com/aws-powertools/powertools-lambda-java/issues/1495)) +* **v2:** document use of aws-crt-client ([#1092](https://github.com/aws-powertools/powertools-lambda-java/issues/1092)) ([#1605](https://github.com/aws-powertools/powertools-lambda-java/issues/1605)) +* **v2:** remove java 1.8 relics from the code ([#1659](https://github.com/aws-powertools/powertools-lambda-java/issues/1659)) +* **v2:** remove deprecated code ([#1624](https://github.com/aws-powertools/powertools-lambda-java/issues/1624)) +* **v2:** Remove rule preventing production release of 2.0.0 ([#1867](https://github.com/aws-powertools/powertools-lambda-java/issues/1867)) +* **v2:** Split powertools idempotency module (without redis impl) ([#1559](https://github.com/aws-powertools/powertools-lambda-java/issues/1559)) + +## Pull Requests -## [1.6.0] - 2021-06-21 +* Merge pull request [#1608](https://github.com/aws-powertools/powertools-lambda-java/issues/1608) from aws-powertools/chore/v2-merge-main-down +* Merge pull request [#1525](https://github.com/aws-powertools/powertools-lambda-java/issues/1525) from aws-powertools/chore/main-into-v2 +* Merge pull request [#1494](https://github.com/aws-powertools/powertools-lambda-java/issues/1494) from aws-powertools/chore/merge-main-into-v2 +* Merge pull request [#1492](https://github.com/aws-powertools/powertools-lambda-java/issues/1492) from aws-powertools/main-into-v2-again +* Merge pull request [#1477](https://github.com/aws-powertools/powertools-lambda-java/issues/1477) from aws-powertools/chore/main-into-v2 -### Added -* **Tracing**: Support for Boolean and Number type as value in `TracingUtils.putAnnotation()`([#423](https://github.com/awslabs/aws-lambda-powertools-java/pull/432)). -* **Logging**: API to remove any additional custom key from logger entry using `LoggingUtils.removeKeys()`([#395](https://github.com/awslabs/aws-lambda-powertools-java/pull/395)). +<a name="v1.20.2"></a> +## [v1.20.2] - 2025-05-20 +## Bug Fixes + +* **ci:** update release workflow ([#1854](https://github.com/aws-powertools/powertools-lambda-java/issues/1854)) +* **ci:** minor fixes for workflows ([#1829](https://github.com/aws-powertools/powertools-lambda-java/issues/1829)) + +## Documentation + +* Add version policy page and llms.txt, enable privacy plugin, fix formatting ([#1823](https://github.com/aws-powertools/powertools-lambda-java/issues/1823)) ## Maintenance -* **deps**: Bump third party dependencies to the latest versions. +* **automation:** Update automation workflows ([#1779](https://github.com/aws-powertools/powertools-lambda-java/issues/1779)) + + +<a name="v1.20.1"></a> +## [v1.20.1] - 2025-04-08 +## Bug Fixes + +* Load version.properties file as resource stream to fix loading when packaged as jar. ([#1813](https://github.com/aws-powertools/powertools-lambda-java/issues/1813)) + +## Documentation + +* fix 2 typos +* Correct XML formatting for Maven configuration in Large Messages utility docs + +## Maintenance + +* Prep release 1.20.1 ([#1817](https://github.com/aws-powertools/powertools-lambda-java/issues/1817)) + + +<a name="v1.20.0"></a> +## [v1.20.0] - 2025-03-25 +## Features + +* **cfn-custom-resource:** Add optional 'reason' field for detailed failure reporting ([#1758](https://github.com/aws-powertools/powertools-lambda-java/issues/1758)) + +## Maintenance + +* Prep release 1.20.0 ([#1811](https://github.com/aws-powertools/powertools-lambda-java/issues/1811)) + + +<a name="v1.19.0"></a> +## [v1.19.0] - 2025-03-07 +## Bug Fixes + +* add workflow dispatch to OSV +* Allow empty responses as well as null response in AppConfig ([#1673](https://github.com/aws-powertools/powertools-lambda-java/issues/1673)) +* **ci:** Add workflow_dispatch to build script ([#1792](https://github.com/aws-powertools/powertools-lambda-java/issues/1792)) +* **ci:** add permissions to release workflow +* **ci:** Permissions ([#1771](https://github.com/aws-powertools/powertools-lambda-java/issues/1771)) +* **ci:** OSSF Changes ([#1769](https://github.com/aws-powertools/powertools-lambda-java/issues/1769)) + +## Documentation + +* add roadmap page and include roadmap for 2025 +* improve tracing doc for sdk instrumentation ([#1687](https://github.com/aws-powertools/powertools-lambda-java/issues/1687)) +* add link to Powertools for AWS Lambda workshop ([#1641](https://github.com/aws-powertools/powertools-lambda-java/issues/1641)) +* HelloWorldStreamFunction in examples fails with sam ([#1532](https://github.com/aws-powertools/powertools-lambda-java/issues/1532)) + +## Features + +* **build:** remove java 8 support in v2 ([#1606](https://github.com/aws-powertools/powertools-lambda-java/issues/1606)) +* **ci:** Add OSV + +## Maintenance + +* deprecate java1.8 al1 ([#1706](https://github.com/aws-powertools/powertools-lambda-java/issues/1706)) +* Testing java21 aspectj pre-release ([#1519](https://github.com/aws-powertools/powertools-lambda-java/issues/1519)) +* Remove build cruft +* SAM and Terraform IaC extracted from pr_build and simplified approach. ([#1533](https://github.com/aws-powertools/powertools-lambda-java/issues/1533)) +* Update netty version ([#1768](https://github.com/aws-powertools/powertools-lambda-java/issues/1768)) +* Set versions of transitive dependencies ([#1767](https://github.com/aws-powertools/powertools-lambda-java/issues/1767)) +* update Jackson +* Remove empty CDK test ([#1542](https://github.com/aws-powertools/powertools-lambda-java/issues/1542)) +* add openssf to repo +* remove auto-merge +* remove unecessary creds acquisition ([#1572](https://github.com/aws-powertools/powertools-lambda-java/issues/1572)) +* update version to next snapshot: 1-19.0-SNAPSHOT ([#1516](https://github.com/aws-powertools/powertools-lambda-java/issues/1516)) +* **ci:** update permissions ([#1764](https://github.com/aws-powertools/powertools-lambda-java/issues/1764)) +* **ci:** Add release environment +* **ci:** Remove RELEASE variable ([#1772](https://github.com/aws-powertools/powertools-lambda-java/issues/1772)) +* **deps:** update JSII to 1.108 ([#1791](https://github.com/aws-powertools/powertools-lambda-java/issues/1791)) +* **deps:** Update deps for jackson ([#1793](https://github.com/aws-powertools/powertools-lambda-java/issues/1793)) +* **docs:** load self hosted mermaid.js + +## Pull Requests + +* Merge pull request [#1720](https://github.com/aws-powertools/powertools-lambda-java/issues/1720) from aws-powertools/chore/docs_script_self + + +<a name="v1.18.0"></a> +## [v1.18.0] - 2023-11-16 +## Bug Fixes + +* get trace id from system property when env var is not set ([#1503](https://github.com/aws-powertools/powertools-lambda-java/issues/1503)) +* Fix schema validation unit test build issues ([#1456](https://github.com/aws-powertools/powertools-lambda-java/issues/1456)) + +## Documentation + +* Update gradle configuration readme ([#1359](https://github.com/aws-powertools/powertools-lambda-java/issues/1359)) +* Adding Kotlin example. ([#1454](https://github.com/aws-powertools/powertools-lambda-java/issues/1454)) +* apply line highlight only for default light mode ([#1453](https://github.com/aws-powertools/powertools-lambda-java/issues/1453)) +* Add Serveless Framework example ([#1363](https://github.com/aws-powertools/powertools-lambda-java/issues/1363)) +* Fix link to SQS large message migration guide ([#1422](https://github.com/aws-powertools/powertools-lambda-java/issues/1422)) +* Change link to absolute versioned path for examples ([#1374](https://github.com/aws-powertools/powertools-lambda-java/issues/1374)) +* **customer-reference:** add Vertex Pharmaceuticals as a customer reference ([#1486](https://github.com/aws-powertools/powertools-lambda-java/issues/1486)) +* **logging:** align example cloudwatch example to correct output from code: lambda_request_id --> function_request_id ([#1411](https://github.com/aws-powertools/powertools-lambda-java/issues/1411)) + +## Features + +* ALC ([#1514](https://github.com/aws-powertools/powertools-lambda-java/issues/1514)) +* Add support for POWERTOOLS_LOGGER_LOG_EVENT ([#1510](https://github.com/aws-powertools/powertools-lambda-java/issues/1510)) +* Terraform example ([#1478](https://github.com/aws-powertools/powertools-lambda-java/issues/1478)) + +## Maintenance + +* Addition of Warn Message If Invalid Annotation Key While Tracing [#1511](https://github.com/aws-powertools/powertools-lambda-java/issues/1511) ([#1512](https://github.com/aws-powertools/powertools-lambda-java/issues/1512)) +* artifacts size on good branches ([#1493](https://github.com/aws-powertools/powertools-lambda-java/issues/1493)) +* add missing projects and improve workflow ([#1487](https://github.com/aws-powertools/powertools-lambda-java/issues/1487)) +* java21 support in our build ([#1488](https://github.com/aws-powertools/powertools-lambda-java/issues/1488)) +* Reporting size of the jars in GitHub comments ([#1196](https://github.com/aws-powertools/powertools-lambda-java/issues/1196)) +* secure github actions using hash instead of versions ([#1232](https://github.com/aws-powertools/powertools-lambda-java/issues/1232)) + + +<a name="v1.17.0"></a> +## [v1.17.0] - 2023-08-21 +## Bug Fixes + +* Roll log4j shade transformer forwards ([#1376](https://github.com/aws-powertools/powertools-lambda-java/issues/1376)) +* Rollback doc changes ([#1323](https://github.com/aws-powertools/powertools-lambda-java/issues/1323)) +* use default credentials provider for all provided SDK clients ([#1303](https://github.com/aws-powertools/powertools-lambda-java/issues/1303)) + +## Documentation + +* Adding CDK example ([#1321](https://github.com/aws-powertools/powertools-lambda-java/issues/1321)) +* improve contributing guide ([#1334](https://github.com/aws-powertools/powertools-lambda-java/issues/1334)) +* Add maintainers guide ([#1326](https://github.com/aws-powertools/powertools-lambda-java/issues/1326)) +* versioning - fix typo ([#1322](https://github.com/aws-powertools/powertools-lambda-java/issues/1322)) +* add support for docs versioning ([#1239](https://github.com/aws-powertools/powertools-lambda-java/issues/1239)) ([#1293](https://github.com/aws-powertools/powertools-lambda-java/issues/1293)) +* Started cleaning up example doc ([#1291](https://github.com/aws-powertools/powertools-lambda-java/issues/1291)) + +## Features + +* Add Batch Processor module ([#1317](https://github.com/aws-powertools/powertools-lambda-java/issues/1317)) +* large message in SQS and SNS ([#1310](https://github.com/aws-powertools/powertools-lambda-java/issues/1310)) + +## Maintenance + +* Fix missing version change pieces ([#1382](https://github.com/aws-powertools/powertools-lambda-java/issues/1382)) +* apply checkstyle again ([#1339](https://github.com/aws-powertools/powertools-lambda-java/issues/1339)) +* Add powertools specific user-agent-suffix to the AWS SDK v2 clients ([#1306](https://github.com/aws-powertools/powertools-lambda-java/issues/1306)) +* checkstyle formater & linter ([#1316](https://github.com/aws-powertools/powertools-lambda-java/issues/1316)) +* update poms to SNAPSHOT version for dev ([#1299](https://github.com/aws-powertools/powertools-lambda-java/issues/1299)) + + +<a name="v1.16.1"></a> +## [v1.16.1] - 2023-07-19 +## Bug Fixes + +* idempotency timeout bug ([#1285](https://github.com/aws-powertools/powertools-lambda-java/issues/1285)) +* ParamManager cannot provide default SSM & Secrets providers ([#1282](https://github.com/aws-powertools/powertools-lambda-java/issues/1282)) +* Handle batch failures in FIFO queues correctly ([#1183](https://github.com/aws-powertools/powertools-lambda-java/issues/1183)) +* examples shouldn't be deployed to mvn central ([#1253](https://github.com/aws-powertools/powertools-lambda-java/issues/1253)) + +## Documentation + +* update README.md ([#1294](https://github.com/aws-powertools/powertools-lambda-java/issues/1294)) +* adding our customer references ([#1287](https://github.com/aws-powertools/powertools-lambda-java/issues/1287)) +* update documentation for aspectJ ([#1273](https://github.com/aws-powertools/powertools-lambda-java/issues/1273)) + +## Maintenance + +* **unit-test:** Add missing unit tests in modules with low coverage ([#1264](https://github.com/aws-powertools/powertools-lambda-java/issues/1264)) + + +<a name="v1.16.0"></a> +## [v1.16.0] - 2023-06-29 +## Bug Fixes + +* e2e tests on JDK8 ([#1225](https://github.com/aws-powertools/powertools-lambda-java/issues/1225)) +* codecov URL ([#1222](https://github.com/aws-powertools/powertools-lambda-java/issues/1222)) +* remove GH pages ([#1211](https://github.com/aws-powertools/powertools-lambda-java/issues/1211)) +* update references to other variants +* missing idempotency key should not persist any data ([#1201](https://github.com/aws-powertools/powertools-lambda-java/issues/1201)) +* **docs:** add site_url to docs + +## Features + +* Add AppConfig provider to parameters module ([#1104](https://github.com/aws-powertools/powertools-lambda-java/issues/1104)) +* end-to-end tests for core modules and idempotency ([#970](https://github.com/aws-powertools/powertools-lambda-java/issues/970)) +* **docs:** adds S3 Docs uploader + +## Maintenance + +* Update docs base origin url ([#1238](https://github.com/aws-powertools/powertools-lambda-java/issues/1238)) +* E2E tests GitHub action ([#1175](https://github.com/aws-powertools/powertools-lambda-java/issues/1175)) +* add all java versions and use corretto for build ([#1191](https://github.com/aws-powertools/powertools-lambda-java/issues/1191)) +* Change repo URL to the new location ([#1171](https://github.com/aws-powertools/powertools-lambda-java/issues/1171)) +* Swap implementation of `aspectj-maven-plugin` to support Java 17 ([#1172](https://github.com/aws-powertools/powertools-lambda-java/issues/1172)) +* update e2e-tests with latest Powertools version ([#1173](https://github.com/aws-powertools/powertools-lambda-java/issues/1173)) +* rename project from Powertools to Powertools for AWS Lambda (Java) ([#1169](https://github.com/aws-powertools/powertools-lambda-java/issues/1169)) +* **ci:** add workflow to dispatch analytics fetching ([#1143](https://github.com/aws-powertools/powertools-lambda-java/issues/1143)) + + +<a name="v1.15.0"></a> +## [v1.15.0] - 2023-03-21 +## Bug Fixes + +* **cloudformation-module:** Use physicalResourceId when not provided by custom resource ([#1082](https://github.com/aws-powertools/powertools-lambda-java/issues/1082)) + +## Documentation + +* **cloudformation-module:** Improve Docs ([#1090](https://github.com/aws-powertools/powertools-lambda-java/issues/1090)) +* **plugin:** fix mdocs and git revision plugin integration ([#1066](https://github.com/aws-powertools/powertools-lambda-java/issues/1066)) + +## Features + +* Add DynamoDB provider to parameters module ([#1091](https://github.com/aws-powertools/powertools-lambda-java/issues/1091)) + +## Maintenance + +* update the project version to 1.15.0 ([#1097](https://github.com/aws-powertools/powertools-lambda-java/issues/1097)) +* **governance:** update issue templates ([#1062](https://github.com/aws-powertools/powertools-lambda-java/issues/1062)) +* **metrics:** deprecate withMetricLogger in favor of withMetricsLogger ([#1060](https://github.com/aws-powertools/powertools-lambda-java/issues/1060)) + + +<a name="v1.14.0"></a> +## [v1.14.0] - 2023-02-17 +## Documentation + +* **home:** update powertools definition + +## Features + +* **metrics:** introduce MetricsUtils.withMetricsLogger utility ([#1000](https://github.com/aws-powertools/powertools-lambda-java/issues/1000)) + +## Maintenance + +* update the project version to 1.14.0 ([#1052](https://github.com/aws-powertools/powertools-lambda-java/issues/1052)) + + +<a name="v1.13.0"></a> +## [v1.13.0] - 2022-12-14 +## Bug Fixes + +* envelope is not taken into account with built-in types ([#960](https://github.com/aws-powertools/powertools-lambda-java/issues/960)) + +## Documentation + +* add missing grammar article ([#976](https://github.com/aws-powertools/powertools-lambda-java/issues/976)) + +## Features + +* **idempotency:** handle lambda timeout scenarios for INPROGRESS records ([#933](https://github.com/aws-powertools/powertools-lambda-java/issues/933)) + +## Maintenance + +* update the project version to 1.13.0 ([#1018](https://github.com/aws-powertools/powertools-lambda-java/issues/1018)) + + +<a name="v1.12.3"></a> +## [v1.12.3] - 2022-07-12 +## Maintenance + +* **ci:** fix build ([#853](https://github.com/aws-powertools/powertools-lambda-java/issues/853)) +* **ci:** Address GitHub workaround for CVE-2022-24765. +* **ci:** upgrade to checkout v3 + + +<a name="v1.12.2"></a> +## [v1.12.2] - 2022-04-29 +## Bug Fixes + +* remove local implementation of PayloadS3Pointer.java and use payloadoffloading-common ([#851](https://github.com/aws-powertools/powertools-lambda-java/issues/851)) + + +<a name="v1.12.1"></a> +## [v1.12.1] - 2022-04-21 +## Bug Fixes + +* disable idempotency doesn't disable dynamodb client creation in persistent store ([#796](https://github.com/aws-powertools/powertools-lambda-java/issues/796)) + +## Maintenance + +* correct bug fix release number +* **docs:** additional rename of project name ([#781](https://github.com/aws-powertools/powertools-lambda-java/issues/781)) ([#789](https://github.com/aws-powertools/powertools-lambda-java/issues/789)) + +## Reverts +* chore: correct bug fix release number + + +<a name="v1.12.0"></a> +## [v1.12.0] - 2022-03-01 +## Bug Fixes + +* **docs:** fix title for custom resources page ([#763](https://github.com/aws-powertools/powertools-lambda-java/issues/763)) + +## Features + +* Easy Event Deserialization ([#757](https://github.com/aws-powertools/powertools-lambda-java/issues/757)) + +## Maintenance + +* remove examples from the project as it was moved to aws-lambda-powertools-examples ([#772](https://github.com/aws-powertools/powertools-lambda-java/issues/772)) +* remove SQS and Idempotency examples ([#754](https://github.com/aws-powertools/powertools-lambda-java/issues/754)) +* downgrade release plugin to validate release fail issue +* downgrade release plugin to validate release fail issue + + +<a name="v1.11.0"></a> +## [v1.11.0] - 2022-02-16 +## Maintenance + +* **docs:** FAQ for Kotlin projects +* **docs:** Clarify test config needed for Tracing module ([#735](https://github.com/aws-powertools/powertools-lambda-java/issues/735)) +* **docs:** typo in CHANGELOG.md + + +<a name="v1.10.3"></a> +## [v1.10.3] - 2022-02-01 +## Bug Fixes + +* Prevent message to be marked as success if failed sending to DLQ ([#731](https://github.com/aws-powertools/powertools-lambda-java/issues/731)) +* **gradle:** Fix gradle example and docs to work with java 12+ ([#703](https://github.com/aws-powertools/powertools-lambda-java/issues/703)) + +## Maintenance + +* move core utilities example to aws-samples/aws-lambda-powertools-examples ([#733](https://github.com/aws-powertools/powertools-lambda-java/issues/733)) +* Update readme to refer examples repo +* **ci:** set release env variable for auto closing issue + + +<a name="v1.10.2"></a> +## [v1.10.2] - 2022-01-07 +## Features + +* **tracing:** ability to override object mapper ([#698](https://github.com/aws-powertools/powertools-lambda-java/issues/698)) + +## Maintenance + +* Automate release prep ([#696](https://github.com/aws-powertools/powertools-lambda-java/issues/696)) +* **automation:** find replace pom.xml at all levels +* **automation:** find replace pom.xml at all levels +* **docs:** Add FAQs section to docs with information about Lombok support. ([#680](https://github.com/aws-powertools/powertools-lambda-java/issues/680)) + + +<a name="v1.10.1"></a> +## [v1.10.1] - 2022-01-06 +## Features + +* **ci:** auto-notify & close issues on release + +## Maintenance + +* prep release 1.10.1 +* action to automate release prep +* action to automate release prep +* **docs:** Latest version of aspectj.post-compile-weaving +* **docs:** Full gradle config in example for each module +* **docs:** use free fair gradle aspect plugin ([#679](https://github.com/aws-powertools/powertools-lambda-java/issues/679)) + + +<a name="v1.10.0"></a> +## [v1.10.0] - 2021-12-27 + +<a name="v1.9.1"></a> +## [v1.9.1] - 2021-12-27 +## Bug Fixes + +* support batch size greater than 10 processing ([#667](https://github.com/aws-powertools/powertools-lambda-java/issues/667)) + +## Features + +* Json layout modern implementation ([#670](https://github.com/aws-powertools/powertools-lambda-java/issues/670)) + +## Maintenance + +* prep release 1.10.0 ([#671](https://github.com/aws-powertools/powertools-lambda-java/issues/671)) +* Fix docs layout + + +<a name="v1.8.3"></a> +## [v1.8.3] - 2021-12-21 + +<a name="v1.9.0"></a> +## [v1.9.0] - 2021-12-21 +## Features + +* **tracing:** add service annotation ([#655](https://github.com/aws-powertools/powertools-lambda-java/issues/655)) + +## Maintenance + +* prep release 1.9.0 ([#666](https://github.com/aws-powertools/powertools-lambda-java/issues/666)) +* localise abstract json layout implementation ([#664](https://github.com/aws-powertools/powertools-lambda-java/issues/664)) +* Update edit url prefix on doc +* Update docs to reflect latest gradle plugin fix ([#656](https://github.com/aws-powertools/powertools-lambda-java/issues/656)) + + +<a name="v1.8.2"></a> +## [v1.8.2] - 2021-12-15 +## Maintenance + +* prep release 1.8.2 ([#653](https://github.com/aws-powertools/powertools-lambda-java/issues/653)) + + +<a name="v1.8.1"></a> +## [v1.8.1] - 2021-12-10 +## Documentation + +* **tenets:** update Idiomatic tenet to Progressive ([#615](https://github.com/aws-powertools/powertools-lambda-java/issues/615)) + +## Maintenance + +* prep release 1.8.1 ([#647](https://github.com/aws-powertools/powertools-lambda-java/issues/647)) + + +<a name="v1.8.0"></a> +## [v1.8.0] - 2021-11-05 +## Bug Fixes + +* LoggingAspect precedence to be last for accepting mutated args ([#567](https://github.com/aws-powertools/powertools-lambda-java/issues/567)) + +## Features + +* create Java cfn-response equivalent ([#558](https://github.com/aws-powertools/powertools-lambda-java/issues/558)) ([#560](https://github.com/aws-powertools/powertools-lambda-java/issues/560)) + +## Maintenance + +* prep release 1.8.0 ([#608](https://github.com/aws-powertools/powertools-lambda-java/issues/608)) +* Fix failing build and auto merge only when build is success +* spotbug check ([#565](https://github.com/aws-powertools/powertools-lambda-java/issues/565)) +* Fix/Ignore spotbugs violations + + +<a name="v1.7.3"></a> +## [v1.7.3] - 2021-09-14 +## Bug Fixes + +* reset cold start only when placed on handler ([#508](https://github.com/aws-powertools/powertools-lambda-java/issues/508)) + +## Documentation + +* **batch-processing:** Support for moving non retryable msg to DLQ ([#531](https://github.com/aws-powertools/powertools-lambda-java/issues/531)) + +## Features + +* **batch-processing:** move non retry-able message to DLQ ([#500](https://github.com/aws-powertools/powertools-lambda-java/issues/500)) + +## Maintenance + +* prep release 1.7.3 + + +<a name="v1.7.2"></a> +## [v1.7.2] - 2021-08-03 +## Documentation + +* status badges + +## Maintenance + +* prep release 1.7.2 ([#490](https://github.com/aws-powertools/powertools-lambda-java/issues/490)) +* Upgrade to latest(1.14.0) aspectj-maven-plugin ([#489](https://github.com/aws-powertools/powertools-lambda-java/issues/489)) +* Logging and SQS utility Optimisations ([#484](https://github.com/aws-powertools/powertools-lambda-java/issues/484)) +* wait for merge until check is green +* wait for merge until check is green +* wait for spotbugs check before automerge + + +<a name="v1.7.1"></a> +## [v1.7.1] - 2021-07-06 +## Maintenance + +* prep release 1.7.1 ([#459](https://github.com/aws-powertools/powertools-lambda-java/issues/459)) + + +<a name="v1.7.0"></a> +## [v1.7.0] - 2021-07-05 +## Features + +* Clear logger state ([#453](https://github.com/aws-powertools/powertools-lambda-java/issues/453)) + +## Maintenance + +* prep release 1.7.0 ([#457](https://github.com/aws-powertools/powertools-lambda-java/issues/457)) + + +<a name="v1.6.0"></a> +## [v1.6.0] - 2021-06-20 +## Features + +* [#421](https://github.com/aws-powertools/powertools-lambda-java/issues/421) Support for Boolean and Number type as value in TracingUtils putAnnotation ([#423](https://github.com/aws-powertools/powertools-lambda-java/issues/423)) +* logger-Remove custom keys interface ([#395](https://github.com/aws-powertools/powertools-lambda-java/issues/395)) + +## Maintenance + +* prep release 1.6.0 ([#436](https://github.com/aws-powertools/powertools-lambda-java/issues/436)) +* setup-java v2 ([#353](https://github.com/aws-powertools/powertools-lambda-java/issues/353)) +* add JDK 16 to build matrix ([#367](https://github.com/aws-powertools/powertools-lambda-java/issues/367)) + + +<a name="v1.5.0"></a> +## [v1.5.0] - 2021-03-31 +## Features + +* remove deprecated attributes on Tracing annotation ([#330](https://github.com/aws-powertools/powertools-lambda-java/issues/330)) + +## Maintenance + +* prep release 1.5.0 ([#345](https://github.com/aws-powertools/powertools-lambda-java/issues/345)) +* rename automerge workflow name +* fix auto merge dependabot PR ([#342](https://github.com/aws-powertools/powertools-lambda-java/issues/342)) + + +<a name="v1.4.0"></a> +## [v1.4.0] - 2021-03-11 +## Bug Fixes + +* null check on dimension + +## Features + +* Default dimensions from powertools instead of emf library ([#317](https://github.com/aws-powertools/powertools-lambda-java/issues/317)) + +## Maintenance + +* prep release 1.4.0 ([#324](https://github.com/aws-powertools/powertools-lambda-java/issues/324)) + + +<a name="v1.3.0"></a> +## [v1.3.0] - 2021-03-05 +## Bug Fixes + +* powertools specific log level env var to not conflict with the system LOG_LEVEL ([#306](https://github.com/aws-powertools/powertools-lambda-java/issues/306)) +* **example:** Update the example to v1.2.0 ([#288](https://github.com/aws-powertools/powertools-lambda-java/issues/288)) + +## Documentation + +* ability to override object mapper used for logging event ([#303](https://github.com/aws-powertools/powertools-lambda-java/issues/303)) + +## Features + +* single metric utility method to pick default namespace ([#305](https://github.com/aws-powertools/powertools-lambda-java/issues/305)) +* ability to override object mapper used for logging event ([#302](https://github.com/aws-powertools/powertools-lambda-java/issues/302)) +* respect code guru profile handler implementation ([#304](https://github.com/aws-powertools/powertools-lambda-java/issues/304)) +* capture metrics even when handler results in exception ([#286](https://github.com/aws-powertools/powertools-lambda-java/issues/286)) + +## Maintenance + +* Prep release 1.3.0 ([#316](https://github.com/aws-powertools/powertools-lambda-java/issues/316)) +* migrate docs from gatsby to mkdocs ([#308](https://github.com/aws-powertools/powertools-lambda-java/issues/308)) +* consistent field names for trace and req id with logger ([#309](https://github.com/aws-powertools/powertools-lambda-java/issues/309)) + + +<a name="v1.2.0"></a> +## [v1.2.0] - 2021-01-13 +## Code Refactoring + +* replace Apache Commons Logging with SLF4J ([#212](https://github.com/aws-powertools/powertools-lambda-java/issues/212)) + +## Documentation + +* shadow sidebar to remain expanded ([#208](https://github.com/aws-powertools/powertools-lambda-java/issues/208)) + +## Features + +* support for env variable in tracing capture modes ([#249](https://github.com/aws-powertools/powertools-lambda-java/issues/249)) +* custom segment names ([#221](https://github.com/aws-powertools/powertools-lambda-java/issues/221)) + +## Maintenance + +* Prep release 1.2.0 ([#250](https://github.com/aws-powertools/powertools-lambda-java/issues/250)) +* Consistent env variable names for tracing ([#251](https://github.com/aws-powertools/powertools-lambda-java/issues/251)) +* update docs dependencies ([#214](https://github.com/aws-powertools/powertools-lambda-java/issues/214)) + + +<a name="v1.1.0"></a> +## [v1.1.0] - 2020-12-03 +## Documentation + +* add source code link in nav bar ([#199](https://github.com/aws-powertools/powertools-lambda-java/issues/199)) + +## Features + +* Parameters injection ([#201](https://github.com/aws-powertools/powertools-lambda-java/issues/201)) + +## Maintenance + +* Prep release 1.1.0 ([#205](https://github.com/aws-powertools/powertools-lambda-java/issues/205)) + + +<a name="v1.0.1"></a> +## [v1.0.1] - 2020-11-26 +## Bug Fixes + +* fixing dependencies security issues ([#169](https://github.com/aws-powertools/powertools-lambda-java/issues/169)) + +## Maintenance + +* Prep for release (1.0.1) ([#198](https://github.com/aws-powertools/powertools-lambda-java/issues/198)) +* upgrade AspectjGradlePlugin to latest and example project to java 11 ([#189](https://github.com/aws-powertools/powertools-lambda-java/issues/189)) + +## Performance Improvements + +* Make UrlConnectionHttpClient default client for params fetch ([#185](https://github.com/aws-powertools/powertools-lambda-java/issues/185)) + + +<a name="v1.0.0"></a> +## [v1.0.0] - 2020-11-04 +## Bug Fixes + +* **docs:** Correct url and not for gradle ([#158](https://github.com/aws-powertools/powertools-lambda-java/issues/158)) + +## Code Refactoring + +* Rename helpers for validation module ([#167](https://github.com/aws-powertools/powertools-lambda-java/issues/167)) +* Rename annotations for GA ([#165](https://github.com/aws-powertools/powertools-lambda-java/issues/165)) + + +<a name="v0.6.0-beta"></a> +## [v0.6.0-beta] - 2020-10-27 +## Documentation + +* Update all the environment variables used ([#127](https://github.com/aws-powertools/powertools-lambda-java/issues/127)) + +## Features + +* log aws request id ([#133](https://github.com/aws-powertools/powertools-lambda-java/issues/133)) + +## Maintenance + +* Prepare for 0.6.0-beta ([#155](https://github.com/aws-powertools/powertools-lambda-java/issues/155)) +* gradle example ([#147](https://github.com/aws-powertools/powertools-lambda-java/issues/147)) + + +<a name="v0.5.0-beta"></a> +## [v0.5.0-beta] - 2020-10-06 +## Features + +* SQS Partial batch Utility ([#120](https://github.com/aws-powertools/powertools-lambda-java/issues/120)) + +## Maintenance + +* Prepare for 0.5.0-beta ([#124](https://github.com/aws-powertools/powertools-lambda-java/issues/124)) + + +<a name="v0.4.0-beta"></a> +## [v0.4.0-beta] - 2020-10-02 +## Bug Fixes + +* Log event via object mapper and not depend on toString ([#113](https://github.com/aws-powertools/powertools-lambda-java/issues/113)) + +## Features + +* integration with CloudWatch ServiceLens [#88](https://github.com/aws-powertools/powertools-lambda-java/issues/88) ([#111](https://github.com/aws-powertools/powertools-lambda-java/issues/111)) + + +<a name="v0.3.1-beta"></a> +## [v0.3.1-beta] - 2020-09-25 +## Bug Fixes + +* Removing Log4J dependencies from the tracing module. ([#106](https://github.com/aws-powertools/powertools-lambda-java/issues/106)) +* Removing v1 Java SDK dependencies for X-Ray ([#105](https://github.com/aws-powertools/powertools-lambda-java/issues/105)) + +## Documentation + +* fixes to the documentation ([#102](https://github.com/aws-powertools/powertools-lambda-java/issues/102)) + + +<a name="v0.3.0-beta"></a> +## [v0.3.0-beta] - 2020-09-22 +## Features + +* Metrics utility ([#91](https://github.com/aws-powertools/powertools-lambda-java/issues/91)) + + +<a name="v0.2.0-beta"></a> +## [v0.2.0-beta] - 2020-09-01 +## Bug Fixes + +* **general:** clean up typos and code ([#62](https://github.com/aws-powertools/powertools-lambda-java/issues/62)) + +## Documentation + +* Readme update ([#72](https://github.com/aws-powertools/powertools-lambda-java/issues/72)) +* Update SQS large payload docs ([#60](https://github.com/aws-powertools/powertools-lambda-java/issues/60)) +* fixing documentation ([#59](https://github.com/aws-powertools/powertools-lambda-java/issues/59)) + +## Features + +* Utility without annotation ([#61](https://github.com/aws-powertools/powertools-lambda-java/issues/61)) +* SQS Large message handling ([#55](https://github.com/aws-powertools/powertools-lambda-java/issues/55)) + + +<a name="v0.1.0-beta"></a> +## v0.1.0-beta - 2020-08-31 +## Bug Fixes + +* Fixing security issues on package.json dependencies ([#22](https://github.com/aws-powertools/powertools-lambda-java/issues/22)) +* Fixing package versions for security purpose ([#16](https://github.com/aws-powertools/powertools-lambda-java/issues/16)) +* Rename Java ArtifactId and GroupId to be compliant with new AWS standard. +* docs pipeline +* Fix Readme.md documentation and remove unecessary parts. + +## Code Refactoring + +* consistent naming of powertools tracing and initial docs. ([#48](https://github.com/aws-powertools/powertools-lambda-java/issues/48)) +* consistent naming of powertools. ([#46](https://github.com/aws-powertools/powertools-lambda-java/issues/46)) +* Split Tracing and Logging packages Dependency split ([#45](https://github.com/aws-powertools/powertools-lambda-java/issues/45)) +* move groupId from software.aws.lambda to softwarte.amazon.lambda ([#23](https://github.com/aws-powertools/powertools-lambda-java/issues/23)) -## [1.5.0] - 2021-03-30 +## Documentation -* **Metrics**: Ability to set multiple dimensions as default dimensions via `MetricsUtils.defaultDimensions()`. - Introduced in [v1.4.0](https://github.com/awslabs/aws-lambda-powertools-java/releases/tag/v1.4.0) - `MetricsUtils.defaultDimensionSet()` is deprecated now for better user experience. +* Adding license to each source file ([#44](https://github.com/aws-powertools/powertools-lambda-java/issues/44)) +* Initial javadocs for PowerLogger class. ([#43](https://github.com/aws-powertools/powertools-lambda-java/issues/43)) +* Implement Logger and Tracer part ([#27](https://github.com/aws-powertools/powertools-lambda-java/issues/27)) +* Initial javadocs for our logging annotation. ([#40](https://github.com/aws-powertools/powertools-lambda-java/issues/40)) +* update maven artifactId and groupId to new one +* Improving README file +* Init project documentation ci: init Github actions flow -## [1.4.0] - 2021-03-11 -* **Metrics**: Ability to set default dimension for metrics via `MetricsUtils.defaultDimensionSet()`. - - **Note**: If your monitoring depends on [default dimensions](https://github.com/awslabs/aws-embedded-metrics-java/blob/master/src/main/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLogger.java#L173) captured before via [aws-embedded-metrics-java](https://github.com/awslabs/aws-embedded-metrics-java), - those either need to be updated or has to be explicitly captured via `MetricsUtils.defaultDimensionSet()`. - +## Pull Requests -* **Metrics**: Remove validation of having minimum one dimension. EMF now support [Dimension set being empty](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html) as well. +* Merge pull request [#1](https://github.com/aws-powertools/powertools-lambda-java/issues/1) from stevehouel/master -## [1.3.0] - 2021-03-05 -* **Powertools**: It now works out of the box with [code guru profile handler implementation](https://docs.aws.amazon.com/codeguru/latest/profiler-ug/lambda-custom.html). -* **Logging**: Ability to override object mapper used for logging event. This provides customers ability to customize how and what they want to log from event. -* **Metrics**: Module now by default captures AWS Request id as property if used together with Metrics annotation. It will also capture Xray Trace ID as property if tracing is enabled. This ensures good observability and tracing. -* **Metrics**:`withSingleMetric` from `MetricsUtils can now pick the default namespace specified either on Metrics annotation or via POWERTOOLS_METRICS_NAMESPACE env var, without need to specify explicitly for each call. -* **Metrics**:`Metrics` annotation captures metrics even in case of unhandled exception from Lambda function. -* **Docs**: Migrated from Gatsby to MKdocs documentation system +[Unreleased]: https://github.com/aws-powertools/powertools-lambda-java/compare/v2.4.0...HEAD +[v2.4.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v2.3.0...v2.4.0 +[v2.3.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v2.2.1...v2.3.0 +[v2.2.1]: https://github.com/aws-powertools/powertools-lambda-java/compare/v2.2.0...v2.2.1 +[v2.2.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v2.1.1...v2.2.0 +[v2.1.1]: https://github.com/aws-powertools/powertools-lambda-java/compare/v2.1.0...v2.1.1 +[v2.1.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v2.0.0...v2.1.0 +[v2.0.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v2.0.0-RC1...v2.0.0 +[v2.0.0-RC1]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.20.2...v2.0.0-RC1 +[v1.20.2]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.20.1...v1.20.2 +[v1.20.1]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.20.0...v1.20.1 +[v1.20.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.19.0...v1.20.0 +[v1.19.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.18.0...v1.19.0 +[v1.18.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.17.0...v1.18.0 +[v1.17.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.16.1...v1.17.0 +[v1.16.1]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.16.0...v1.16.1 +[v1.16.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.15.0...v1.16.0 +[v1.15.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.14.0...v1.15.0 +[v1.14.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.13.0...v1.14.0 +[v1.13.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.12.3...v1.13.0 +[v1.12.3]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.12.2...v1.12.3 +[v1.12.2]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.12.1...v1.12.2 +[v1.12.1]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.12.0...v1.12.1 +[v1.12.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.11.0...v1.12.0 +[v1.11.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.10.3...v1.11.0 +[v1.10.3]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.10.2...v1.10.3 +[v1.10.2]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.10.1...v1.10.2 +[v1.10.1]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.10.0...v1.10.1 +[v1.10.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.9.1...v1.10.0 +[v1.9.1]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.8.3...v1.9.1 +[v1.8.3]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.9.0...v1.8.3 +[v1.9.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.8.2...v1.9.0 +[v1.8.2]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.8.1...v1.8.2 +[v1.8.1]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.8.0...v1.8.1 +[v1.8.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.7.3...v1.8.0 +[v1.7.3]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.7.2...v1.7.3 +[v1.7.2]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.7.1...v1.7.2 +[v1.7.1]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.7.0...v1.7.1 +[v1.7.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.6.0...v1.7.0 +[v1.6.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.5.0...v1.6.0 +[v1.5.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.4.0...v1.5.0 +[v1.4.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.3.0...v1.4.0 +[v1.3.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.2.0...v1.3.0 +[v1.2.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.1.0...v1.2.0 +[v1.1.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.0.1...v1.1.0 +[v1.0.1]: https://github.com/aws-powertools/powertools-lambda-java/compare/v1.0.0...v1.0.1 +[v1.0.0]: https://github.com/aws-powertools/powertools-lambda-java/compare/v0.6.0-beta...v1.0.0 +[v0.6.0-beta]: https://github.com/aws-powertools/powertools-lambda-java/compare/v0.5.0-beta...v0.6.0-beta +[v0.5.0-beta]: https://github.com/aws-powertools/powertools-lambda-java/compare/v0.4.0-beta...v0.5.0-beta +[v0.4.0-beta]: https://github.com/aws-powertools/powertools-lambda-java/compare/v0.3.1-beta...v0.4.0-beta +[v0.3.1-beta]: https://github.com/aws-powertools/powertools-lambda-java/compare/v0.3.0-beta...v0.3.1-beta +[v0.3.0-beta]: https://github.com/aws-powertools/powertools-lambda-java/compare/v0.2.0-beta...v0.3.0-beta +[v0.2.0-beta]: https://github.com/aws-powertools/powertools-lambda-java/compare/v0.1.0-beta...v0.2.0-beta diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 914e0741d..080643027 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,58 +1,130 @@ +<!-- markdownlint-disable MD043 MD041 --> +# Table of contents <!-- omit in toc --> + +- [Contributing Guidelines](#contributing-guidelines) + - [Reporting Bugs/Feature Requests](#reporting-bugsfeature-requests) + - [Contributing via Pull Requests](#contributing-via-pull-requests) + - [Dev setup](#dev-setup) + - [Local documentation](#local-documentation) + - [Conventions](#conventions) + - [General terminology and practices](#general-terminology-and-practices) + - [Testing definition](#testing-definition) + - [Finding contributions to work on](#finding-contributions-to-work-on) + - [Code of Conduct](#code-of-conduct) + - [Security issue notifications](#security-issue-notifications) + - [Troubleshooting](#troubleshooting) + - [Licensing](#licensing) + # Contributing Guidelines -Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional -documentation, we greatly value feedback and contributions from our community. +<!-- markdownlint-disable MD013 --> +Thank you for your interest in contributing to our project. Whether it's a [bug report](https://github.com/aws-powertools/powertools-lambda-java/issues/new?assignees=&labels=bug%2C+triage&projects=&template=bug_report.md&title=Bug%3A+TITLE), [new feature](https://github.com/aws-powertools/powertools-lambda-java/issues/new?assignees=&labels=feature-request%2C+triage&projects=&template=feature_request.md&title=Feature+request%3A+TITLE) or [additional documentation](https://github.com/aws-powertools/powertools-lambda-java/issues/new?assignees=&labels=documentation%2Ctriage&projects=&template=documentation_improvements.yml&title=Docs%3A+TITLE), we greatly value feedback and contributions from our community. +<!-- markdownlint-enable MD013 --> + +We encourage contributions from the community and we will work with contributors to merge their pull requests. +Rarely, we may close pull requests that do not meet our guidelines specified in CONTRIBUTING.md, or will require unreasonable effort to meet our quality bar. Please read through this document before submitting any issues or pull requests to ensure we have all the necessary information to effectively respond to your bug report or contribution. - ## Reporting Bugs/Feature Requests -We welcome you to use the GitHub issue tracker to report bugs or suggest features. - -When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already -reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: - -* A reproducible test case or series of steps -* The version of our code being used -* Any modifications you've made relevant to the bug -* Anything unusual about your environment or deployment +We welcome you to use the GitHub issue tracker to report bugs, suggest features, or documentation improvements. +<!-- markdownlint-disable MD013 --> +[When filing an issue](https://github.com/aws-powertools/powertools-lambda-java/issues/new/choose), please check [existing open](https://github.com/aws-powertools/powertools-lambda-java/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc), or [recently closed](https://github.com/aws-powertools/powertools-lambda-java/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed) issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. +<!-- markdownlint-enable MD013 --> ## Contributing via Pull Requests + Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: -1. You are working against the latest source on the *master* branch. -2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. -3. You open an issue to discuss any significant work - we would hate for your time to be wasted. +1. You are working on a fork. [Fork the repository](https://github.com/aws-powertools/powertools-lambda-java/fork). +2. You are working against the latest source on the **main** branch. +3. You've checked existing open, and recently merged pull requests to make sure someone else hasn't addressed the problem already. +4. You've opened an [issue](https://github.com/aws-powertools/powertools-lambda-java/issues/new/choose) before you begin any implementation. We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. + +### Dev setup + +We recommend using [IntelliJ IDEA](https://www.jetbrains.com/idea/) from JetBrains. +A community version is available and largely enough for our purpose. + +#### Code Formatting + +We strongly recommend installing the CheckStyle-IDEA plugin and apply the provided [checkstyle.xml](checkstyle.xml) in order to comply with our formatting rules: + +1. Install the [CheckStyle-IDEA plugin](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea) and restart IntelliJ. + +2. After installing the plugin, open the preferences (`⌘,` on macOS, or `Ctrl+Alt+S` on Windows/Linux) and search for _Code Style_. Click on the gear icon near the scheme and import checkstyle configuration. Click on "Apply" and "OK". +![](docs/media/intellij_checkstyle_1.png) + +3. Select the code you've created (module, package, class) and reformat code: `⌘⌥L` (macOS), or `Ctrl+Alt+L` (Windows/Linux). + +4. Apply the reformat, optimize imports, rearrange and cleanup to your code and only to java files: +![](docs/media/intellij_checkstyle_3.png) + +#### License headers +All the java files should contain the licence/copyright header. You can copy paste it from the [license-header](license-header) file. -To send us a pull request, please: +### Creating the pull request -1. Fork the repository. -2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. -3. Ensure local tests pass. -4. Commit to your fork using clear commit messages. -5. Send us a pull request, answering any default questions in the pull request interface. -6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. +To send us a pull request, please follow these steps: + +1. Create a new branch to focus on the specific change you are contributing e.g. `improv/logger-debug-sampling` +2. Run all tests, and code baseline checks: `mvn clean verify -P build-with-spotbugs` +3. Commit to your fork using clear commit messages. +4. Send us a pull request with a [conventional semantic title](.github/semantic.yml), and answering any default questions in the pull request interface. +5. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). +### Local documentation + +If you work on the documentation, you may find useful to display it locally while editing, using the following command: + +- **Docs website**: `make docs-local-docker` + +## Conventions + +### General terminology and practices + +| Category | Convention | +|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Style guide** | We use Checkstyle and Sonar to enforce beyond good practices. | +| **Exceptions** | Specific exceptions live within the utilities themselves and use `Exception` suffix e.g. `IdempotencyKeyException`. | +| **Git commits** | We follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). We do not enforce conventional commits on contributors to lower the entry bar. Instead, we enforce a conventional PR title so our label automation and changelog are generated correctly. | +| **API documentation** | API reference docs are generated from Javadoc which should have examples to allow developers to have what they need within their own IDE. Documentation website covers the wider usage, tips, and strive to be concise. | +| **Documentation** | We treat it like a product. We sub-divide content aimed at getting started (80% of customers) vs advanced usage (20%). We also ensure customers know how to unit test their code when using our features. | + +### Testing definition + +We group tests in different categories + +| Test | When to write | Notes | Speed | +| ----------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | +| Unit tests | Verify the smallest possible unit works. | Networking access is prohibited. Prefer Functional tests given our complexity. | Lightning fast (nsec to ms) | +| End-to-end tests | Gain confidence that a Lambda function with our code operates as expected. | It simulates how customers configure, deploy, and run their Lambda function - Event Source configuration, IAM permissions, etc. | Slow (minutes) | + +**NOTE**: Unit tests are mandatory. End-to-end tests should be created for new modules. +We apply the principle of the [test pyramid](https://martinfowler.com/articles/practical-test-pyramid.html), having a majority of unit tests to cover most cases (standard, edge, errors, ...) and generally one end-to-end test to verify the standard case in the target environment. ## Finding contributions to work on -Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. +Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/help wanted/invalid/question/documentation), [looking at any 'good first issue' issues is a great place to start](https://github.com/aws-powertools/powertools-lambda-java/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). ## Code of Conduct + This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. - +<opensource-codeofconduct@amazon.com> with any additional questions or comments. ## Security issue notifications + If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. +## Troubleshooting + ## Licensing diff --git a/GraalVM.md b/GraalVM.md new file mode 100644 index 000000000..bbddb5e3b --- /dev/null +++ b/GraalVM.md @@ -0,0 +1,86 @@ +# GraalVM Compatibility for AWS Lambda Powertools Java + +## Table of Contents +- [Overview](#overview) +- [Prerequisites](#prerequisites) +- [General Implementation Steps](#general-implementation-steps) +- [Known Issues and Solutions](#known-issues-and-solutions) +- [Reference Implementation](#reference-implementation) + +## Overview +This documentation provides guidance for adding GraalVM support for AWS Lambda Powertools Java modules and using the modules in Lambda functions. + +## Prerequisites +- GraalVM 21+ installation +- Maven 3.x + +## General Implementation Steps +GraalVM native image compilation requires complete knowledge of an application's dynamic features at build time. The GraalVM reachability metadata (GRM) JSON files are essential because Java applications often use features that are determined at runtime, such as reflection, dynamic proxy classes, resource loading, and JNI (Java Native Interface). The metadata files tell GraalVM which classes need reflection access, which resources need to be included in the native image, and which proxy classes need to be generated. + +In order to generate the metadata reachability files for Powertools for Lambda, follow these general steps. + +1. **Add Maven Profiles** + - Add profile for generating GraalVM reachability metadata files. You can find an example of this in profile `generate-graalvm-files` of this [pom.xml](powertools-common/pom.xml). + - Add another profile for running the tests in the native image. You can find and example of this in profile `graalvm-native` of this [pom.xml](powertools-common/pom.xml). + +2. **Generate Reachability Metadata** + - Set the `JAVA_HOME` environment variable to use GraalVM + - Run tests with `-Pgenerate-graalvm-files` profile. +```shell +mvn -Pgenerate-graalvm-files clean test +``` + +3. **Validate Native Image Tests** + - Set the `JAVA_HOME` environment variable to use GraalVM + - Run tests with `-Pgraalvm-native` profile. This will build a GraalVM native image and run the JUnit tests. +```shell +mvn -Pgraalvm-native clean test +``` + +4. **Clean Up Metadata** + - GraalVM metadata reachability files generated in Step 2 contains references to the test scoped dependencies as well. + - Remove the references in generated metadata files for the following (and any other references to test scoped resources and classes): + - JUnit + - Mockito + - ByteBuddy + +## Known Issues and Solutions +1. **Mockito Compatibility** + - Powertools uses Mockito 5.x which uses “inline mock maker” as the default. This mock maker does not play well with GraalVM. Mockito [recommends](https://github.com/mockito/mockito/releases/tag/v5.0.0) using subclass mock maker with GraalVM. Therefore `generate-graalvm-files` profile uses subclass mock maker instead of inline mock maker. + - Subclass mock maker does not support testing static methods. Tests have therefore been modified to use [JUnit Pioneer](https://junit-pioneer.org/docs/environment-variables/) to inject the environment variables in the scope of the test's execution. + +2. **Log4j Compatibility** + - Version 2.22.1 fails with this error +``` +java.lang.InternalError: com.oracle.svm.core.jdk.UnsupportedFeatureError: Defining hidden classes at runtime is not supported. +``` + - This has been [fixed](https://github.com/apache/logging-log4j2/discussions/2364#discussioncomment-8950077) in Log4j 2.24.x. PT has been updated to use this version of Log4j + +3. **Test Class Organization** + - **Issue**: Anonymous inner classes and lambda expressions in Mockito matchers cause `NoSuchMethodError` in GraalVM native tests + - **Solution**: + - Extract static inner test classes to separate concrete classes in the same package as the class under test + - Replace lambda expressions in `ArgumentMatcher` with concrete implementations + - Use `mockito-subclass` dependency in GraalVM profiles + - **Example**: Replace `argThat(resp -> resp.getStatus() != expectedStatus)` with: + ```java + argThat(new ArgumentMatcher<Response>() { + @Override + public boolean matches(Response resp) { + return resp != null && resp.getStatus() != expectedStatus; + } + }) + ``` + +4. **Package Visibility Issues** + - **Issue**: Test handler classes cannot access package-private methods when placed in subpackages + - **Solution**: Place test handler classes in the same package as the class under test, not in subpackages like `handlers/` + - **Example**: Use `software.amazon.lambda.powertools.cloudformation` instead of `software.amazon.lambda.powertools.cloudformation.handlers` + +5. **Test Stubs Best Practice** + - **Best Practice**: Avoid mocking where possible and use concrete test stubs provided by `powertools-common` package + - **Solution**: Use `TestLambdaContext` and other test stubs from `powertools-common` test-jar instead of Mockito mocks + - **Implementation**: Add `powertools-common` test-jar dependency and replace `mock(Context.class)` with `new TestLambdaContext()` + +## Reference Implementation +Working example is available in the [examples](examples/powertools-examples-core-utilities/sam-graalvm). diff --git a/Priming.md b/Priming.md new file mode 100644 index 000000000..d7597ad8c --- /dev/null +++ b/Priming.md @@ -0,0 +1,59 @@ +# Automatic Priming for AWS Lambda Powertools Java + +## Table of Contents +- [Overview](#overview) +- [Implementation Steps](#general-implementation-steps) +- [Known Issues](#known-issues-and-solutions) +- [Reference Implementation](#reference-implementation) + +## Overview +Priming is the process of preloading dependencies and initializing resources during the INIT phase, rather than during the INVOKE phase to further optimize startup performance with SnapStart. +This is required because Java frameworks that use dependency injection load classes into memory when these classes are explicitly invoked, which typically happens during Lambda’s INVOKE phase. + +This documentation provides guidance for automatic class priming in Powertools for AWS Lambda Java modules. + + +## Implementation Steps +Classes are proactively loaded using Java runtime hooks which are part of the open source [CRaC (Coordinated Restore at Checkpoint) project](https://openjdk.org/projects/crac/). +Implementations across the project use the `beforeCheckpoint()` hook, to prime Snapstart-enabled Java functions via Class Priming. +In order to generate the `classloaded.txt` file for a Java module in this project, follow these general steps. + +1. **Add Maven Profile** + - Add maven test profile with the following VM argument for generating classes loaded files. + ```shell + -Xlog:class+load=info:classesloaded.txt + ``` + - You can find an example of this in `generate-classesloaded-file` profile in this [pom.xml](powertools-metrics/pom.xml). + +2. **Generate classes loaded file** + - Run tests with `-Pgenerate-classesloaded-file` profile. + ```shell + mvn -Pgenerate-classesloaded-file clean test + ``` + - This will generate a file named `classesloaded.txt` in the target directory of the module. + +3. **Cleanup the file** + - The classes loaded file generated in Step 2 has the format + `[0.054s][info][class,load] java.lang.Object source: shared objects file` + but we are only interested in `java.lang.Object` - the fully qualified class name. + - To strip the lines to include only the fully qualified class name, + Use the following regex to replace with empty string. + - `^\[[\[\]0-9.a-z,]+ ` (to replace the left part) + - `( source: )[0-9a-z :/._$-]+` (to replace the right part) + +4. **Add file to resources** + - Move the cleaned-up file to the corresponding `src/main/resources` directory of the module. See [example](powertools-metrics/src/main/resources/classesloaded.txt). + +5. **Register and checkpoint** + - A class, usually the entry point of the module, should register the CRaC resource in the constructor. [Example](powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsFactory.java) + - Note that AspectJ aspect is not suitable for this purpose, as it does not work with CRaC. + - Add the `beforeCheckpoint()` hook in the same class to invoke `ClassPreLoader.preloadClasses()`. The `ClassPreLoader` class is implemented in `powertools-common` module. + - This will ensure that the classes are already pre-loaded by the Snapstart RESTORE operation leading to a shorter INIT duration. + + +## Known Issues +- This is a manual process at the moment, but it can be automated in the future. +- `classesloaded.txt` file includes test classes as well because the file is generated while running tests. This is not a problem because all the classes that are not found are ignored by `ClassPreLoader.preloadClasses()`. Also `beforeCheckpoint()` hook is not time-sensitive, it only runs once when a new Lambda version gets published. + +## Reference Implementation +Working example is available in the [powertools-metrics](powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsFactory.java). diff --git a/README.md b/README.md index fff63d89c..4c02e2d1f 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,60 @@ -# AWS Lambda Powertools (Java) +# Powertools for AWS Lambda (Java) -![aws provider](https://img.shields.io/badge/provider-AWS-orange?logo=amazon-aws&color=ff9900) ![Build status](https://github.com/awslabs/aws-lambda-powertools-java/actions/workflows/build.yml/badge.svg) ![Maven Central](https://img.shields.io/maven-central/v/software.amazon.lambda/powertools-parent) +![aws provider](https://img.shields.io/badge/provider-AWS-orange?logo=amazon-aws&color=ff9900) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=aws-powertools_powertools-lambda-java&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=aws-powertools_powertools-lambda-java) +[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=aws-powertools_powertools-lambda-java&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=aws-powertools_powertools-lambda-java) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/aws-powertools/powertools-lambda-java/badge)](https://api.securityscorecards.dev/projects/github.com/aws-powertools/powertools-lambda-java) ![Maven Central](https://img.shields.io/maven-central/v/software.amazon.lambda/powertools-parent) [![codecov.io](https://codecov.io/github/aws-powertools/powertools-lambda-java/branch/main/graphs/badge.svg)](https://app.codecov.io/gh/aws-powertools/powertools-lambda-java) -A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. ([AWS Lambda Powertools Python](https://github.com/awslabs/aws-lambda-powertools-python) is also available). -**[📜Documentation](https://awslabs.github.io/aws-lambda-powertools-java/)** | **[Feature request](https://github.com/awslabs/aws-lambda-powertools-java/issues/new?assignees=&labels=feature-request%2C+triage&template=feature_request.md&title=)** | **[🐛Bug Report](https://github.com/awslabs/aws-lambda-powertools-java/issues/new?assignees=&labels=bug%2C+triage&template=bug_report.md&title=)** | **[Detailed blog post](https://aws.amazon.com/blogs/opensource/simplifying-serverless-best-practices-with-aws-lambda-powertools-java/)** +Powertools for AWS Lambda (Java) is a developer toolkit to implement Serverless best practices and increase developer velocity. -### Installation +> Also available in [Python](https://github.com/aws-powertools/powertools-lambda-python), [TypeScript](https://github.com/aws-powertools/powertools-lambda-typescript), and [.NET](https://github.com/aws-powertools/powertools-lambda-dotnet). -Powertools is available in Maven Central. You can use your favourite dependency management tool to install it +**[📜Documentation](https://docs.powertools.aws.dev/lambda-java/latest)** | **[Feature request](https://github.com/aws-powertools/powertools-lambda-java/issues/new?template=feature_request.yml)** | **[🐛Bug Report](https://github.com/aws-powertools/powertools-lambda-java/issues/new?template=bug_report.yml)** | **[Detailed blog post](https://aws.amazon.com/blogs/compute/introducing-v2-of-powertools-for-aws-lambda-java/)** -* [maven](https://maven.apache.org/): +## Installation + +Powertools for AWS Lambda (Java) is available in Maven Central. You can use your favourite dependency management tool to install it + +### Maven: ```xml <dependencies> ... <dependency> <groupId>software.amazon.lambda</groupId> <artifactId>powertools-tracing</artifactId> - <version>1.10.2</version> + <version>2.9.0</version> </dependency> <dependency> <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-logging</artifactId> - <version>1.10.2</version> + <artifactId>powertools-logging-log4j</artifactId> + <version>2.9.0</version> </dependency> <dependency> <groupId>software.amazon.lambda</groupId> <artifactId>powertools-metrics</artifactId> - <version>1.10.2</version> + <version>2.9.0</version> </dependency> ... </dependencies> ``` -And configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project: +Next, configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project. +<details> + <summary><b>Maven</b></summary> + ```xml <build> <plugins> ... <plugin> - <groupId>org.codehaus.mojo</groupId> + <groupId>dev.aspectj</groupId> <artifactId>aspectj-maven-plugin</artifactId> - <version>1.14.0</version> + <version>1.14</version> <configuration> - <source>1.8</source> - <target>1.8</target> - <complianceLevel>1.8</complianceLevel> + <source>11</source> + <target>11</target> + <complianceLevel>11</complianceLevel> <aspectLibraries> <aspectLibrary> <groupId>software.amazon.lambda</groupId> @@ -62,6 +70,14 @@ And configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambd </aspectLibrary> </aspectLibraries> </configuration> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <!-- AspectJ compiler version, in sync with runtime --> + <version>1.9.22</version> + </dependency> + </dependencies> <executions> <execution> <goals> @@ -74,16 +90,97 @@ And configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambd </plugins> </build> ``` +</details> + +<details> +<summary><b>Gradle</b></summary> + +```groovy + + plugins { + id 'java' + id 'io.freefair.aspectj.post-compile-weaving' version '8.2.2' + } + + // the freefair aspect plugins targets gradle 8.2.1 + // https://docs.freefair.io/gradle-plugins/8.2.2/reference/ + wrapper { + gradleVersion = "8.2.1" + } + + repositories { + mavenCentral() + } + + dependencies { + aspect 'software.amazon.lambda:powertools-logging:{{ powertools.version }}' + aspect 'software.amazon.lambda:powertools-tracing:{{ powertools.version }}' + aspect 'software.amazon.lambda:powertools-metrics:{{ powertools.version }}' + implementation 'software.amazon.lambda:powertools-logging-log4j:{{ powertools.version }}' + implementation "org.aspectj:aspectjrt:1.9.22" + } + + sourceCompatibility = 11 + targetCompatibility = 11 +``` +</details> + + +### Java Compatibility +Powertools for AWS Lambda (Java) supports all Java versions from 11 to 25 in line with the [corresponding Lambda runtimes](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html). + +For the modules that provide annotations, Powertools for AWS Lambda (Java) leverages the **aspectj** library. +You may need to add the appropriate version of `aspectjrt` to your dependencies based on the JDK used for building your function: + +```xml +<dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>1.9.??</version> +</dependency> +``` + +<details> + <summary><b>JDK - aspectj dependency matrix</b></summary> + +Use the following [dependency matrix](https://github.com/eclipse-aspectj/aspectj/blob/master/docs/release/JavaVersionCompatibility.adoc) to understand which AspectJ version to use based on your JDK version: + +| JDK version | aspectj version | +|-------------|------------------------| +| `11-17` | `1.9.20.1` (or higher) | +| `21` | `1.9.21` (or higher) | +| `25` | `1.9.25` (or higher) | + +</details> + +## Examples + +See the latest release of the **[examples](https://github.com/aws-powertools/powertools-lambda-java/tree/main/examples)** for example projects showcasing usage of different utilities. + +Have a demo project to contribute which showcase usage of different utilities from powertools? We are happy to accept it [here](CONTRIBUTING.md#security-issue-notifications). + +## How to support Powertools for AWS Lambda (Java)? + +### Becoming a reference customer + +Knowing which companies are using this library is important to help prioritize the project internally. If your company is using Powertools for AWS Lambda (Java), you can request to have your name and logo added to the README file by raising a [Support Powertools for AWS Lambda (Java) (become a reference)](https://github.com/aws-powertools/powertools-lambda-java/issues/new?template=support_powertools.yml) issue. + +The following companies, among others, use Powertools: + +* [Capital One](https://www.capitalone.com/) +* [CPQi (Exadel Financial Services)](https://cpqi.com/) +* [Europace AG](https://europace.de/) +* [Vertex Pharmaceuticals](https://www.vrtx.com/) -## Example +## Connect -See **[example](./example/README.md)** for maven or gradle configurations including all features, and a SAM template with all Powertools env vars. +- **Powertools for AWS Lambda on Discord**: `#java` - **[Invite link](https://discord.gg/B8zZKbbyET)** +- **Email**: <aws-powertools-maintainers@amazon.com> -## Credits +## Security disclosures -* [MkDocs](https://www.mkdocs.org/) -* [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) +If you think you’ve found a potential security issue, please do not post it in the Issues. Instead, please follow the instructions [here](https://aws.amazon.com/security/vulnerability-reporting/) or [email AWS security directly](mailto:aws-security@amazon.com). ## License -This library is licensed under the Apache License, Version 2.0. See the LICENSE file. +This library is licensed under the MIT-0 License. See the [LICENSE](https://github.com/aws-powertools/powertools-lambda-java/blob/main/LICENSE) file. diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 000000000..680d8808c --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,427 @@ +<?xml version="1.0"?> +<!DOCTYPE module PUBLIC + "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" + "https://checkstyle.org/dtds/configuration_1_3.dtd"> + +<!-- + Checkstyle configuration that checks the Google coding conventions from Google Java Style + that can be found at https://google.github.io/styleguide/javaguide.html + + Checkstyle is very configurable. Be sure to read the documentation at + http://checkstyle.org (or in your downloaded distribution). + + To completely disable a check, just comment it out or delete it from the file. + To suppress certain violations please review suppression filters. + + Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov. + --> + +<module name="Checker"> + <module name="SuppressWarningsFilter"/> + + <property name="charset" value="UTF-8"/> + + <property name="severity" value="warning"/> + + <property name="fileExtensions" value="java"/> + <!-- Excludes all 'module-info.java' files --> + <!-- See https://checkstyle.org/filefilters/index.html --> + <module name="BeforeExecutionExclusionFileFilter"> + <property name="fileNamePattern" value="module\-info\.java$"/> + </module> + <!-- https://checkstyle.org/filters/suppressionfilter.html --> + <module name="SuppressionFilter"> + <property name="file" value="${org.checkstyle.google.suppressionfilter.config}" + default="checkstyle-suppressions.xml" /> + <property name="optional" value="true"/> + </module> + + <!-- Checks for whitespace --> + <!-- See http://checkstyle.org/checks/whitespace/index.html --> + <module name="FileTabCharacter"> + <property name="eachLine" value="true"/> + </module> + + <module name="LineLength"> + <property name="fileExtensions" value="java"/> + <property name="max" value="120"/> + <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/> + </module> + + <module name="FileLength"/> + + <module name="Header"> + <property name="headerFile" value="${basedir}/license-header"/> + <property name="severity" value="error"/> + </module> + + <module name="TreeWalker"> + <module name="OuterTypeFilename"/> + <module name="IllegalTokenText"> + <property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/> + <property name="format" + value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/> + <property name="message" + value="Consider using special escape sequence instead of octal value or Unicode escaped value."/> + </module> + <module name="AvoidEscapedUnicodeCharacters"> + <property name="allowEscapesForControlCharacters" value="true"/> + <property name="allowByTailComment" value="true"/> + <property name="allowNonPrintableEscapes" value="true"/> + </module> + <module name="AvoidStarImport"> + <property name="severity" value="error"/> + </module> + <module name="OneTopLevelClass"> + <property name="severity" value="error"/> + </module> + <module name="NoLineWrap"> + <property name="severity" value="info"/> + <property name="tokens" value="PACKAGE_DEF, IMPORT, STATIC_IMPORT"/> + </module> + <module name="EmptyBlock"> + <property name="option" value="TEXT"/> + <property name="tokens" + value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/> + </module> + <module name="EqualsAvoidNull"> + <property name="severity" value="error"/> + </module> + <module name="EqualsHashCode"> + <property name="severity" value="error"/> + </module> + <module name="NeedBraces"> + <property name="tokens" + value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE"/> + </module> + <module name="LeftCurly"> + <property name="tokens" + value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF, + INTERFACE_DEF, LAMBDA, LITERAL_CASE, LITERAL_CATCH, LITERAL_DEFAULT, + LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, + LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF, + OBJBLOCK, STATIC_INIT, RECORD_DEF, COMPACT_CTOR_DEF"/> + </module> + <module name="RightCurly"> + <property name="id" value="RightCurlySame"/> + <property name="tokens" + value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, + LITERAL_DO"/> + </module> + <module name="RightCurly"> + <property name="id" value="RightCurlyAlone"/> + <property name="option" value="alone"/> + <property name="tokens" + value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, + INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, INTERFACE_DEF, RECORD_DEF, + COMPACT_CTOR_DEF, LITERAL_SWITCH"/> + </module> + <module name="SuppressionXpathSingleFilter"> + <!-- suppresion is required till https://github.com/checkstyle/checkstyle/issues/7541 --> + <property name="id" value="RightCurlyAlone"/> + <property name="query" value="//RCURLY[parent::SLIST[count(./*)=1] + or preceding-sibling::*[last()][self::LCURLY]]"/> + </module> + <module name="WhitespaceAfter"> + <property name="tokens" + value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE, LITERAL_RETURN, + LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, LITERAL_FINALLY, DO_WHILE, ELLIPSIS, + LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_CATCH, LAMBDA, + LITERAL_YIELD, LITERAL_CASE"/> + </module> + <module name="WhitespaceAround"> + <property name="allowEmptyConstructors" value="true"/> + <property name="allowEmptyLambdas" value="true"/> + <property name="allowEmptyMethods" value="true"/> + <property name="allowEmptyTypes" value="true"/> + <property name="allowEmptyLoops" value="true"/> + <property name="ignoreEnhancedForColon" value="false"/> + <property name="tokens" + value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, + BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND, + LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, + LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED, + LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, + NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR, + SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/> + <message key="ws.notFollowed" + value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks + may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/> + <message key="ws.notPreceded" + value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/> + </module> + <module name="OneStatementPerLine"/> + <module name="MultipleVariableDeclarations"/> + <module name="ArrayTypeStyle"/> + <module name="MissingSwitchDefault"/> + <module name="FallThrough"> + <property name="severity" value="error"/> + </module> + <module name="UpperEll"/> + <module name="ModifierOrder"/> + <module name="EmptyLineSeparator"> + <property name="tokens" + value="PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF, + STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF, RECORD_DEF, + COMPACT_CTOR_DEF"/> + <property name="allowNoEmptyLineBetweenFields" value="true"/> + </module> + <module name="SeparatorWrap"> + <property name="id" value="SeparatorWrapDot"/> + <property name="tokens" value="DOT"/> + <property name="option" value="nl"/> + </module> + <module name="SeparatorWrap"> + <property name="id" value="SeparatorWrapComma"/> + <property name="tokens" value="COMMA"/> + <property name="option" value="EOL"/> + </module> + <module name="SeparatorWrap"> + <!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/259 --> + <property name="id" value="SeparatorWrapEllipsis"/> + <property name="tokens" value="ELLIPSIS"/> + <property name="option" value="EOL"/> + </module> + <module name="SeparatorWrap"> + <!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/258 --> + <property name="id" value="SeparatorWrapArrayDeclarator"/> + <property name="tokens" value="ARRAY_DECLARATOR"/> + <property name="option" value="EOL"/> + </module> + <module name="SeparatorWrap"> + <property name="id" value="SeparatorWrapMethodRef"/> + <property name="tokens" value="METHOD_REF"/> + <property name="option" value="nl"/> + </module> + <module name="PackageName"> + <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/> + <message key="name.invalidPattern" + value="Package name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="TypeName"> + <property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, + ANNOTATION_DEF, RECORD_DEF"/> + <message key="name.invalidPattern" + value="Type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="MemberName"> + <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/> + <message key="name.invalidPattern" + value="Member name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="ParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Parameter name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="LambdaParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="CatchParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Catch parameter name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="LocalVariableName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Local variable name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="PatternVariableName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Pattern variable name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="ClassTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Class type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="RecordComponentName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Record component name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="RecordTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Record type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="MethodTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Method type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="InterfaceTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Interface type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="NoFinalizer"/> + <module name="GenericWhitespace"> + <message key="ws.followed" + value="GenericWhitespace ''{0}'' is followed by whitespace."/> + <message key="ws.preceded" + value="GenericWhitespace ''{0}'' is preceded with whitespace."/> + <message key="ws.illegalFollow" + value="GenericWhitespace ''{0}'' should followed by whitespace."/> + <message key="ws.notPreceded" + value="GenericWhitespace ''{0}'' is not preceded with whitespace."/> + </module> + <module name="Indentation"> + <property name="basicOffset" value="4"/> + <property name="braceAdjustment" value="4"/> + <property name="caseIndent" value="4"/> + <property name="throwsIndent" value="8"/> + <property name="lineWrappingIndentation" value="8"/> + <property name="arrayInitIndent" value="4"/> + </module> + <module name="AbbreviationAsWordInName"> + <property name="ignoreFinal" value="false"/> + <property name="allowedAbbreviationLength" value="4"/> + <property name="tokens" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF, + PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF, PATTERN_VARIABLE_DEF, RECORD_DEF, + RECORD_COMPONENT_DEF"/> + </module> + <module name="NoWhitespaceBeforeCaseDefaultColon"/> + <module name="OverloadMethodsDeclarationOrder"/> + <module name="VariableDeclarationUsageDistance"/> + <module name="CustomImportOrder"> + <property name="sortImportsInGroupAlphabetically" value="true"/> + <property name="separateLineBetweenGroups" value="true"/> + <property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/> + <property name="tokens" value="IMPORT, STATIC_IMPORT, PACKAGE_DEF"/> + </module> + <module name="MethodParamPad"> + <property name="tokens" + value="CTOR_DEF, LITERAL_NEW, METHOD_CALL, METHOD_DEF, + SUPER_CTOR_CALL, ENUM_CONSTANT_DEF, RECORD_DEF"/> + </module> + <module name="NoWhitespaceBefore"> + <property name="tokens" + value="COMMA, SEMI, POST_INC, POST_DEC, DOT, + LABELED_STAT, METHOD_REF"/> + <property name="allowLineBreaks" value="true"/> + </module> + <module name="ParenPad"> + <property name="tokens" + value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF, + EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW, + LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL, + METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA, + RECORD_DEF"/> + </module> + <module name="OperatorWrap"> + <property name="option" value="NL"/> + <property name="tokens" + value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, + LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF, + TYPE_EXTENSION_AND "/> + </module> + <module name="AnnotationLocation"> + <property name="id" value="AnnotationLocationMostCases"/> + <property name="tokens" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, + RECORD_DEF, COMPACT_CTOR_DEF"/> + </module> + <module name="AnnotationLocation"> + <property name="id" value="AnnotationLocationVariables"/> + <property name="tokens" value="VARIABLE_DEF"/> + <property name="allowSamelineMultipleAnnotations" value="true"/> + </module> + <module name="NonEmptyAtclauseDescription"> + <property name="severity" value="info"/> + </module> + <module name="InvalidJavadocPosition"> + <property name="severity" value="info"/> + </module> + <module name="JavadocTagContinuationIndentation"> + <property name="severity" value="ignore"/> + </module> + <module name="SummaryJavadoc"> + <property name="severity" value="ignore"/> + <property name="forbiddenSummaryFragments" + value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/> + </module> + <module name="JavadocParagraph"> + <property name="severity" value="ignore"/> + </module> + <module name="RequireEmptyLineBeforeBlockTagGroup"/> + <module name="AtclauseOrder"> + <property name="tagOrder" value="@param, @return, @throws, @deprecated"/> + <property name="target" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/> + </module> + <module name="JavadocMethod"> + <property name="severity" value="info"/> + <property name="accessModifiers" value="public"/> + <property name="allowMissingParamTags" value="true"/> + <property name="allowMissingReturnTag" value="true"/> + <property name="allowedAnnotations" value="Override, Test"/> + <property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, COMPACT_CTOR_DEF"/> + </module> + <module name="MissingJavadocMethod"> + <property name="severity" value="info"/> + <property name="scope" value="public"/> + <property name="minLineCount" value="1"/> + <property name="allowedAnnotations" value="Override, Test"/> + <property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, + COMPACT_CTOR_DEF"/> + </module> + <module name="MissingJavadocType"> + <property name="severity" value="info"/> + <property name="scope" value="protected"/> + <property name="tokens" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, + RECORD_DEF, ANNOTATION_DEF"/> + <property name="excludeScope" value="nothing"/> + </module> + <module name="MethodName"> + <property name="format" value="^[a-z][a-z0-9]\w*$"/> + <message key="name.invalidPattern" + value="Method name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="SingleLineJavadoc"> + <property name="severity" value="ignore"/> + </module> + <module name="EmptyCatchBlock"> + <property name="exceptionVariableName" value="expected"/> + </module> + <module name="UnusedImports"> + <property name="severity" value="error"/> + </module> + <module name="UnusedLocalVariable"> + <property name="severity" value="error"/> + </module> + <module name="CommentsIndentation"> + <property name="severity" value="info"/> + <property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/> + </module> + <module name="CyclomaticComplexity" /> + <module name="DefaultComesLast"> + <property name="severity" value="error"/> + </module> + <!-- https://checkstyle.org/filters/suppressionxpathfilter.html --> + <module name="SuppressionXpathFilter"> + <property name="file" value="${org.checkstyle.google.suppressionxpathfilter.config}" + default="checkstyle-xpath-suppressions.xml" /> + <property name="optional" value="true"/> + </module> + <module name="SuppressWarningsHolder" /> + <module name="SuppressionCommentFilter"> + <property name="offCommentFormat" value="CHECKSTYLE.OFF\: ([\w\|]+)" /> + <property name="onCommentFormat" value="CHECKSTYLE.ON\: ([\w\|]+)" /> + <property name="checkFormat" value="$1" /> + </module> + <module name="SuppressWithNearbyCommentFilter"> + <property name="commentFormat" value="CHECKSTYLE.SUPPRESS\: ([\w\|]+)"/> + <!-- $1 refers to the first match group in the regex defined in commentFormat --> + <property name="checkFormat" value="$1"/> + <!-- The check is suppressed in the next line of code after the comment --> + <property name="influenceFormat" value="1"/> + </module> + </module> +</module> diff --git a/docs/Dockerfile b/docs/Dockerfile index 1524933ab..56c4c19ea 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,2 +1,4 @@ -FROM squidfunk/mkdocs-material -RUN pip install mkdocs-git-revision-date-plugin mkdocs-macros-plugin \ No newline at end of file +FROM squidfunk/mkdocs-material@sha256:980e11fed03b8e7851e579be9f34b1210f516c9f0b4da1a1457f21a460bd6628 + +COPY requirements.txt /tmp/ +RUN pip install --require-hashes -r /tmp/requirements.txt diff --git a/docs/FAQs.md b/docs/FAQs.md index 582590740..cea4b774f 100644 --- a/docs/FAQs.md +++ b/docs/FAQs.md @@ -3,16 +3,17 @@ title: FAQs description: Frequently Asked Questions --- +## How can I use Powertools for AWS Lambda (Java) with Lombok? -## How can I use Powertools with Lombok? +Many utilities in this library use `aspectj-maven-plugin` to compile-time weave (CTW) aspects into the project. In case you want to use `Lombok` or other compile-time preprocessor for your project, it is required to change `aspectj-maven-plugin` configuration to enable in-place weaving feature. Otherwise the plugin will ignore changes introduced by `Lombok` and will use `.java` files as a source. -Poweretools uses `aspectj-maven-plugin` to compile-time weave (CTW) aspects into the project. In case you want to use `Lombok` or other compile-time preprocessor for your project, it is required to change `aspectj-maven-plugin` configuration to enable in-place weaving feature. Otherwise the plugin will ignore changes introduced by `Lombok` and will use `.java` files as a source. +Alternatively, you can use the [functional approach](./usage-patterns.md#functional-approach) which does not require AspectJ configuration. To enable in-place weaving feature you need to use following `aspectj-maven-plugin` configuration: ```xml hl_lines="2-6" <configuration> - <forceAjcCompile>true</forceAjcCompile> + <forceAjcCompile>true</forceAjcCompile> <sources/> <weaveDirectories> <weaveDirectory>${project.build.directory}/classes</weaveDirectory> @@ -25,4 +26,236 @@ To enable in-place weaving feature you need to use following `aspectj-maven-plug </aspectLibrary> </aspectLibraries> </configuration> -``` \ No newline at end of file +``` + +## How can I use Powertools for AWS Lambda (Java) with Kotlin projects? + +Many utilities use `aspectj-maven-plugin` to compile-time weave (CTW) aspects into the project. When using it with Kotlin projects, it is required to `forceAjcCompile`. +No explicit configuration should be required for gradle projects. + +Alternatively, you can use the [functional approach](./usage-patterns.md#functional-approach) which does not require AspectJ configuration. + +To enable `forceAjcCompile` you need to use following `aspectj-maven-plugin` configuration: + +```xml hl_lines="2" +<configuration> + <forceAjcCompile>true</forceAjcCompile> + ... + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> +</configuration> +``` + +## How can I use Powertools for AWS Lambda (Java) with the AWS CRT HTTP Client? + +Utilities relying on AWS SDK clients use the `url-connection-client` as the default HTTP client. The `url-connection-client` is a lightweight HTTP client, which keeps the impact on Lambda cold starts to a minimum. +With the [announcement](https://aws.amazon.com/blogs/developer/announcing-availability-of-the-aws-crt-http-client-in-the-aws-sdk-for-java-2-x/) of the `aws-crt-client` a new HTTP client has been released, which offers faster SDK startup time and smaller memory footprint. + +Unfortunately, replacing the `url-connection-client` dependency with the `aws-crt-client` will not immediately improve the lambda cold start performance and memory footprint, +as the default version of the dependency contains native system libraries for all supported runtimes and architectures (Linux, MacOS, Windows, AMD64, ARM64, etc). This makes the CRT client portable, without the user having to consider _where_ their code will run, but comes at the cost of JAR size. + +### Configuring dependencies + +Using the `aws-crt-client` in your project requires the exclusion of the `url-connection-client` transitive dependency from the `powertools-*` dependency. + +```xml +<dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters</artifactId> + <version>2.0.0</version> + <exclusions> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>url-connection-client</artifactId> + </exclusion> + </exclusions> +</dependency> +``` + +Next, add the `aws-crt-client` and exclude the "generic" `aws-crt` dependency (contains all runtime libraries). +Instead, set a specific classifier of the `aws-crt` to use the one for your target runtime: either `linux-x86_64` for a Lambda configured for x86 or `linux-aarch_64` for Lambda using arm64. + +!!! note "You will need to add a separate maven profile to build and debug locally when your development environment does not share the target architecture you are using in Lambda." +By specifying the specific target runtime, we prevent other target runtimes from being included in the jar file, resulting in a smaller Lambda package and improved cold start times. + +```xml +<dependencies> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>aws-crt-client</artifactId> + <version>2.23.21</version> + <exclusions> + <exclusion> + <groupId>software.amazon.awssdk.crt</groupId> + <artifactId>aws-crt</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>software.amazon.awssdk.crt</groupId> + <artifactId>aws-crt</artifactId> + <version>0.29.9</version> + <classifier>linux-x86_64</classifier> + </dependency> +</dependencies> +``` + +### Explicitly set the AWS CRT HTTP Client + +After configuring the dependencies, it's required to explicitly specify the AWS SDK HTTP client. +Depending on the utility you are using, there is a different way to configure the SDK client. + +The following example shows how to use the Parameters module while leveraging the AWS CRT Client. + +```java hl_lines="16 23-24" +import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.awssdk.http.crt.AwsCrtHttpClient; +import software.amazon.lambda.powertools.parameters.ssm.SSMProvider; + +public class RequestHandlerWithParams implements RequestHandler<String, String> { + + // Get an instance of the SSMProvider with a custom HTTP client (aws crt). + SSMProvider ssmProvider = SSMProvider + .builder() + .withClient( + SsmClient.builder() + .httpClient(AwsCrtHttpClient.builder().build()) + .build() + ) + .build(); + + public String handleRequest(String input, Context context) { + // Retrieve a single param + String value = ssmProvider + .get("/my/secret"); + // We might instead want to retrieve multiple parameters at once, returning a Map of key/value pairs + // .getMultiple("/my/secret/path"); + + // Return the result + return value; + } +} +``` + +The `aws-crt-client` was considered for adoption as the default HTTP client in Powertools for AWS Lambda (Java) as mentioned in [Move SDK http client to CRT](https://github.com/aws-powertools/powertools-lambda-java/issues/1092), +but due to the impact on the developer experience it was decided to stick with the `url-connection-client`. + +## How can I use Powertools for AWS Lambda (Java) with GraalVM? + +Core utilities, i.e. [logging](./core/logging.md), [metrics](./core/metrics.md) and [tracing](./core/tracing.md), include the [GraalVM Reachability Metadata (GRM)](https://www.graalvm.org/latest/reference-manual/native-image/metadata/) in the `META-INF` directories of the respective JARs. You can find a working example of Serverless Application Model (SAM) based application in the [examples](../examples/powertools-examples-core-utilities/sam-graalvm/README.md) directory. + +Below, you find typical steps you need to follow in a Maven based Java project: + +### Set the environment to use GraalVM + +```shell +export JAVA_HOME=<path to GraalVM> +``` + +### Use log4j `>2.24.0` + +Log4j version `2.24.0` adds [support for GraalVM](https://github.com/apache/logging-log4j2/issues/1539#issuecomment-2106766878). Depending on your project's dependency hierarchy, older version of log4j might be included in the final dependency graph. Make sure version `>2.24.0` of these dependencies are used by your Maven project: + +```xml +<dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-api</artifactId> + <version>${log4j.version}</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + <version>${log4j.version}</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j2-impl</artifactId> + <version>${log4j.version}</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-layout-template-json</artifactId> + <version>${log4j.version}</version> + </dependency> +</dependencies> + +``` + +### Add the AWS Lambda Java Runtime Interface Client dependency + +The Runtime Interface Client allows your function to receive invocation events from Lambda, send the response back to Lambda, and report errors to the Lambda service. Add the below dependency to your Maven project: + +```xml +<dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + <version>2.1.1</version> +</dependency> +``` + +Also include the AWS Lambda GRM files by copying the `com.amazonaws` [directory](../examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/) in your project's `META-INF/native-image` directory + +### Build the native image + +Use the `native-maven-plugin` to build the native image. You can do this by adding the plugin to your `pom.xml` and creating a build profile called `native-image` that can build the native image of your Lambda function: + +```xml +<profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.10.1</version> + <extensions>true</extensions> + <executions> + <execution> + <id>build-native</id> + <goals> + <goal>build</goal> + </goals> + <phase>package</phase> + </execution> + </executions> + <configuration> + <imageName>your-project-name</imageName> + <mainClass>com.amazonaws.services.lambda.runtime.api.client.AWSLambda</mainClass> + <buildArgs> + <!-- required for AWS Lambda Runtime Interface Client --> + <arg>--enable-url-protocols=http</arg> + <arg>--add-opens java.base/java.util=ALL-UNNAMED</arg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> +</profiles> +``` + +Create a Docker image using a `Dockerfile` like [this](../examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile) to create an x86 based build image. + +```shell +docker build --platform linux/amd64 . -t your-org/your-app-graalvm-builder +``` + +Create the native image of you Lambda function using the Docker command below. + +```shell +docker run --platform linux/amd64 -it -v `pwd`:`pwd` -w `pwd` -v ~/.m2:/root/.m2 your-org/your-app-graalvm-builder mvn clean -Pnative-image package + +``` + +The native image is created in the `target/` directory. diff --git a/docs/core/logging.md b/docs/core/logging.md index f35c88067..8358087d2 100644 --- a/docs/core/logging.md +++ b/docs/core/logging.md @@ -5,25 +5,237 @@ description: Core utility Logging provides an opinionated logger with output structured as JSON. -**Key features** +## Key features + +* Leverages standard logging libraries: [_SLF4J_](https://www.slf4j.org/){target="_blank"} as the API, and [_log4j2_](https://logging.apache.org/log4j/2.x/){target="_blank"} or [_logback_](https://logback.qos.ch/){target="_blank"} for the implementation +* Captures key fields from Lambda context, cold start and structures logging output as JSON +* Optionally logs Lambda request +* Optionally logs Lambda response +* Optionally supports log sampling by including a configurable percentage of DEBUG logs in logging output +* Optionally supports buffering lower level logs and flushing them on error or manually +* Allows additional keys to be appended to the structured log at any point in time +* GraalVM support + + +## Getting started + +???+ tip + You can find complete examples in the [project repository](https://github.com/aws-powertools/powertools-lambda-java/tree/v2/examples/powertools-examples-core-utilities){target="_blank"}. + +### Installation +Depending on preference, you must choose to use either _log4j2_ or _logback_ as your log provider. If you use the AspectJ annotation approach, you must configure _aspectj_ to weave the code and make sure the annotation is processed. If you prefer the [functional approach](../usage-patterns.md#functional-approach), AspectJ configuration is not required. + +#### Maven +=== "log4j2" + + ```xml hl_lines="3-12 30-33" + <dependencies> + ... + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>{{ powertools.version }}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + <version>{{ powertools.version }}</version> + </dependency> + ... + </dependencies> + ... + <!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project --> + <!-- Note: This AspectJ configuration is not needed when using the functional approach --> + <build> + <plugins> + ... + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14</version> + <configuration> + <source>11</source> <!-- or higher --> + <target>11</target> <!-- or higher --> + <complianceLevel>11</complianceLevel> <!-- or higher --> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <!-- AspectJ compiler version, in sync with runtime --> + <version>1.9.22</version> + </dependency> + </dependencies> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + ... + </plugins> + </build> + ``` -* Capture key fields from Lambda context, cold start and structures logging output as JSON -* Log Lambda event when instructed, disabled by default, can be enabled explicitly via annotation param -* Append additional keys to structured log at any point in time +=== "logback" + + ```xml hl_lines="3-12 30-33" + <dependencies> + ... + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-logback</artifactId> + <version>{{ powertools.version }}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + <version>{{ powertools.version }}</version> + </dependency> + ... + </dependencies> + ... + <!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project --> + <!-- Note: This AspectJ configuration is not needed when using the functional approach --> + <build> + <plugins> + ... + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14</version> + <configuration> + <source>11</source> <!-- or higher --> + <target>11</target> <!-- or higher --> + <complianceLevel>11</complianceLevel> <!-- or higher --> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <!-- AspectJ compiler version, in sync with runtime --> + <version>1.9.22</version> + </dependency> + </dependencies> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + ... + </plugins> + </build> + ``` -## Initialization +#### Gradle -Powertools extends the functionality of Log4J. Below is an example `#!xml log4j2.xml` file, with the `JsonTemplateLayout` using `#!json LambdaJsonLayout.json` configured. +=== "log4j2" -!!! info "LambdaJsonLayout is now deprecated" + ```groovy hl_lines="3 11-12" + plugins { + id 'java' + id 'io.freefair.aspectj.post-compile-weaving' version '8.1.0' // Not needed when using the functional approach + } + + repositories { + mavenCentral() + } + + dependencies { + aspect 'software.amazon.lambda:powertools-logging:{{ powertools.version }}' // Not needed when using the functional approach + implementation 'software.amazon.lambda:powertools-logging-log4j:{{ powertools.version }}' + } + + sourceCompatibility = 11 + targetCompatibility = 11 + ``` - Configuring utiltiy using `<LambdaJsonLayout/>` plugin is deprecated now. While utility still supports the old configuration, we strongly recommend upgrading the - `log4j2.xml` configuration to `JsonTemplateLayout` instead. [JsonTemplateLayout](https://logging.apache.org/log4j/2.x/manual/json-template-layout.html) is recommended way of doing structured logging. - - Please follow [this guide](#upgrade-to-jsontemplatelayout-from-deprecated-lambdajsonlayout-configuration-in-log4j2xml) for upgrade steps. +=== "logback" + + ```groovy hl_lines="3 11-12" + plugins { + id 'java' + id 'io.freefair.aspectj.post-compile-weaving' version '8.1.0' // Not needed when using the functional approach + } + + repositories { + mavenCentral() + } + + dependencies { + aspect 'software.amazon.lambda:powertools-logging:{{ powertools.version }}' // Not needed when using the functional approach + implementation 'software.amazon.lambda:powertools-logging-logback:{{ powertools.version }}' + } + + sourceCompatibility = 11 + targetCompatibility = 11 + ``` + + +### Configuration + +#### Main environment variables + +The logging module requires two settings: + +| Environment variable | Setting | Description | +|---------------------------|-------------------|-------------------------------------------------------------------------------------------------------------| +| `POWERTOOLS_LOG_LEVEL` | **Logging level** | Sets how verbose Logger should be. If not set, will use the [Logging configuration](#logging-configuration) | +| `POWERTOOLS_SERVICE_NAME` | **Service** | Sets service key that will be included in all log statements (Default is `service_undefined`) | + +Here is an example using AWS Serverless Application Model (SAM): + +=== "template.yaml" +``` yaml hl_lines="10 11" +Resources: + PaymentFunction: + Type: AWS::Serverless::Function + Properties: + MemorySize: 512 + Timeout: 20 + Runtime: java17 + Environment: + Variables: + POWERTOOLS_LOG_LEVEL: WARN + POWERTOOLS_SERVICE_NAME: payment +``` + +There are some other environment variables which can be set to modify Logging's settings at a global scope: + +| Environment variable | Type | Description | +|---------------------------------|----------|-------------------------------------------------------------------------------------------------------------------------| +| `POWERTOOLS_LOGGER_SAMPLE_RATE` | float | Configure the sampling rate at which `DEBUG` logs should be included. See [sampling rate](#sampling-debug-logs) | +| `POWERTOOLS_LOGGER_LOG_EVENT` | boolean | Specify if the incoming Lambda event should be logged. See [Logging event](#logging-incoming-event) | +| `POWERTOOLS_LOGGER_LOG_RESPONSE` | boolean | Specify if the Lambda response should be logged. See [logging response](#logging-handler-response) | +| `POWERTOOLS_LOGGER_LOG_ERROR` | boolean | Specify if a Lambda uncaught exception should be logged. See [logging exception](#logging-handler-uncaught-exception ) | + +#### Logging configuration + +Powertools for AWS Lambda (Java) simply extends the functionality of the underlying library you choose (_log4j2_ or _logback_). +You can leverage the standard configuration files (_log4j2.xml_ or _logback.xml_): === "log4j2.xml" + With log4j2, we leverage the [`JsonTemplateLayout`](https://logging.apache.org/log4j/2.x/manual/json-template-layout.html){target="_blank"} + to provide structured logging. A default template is provided in powertools ([_LambdaJsonLayout.json_](https://github.com/aws-powertools/powertools-lambda-java/blob/4444b4bce8eb1cc19880d1c1ef07188d97de9126/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json){target="_blank"}): + ```xml hl_lines="5" <?xml version="1.0" encoding="UTF-8"?> <Configuration> @@ -33,7 +245,7 @@ Powertools extends the functionality of Log4J. Below is an example `#!xml log4j2 </Console> </Appenders> <Loggers> - <Logger name="JsonLogger" level="INFO" additivity="false"> + <Logger name="com.example" level="debug" additivity="false"> <AppenderRef ref="JsonAppender"/> </Logger> <Root level="info"> @@ -43,154 +255,201 @@ Powertools extends the functionality of Log4J. Below is an example `#!xml log4j2 </Configuration> ``` -You can also override log level by setting **`POWERTOOLS_LOG_LEVEL`** env var. Here is an example using AWS Serverless Application Model (SAM) +=== "logback.xml" -=== "template.yaml" - ``` yaml hl_lines="9 10" - Resources: - HelloWorldFunction: - Type: AWS::Serverless::Function - Properties: - ... - Runtime: java8 - Environment: - Variables: - POWERTOOLS_LOG_LEVEL: DEBUG - POWERTOOLS_SERVICE_NAME: example + With logback, we leverage a custom [Encoder](https://logback.qos.ch/manual/encoders.html){target="_blank"} + to provide structured logging: + + ```xml hl_lines="4 5" + <?xml version="1.0" encoding="UTF-8"?> + <configuration> + <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + </encoder> + </appender> + <logger name="com.example" level="DEBUG" additivity="false"> + <appender-ref ref="console" /> + </logger> + <root level="INFO"> + <appender-ref ref="console" /> + </root> + </configuration> ``` -You can also explicitly set a service name via **`POWERTOOLS_SERVICE_NAME`** env var. This sets **service** key that will be present across all log statements. +## Log level +Log level is generally configured in the `log4j2.xml` or `logback.xml`. But this level is static and needs a redeployment of the function to be changed. +Powertools for AWS Lambda permits to change this level dynamically thanks to an environment variable `POWERTOOLS_LOG_LEVEL`. -## Standard structured keys +We support the following log levels (SLF4J levels): `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`. +If the level is set to `CRITICAL` (supported in log4j but not logback), we revert it back to `ERROR`. +If the level is set to any other value, we set it to the default value (`INFO`). -Your logs will always include the following keys to your structured logging: +### AWS Lambda Advanced Logging Controls (ALC) -Key | Type | Example | Description -------------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------- -**timestamp** | String | "2020-05-24 18:17:33,774" | Timestamp of actual log statement -**level** | String | "INFO" | Logging level -**coldStart** | Boolean | true| ColdStart value. -**service** | String | "payment" | Service name defined. "service_undefined" will be used if unknown -**samplingRate** | int | 0.1 | Debug logging sampling rate in percentage e.g. 10% in this case -**message** | String | "Collecting payment" | Log statement value. Unserializable JSON values will be casted to string -**functionName**| String | "example-powertools-HelloWorldFunction-1P1Z6B39FLU73" -**functionVersion**| String | "12" -**functionMemorySize**| String | "128" -**functionArn**| String | "arn:aws:lambda:eu-west-1:012345678910:function:example-powertools-HelloWorldFunction-1P1Z6B39FLU73" -**xray_trace_id**| String | "1-5759e988-bd862e3fe1be46a994272793" | X-Ray Trace ID when Lambda function has enabled Tracing -**function_request_id**| String | "899856cb-83d1-40d7-8611-9e78f15f32f4"" | AWS Request ID from lambda context +!!!question "When is it useful?" + When you want to set a logging policy to drop informational or verbose logs for one or all AWS Lambda functions, regardless of runtime and logger used. -## Capturing context Lambda info +<!-- markdownlint-disable MD013 --> +With [AWS Lambda Advanced Logging Controls (ALC)](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-advanced){target="_blank"}, you can enforce a minimum log level that Lambda will accept from your application code. -You can enrich your structured logs with key Lambda context information via `logEvent` annotation parameter. -You can also explicitly log any incoming event using `logEvent` param. Refer [Override default object mapper](#override-default-object-mapper) -to customise what is logged. +When enabled, you should keep your own log level and ALC log level in sync to avoid data loss. -!!! warning - Log event is disabled by default to prevent sensitive info being logged. +Here's a sequence diagram to demonstrate how ALC will drop both `INFO` and `DEBUG` logs emitted from `Logger`, when ALC log level is stricter than `Logger`. +<!-- markdownlint-enable MD013 --> +```mermaid +sequenceDiagram + participant Lambda service + participant Lambda function + participant Application Logger -=== "App.java" + Note over Lambda service: AWS_LAMBDA_LOG_LEVEL="WARN" + Note over Application Logger: POWERTOOLS_LOG_LEVEL="DEBUG" - ```java hl_lines="14" - import org.apache.logging.log4j.LogManager; - import org.apache.logging.log4j.Logger; - import software.amazon.lambda.powertools.logging.LoggingUtils; + Lambda service->>Lambda function: Invoke (event) + Lambda function->>Lambda function: Calls handler + Lambda function->>Application Logger: logger.error("Something happened") + Lambda function-->>Application Logger: logger.debug("Something happened") + Lambda function-->>Application Logger: logger.info("Something happened") + Lambda service--xLambda service: DROP INFO and DEBUG logs + Lambda service->>CloudWatch Logs: Ingest error logs +``` + +### Priority of log level settings in Powertools for AWS Lambda + +We prioritise log level settings in this order: + +1. `AWS_LAMBDA_LOG_LEVEL` environment variable +2. `POWERTOOLS_LOG_LEVEL` environment variable +3. level defined in the `log4j2.xml` or `logback.xml` files + +If you set `POWERTOOLS_LOG_LEVEL` lower than ALC, we will emit a warning informing you that your messages will be discarded by Lambda. + +!!! note + With ALC enabled, we are unable to increase the minimum log level below the `AWS_LAMBDA_LOG_LEVEL` environment variable value, see [AWS Lambda service documentation](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-log-level){target="_blank"} for more details. + +## Basic Usage + +You can use Powertools for AWS Lambda Logging with either the `@Logging` annotation or the functional API: + +=== "@Logging annotation" + + ```java hl_lines="8 10 12 14" + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; - ... - - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - - Logger log = LogManager.getLogger(); + // ... other imports + + public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentFunction.class); + @Logging public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... + LOGGER.info("Collecting payment"); + // ... + LOGGER.debug("order={}, amount={}", order.getId(), order.getAmount()); + // ... } } ``` -=== "AppLogEvent.java" - - ```java hl_lines="8" - /** - * Handler for requests to Lambda function. - */ - public class AppLogEvent implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { +=== "Functional API" + + ```java hl_lines="8 11 12 14 17" + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.logging.PowertoolsLogging; + // ... other imports + + public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - Logger log = LogManager.getLogger(); + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentFunction.class); - @Logging(logEvent = true) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... + return PowertoolsLogging.withLogging(context, () -> { + LOGGER.info("Collecting payment"); + // ... + LOGGER.debug("order={}, amount={}", order.getId(), order.getAmount()); + // ... + return new APIGatewayProxyResponseEvent().withStatusCode(200); + }); } } ``` -### Customising fields in logs +## Standard structured keys -- Utility by default emits `timestamp` field in the logs in format `yyyy-MM-dd'T'HH:mm:ss.SSSZz` and in system default timezone. -If you need to customize format and timezone, you can do so by configuring `log4j2.component.properties` and configuring properties as shown in example below: +Your logs will always include the following keys in your structured logging: -=== "log4j2.component.properties" +| Key | Type | Example | Description | +|-------------------|--------|-----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| +| **timestamp** | String | "2023-12-01T14:49:19.293Z" | Timestamp of actual log statement, by default uses default AWS Lambda timezone (UTC) | +| **level** | String | "INFO" | Logging level (any level supported by _SLF4J_ (i.e. `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`) | +| **service** | String | "payment" | Service name defined, by default `service_undefined` | +| **sampling_rate** | float | 0.1 | Debug logging sampling rate in percentage e.g. 10% in this case (logged if not 0) | +| **message** | String | "Collecting payment" | Log statement value. Unserializable JSON values will be casted to string | +| **xray_trace_id** | String | "1-5759e988-bd862e3fe1be46a994272793" | X-Ray Trace ID when [Tracing is enabled](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html){target="_blank"} | +| **error** | Map | `{ "name": "InvalidAmountException", "message": "Amount must be superior to 0", "stack": "at..." }` | Eventual exception (e.g. when doing `logger.error("Error", new InvalidAmountException("Amount must be superior to 0"));`) | - ```properties hl_lines="1 2" - log4j.layout.jsonTemplate.timestampFormatPattern=yyyy-MM-dd'T'HH:mm:ss.SSSZz - log4j.layout.jsonTemplate.timeZone=Europe/Oslo - ``` +???+ note + If you emit a log message with a key that matches one of the [standard structured keys](#standard-structured-keys) or one of the [additional structured keys](#additional-structured-keys), the Logger will log a warning message and ignore the key. -- Utility also provides sample template for [Elastic Common Schema(ECS)](https://www.elastic.co/guide/en/ecs/current/ecs-reference.html) layout. -The field emitted in logs will follow specs from [ECS](https://www.elastic.co/guide/en/ecs/current/ecs-reference.html) together with field captured by utility as mentioned [above](#standard-structured-keys). +## Additional structured keys - Use `LambdaEcsLayout.json` as `eventTemplateUri` when configuring `JsonTemplateLayout`. +### Logging Lambda context information +The following keys will also be added to all your structured logs (unless [configured otherwise](#more-customization_1)): -=== "log4j2.xml" +| Key | Type | Example | Description | +|--------------------------|---------|----------------------------------------------------------------------------------------|------------------------------------| +| **cold_start** | Boolean | false | ColdStart value | +| **function_name** | String | "example-PaymentFunction-1P1Z6B39FLU73" | Name of the function | +| **function_version** | String | "12" | Version of the function | +| **function_memory_size** | String | "512" | Memory configure for the function | +| **function_arn** | String | "arn:aws:lambda:eu-west-1:012345678910:function:example-PaymentFunction-1P1Z6B39FLU73" | ARN of the function | +| **function_request_id** | String | "899856cb-83d1-40d7-8611-9e78f15f32f4"" | AWS Request ID from lambda context | - ```xml hl_lines="5" - <?xml version="1.0" encoding="UTF-8"?> - <Configuration> - <Appenders> - <Console name="JsonAppender" target="SYSTEM_OUT"> - <JsonTemplateLayout eventTemplateUri="classpath:LambdaEcsLayout.json" /> - </Console> - </Appenders> - <Loggers> - <Logger name="JsonLogger" level="INFO" additivity="false"> - <AppenderRef ref="JsonAppender"/> - </Logger> - <Root level="info"> - <AppenderRef ref="JsonAppender"/> - </Root> - </Loggers> - </Configuration> - ``` +### Logging additional keys -## Setting a Correlation ID +#### Logging a correlation ID -You can set a Correlation ID using `correlationIdPath` attribute by passing a [JSON Pointer expression](https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03){target="_blank"}. +You can set a correlation ID using the `correlationIdPath` parameter by passing a [JMESPath expression](https://jmespath.org/tutorial.html){target="_blank"}, +including our custom [JMESPath Functions](../utilities/serialization.md#built-in-functions). -=== "App.java" +=== "@Logging annotation" - ```java hl_lines="8" - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + ```java hl_lines="5" + public class AppCorrelationIdPath implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - Logger log = LogManager.getLogger(); + private static final Logger LOGGER = LoggerFactory.getLogger(AppCorrelationIdPath.class); - @Logging(correlationIdPath = "/headers/my_request_id_header") + @Logging(correlationIdPath = "headers.my_request_id_header") public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... - log.info("Collecting payment") - ... + // ... + LOGGER.info("Collecting payment") + // ... } } ``` -=== "Example Event" + +=== "Functional API" + + ```java hl_lines="6" + public class AppCorrelationIdPath implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppCorrelationIdPath.class); + + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + return PowertoolsLogging.withLogging(context, "headers.my_request_id_header", input, () -> { + // ... + LOGGER.info("Collecting payment"); + // ... + return new APIGatewayProxyResponseEvent().withStatusCode(200); + }); + } + } + ``` +=== "Example HTTP Event" ```json hl_lines="3" { @@ -200,42 +459,57 @@ You can set a Correlation ID using `correlationIdPath` attribute by passing a [J } ``` -=== "Example CloudWatch Logs excerpt" +=== "CloudWatch Logs" - ```json hl_lines="11" + ```json hl_lines="6" { "level": "INFO", "message": "Collecting payment", - "timestamp": "2021-05-03 11:47:12,494+0200", + "timestamp": "2023-12-01T14:49:19.293Z", "service": "payment", - "coldStart": true, - "functionName": "test", - "functionMemorySize": 128, - "functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test", - "lambda_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72", "correlation_id": "correlation_id_value" } ``` -We provide [built-in JSON Pointer expression](https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03){target="_blank"} -for known event sources, where either a request ID or X-Ray Trace ID are present. + +**Known correlation IDs** -=== "App.java" +To ease routine tasks like extracting correlation ID from popular event sources, +we provide [built-in JMESPath expressions](#built-in-correlation-id-expressions). - ```java hl_lines="10" - import software.amazon.lambda.powertools.logging.CorrelationIdPathConstants; +=== "@Logging annotation" - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + ```java hl_lines="1 7" + import software.amazon.lambda.powertools.logging.CorrelationIdPaths; + + public class AppCorrelationId implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - Logger log = LogManager.getLogger(); + private static final Logger LOGGER = LoggerFactory.getLogger(AppCorrelationId.class); - @Logging(correlationIdPath = CorrelationIdPathConstants.API_GATEWAY_REST) + @Logging(correlationIdPath = CorrelationIdPaths.API_GATEWAY_REST) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... - log.info("Collecting payment") - ... + // ... + LOGGER.info("Collecting payment") + // ... + } + } + ``` + +=== "Functional API" + + ```java hl_lines="1 8" + import software.amazon.lambda.powertools.logging.CorrelationIdPaths; + + public class AppCorrelationId implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppCorrelationId.class); + + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + return PowertoolsLogging.withLogging(context, CorrelationIdPaths.API_GATEWAY_REST, input, () -> { + // ... + LOGGER.info("Collecting payment"); + // ... + return new APIGatewayProxyResponseEvent().withStatusCode(200); + }); } } ``` @@ -244,268 +518,1097 @@ for known event sources, where either a request ID or X-Ray Trace ID are present ```json hl_lines="3" { - "requestContext": { - "requestId": "correlation_id_value" - } + "requestContext": { + "requestId": "correlation_id_value" + } } ``` -=== "Example CloudWatch Logs excerpt" +=== "Example CloudWatch Logs" - ```json hl_lines="11" + ```json hl_lines="6" { "level": "INFO", "message": "Collecting payment", - "timestamp": "2021-05-03 11:47:12,494+0200", + "timestamp": "2023-12-01T14:49:19.293Z", "service": "payment", - "coldStart": true, - "functionName": "test", - "functionMemorySize": 128, - "functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test", - "lambda_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72", "correlation_id": "correlation_id_value" } ``` - -## Appending additional keys -!!! info "Custom keys are persisted across warm invocations" - Always set additional keys as part of your handler to ensure they have the latest value, or explicitly clear them with [`clearState=true`](#clearing-all-state). +#### Custom keys -You can append your own keys to your existing logs via `appendKey`. +**Using StructuredArguments** -=== "App.java" +To append additional keys in your logs, you can use the `StructuredArguments` class: - ```java hl_lines="11 19" - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { +=== "PaymentFunction.java" + + ```java hl_lines="1 2 11 17" + import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.entry; + import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.entries; + + public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - Logger log = LogManager.getLogger(); + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogResponse.class); - @Logging(logEvent = true) + @Logging public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... - LoggingUtils.appendKey("test", "willBeLogged"); - ... - - ... - Map<String, String> customKeys = new HashMap<>(); - customKeys.put("test", "value"); - customKeys.put("test1", "value1"); + // ... + LOGGER.info("Collecting payment", entry("orderId", order.getId())); - LoggingUtils.appendKeys(customKeys); - ... + // ... + Map<String, String> customKeys = new HashMap<>(); + customKeys.put("paymentId", payment.getId()); + customKeys.put("amount", payment.getAmount); + LOGGER.info("Payment successful", entries(customKeys)); } } ``` +=== "CloudWatch Logs for PaymentFunction" -### Removing additional keys + ```json hl_lines="7 16-18" + { + "level": "INFO", + "message": "Collecting payment", + "service": "payment", + "timestamp": "2023-12-01T14:49:19.293Z", + "xray_trace_id": "1-6569f266-4b0c7f97280dcd8428d3c9b5", + "orderId": "41376" + } + ... + { + "level": "INFO", + "message": "Payment successful", + "service": "payment", + "timestamp": "2023-12-01T14:49:20.118Z", + "xray_trace_id": "1-6569f266-4b0c7f97280dcd8428d3c9b5", + "orderId": "41376", + "paymentId": "3245", + "amount": 345.99 + } + ``` -You can remove any additional key from entry using `LoggingUtils.removeKeys()`. +`StructuredArguments` provides several options: -=== "App.java" + - `entry` to add one key and value into the log structure. Note that value can be any object type. + - `entries` to add multiple keys and values (from a Map) into the log structure. Note that values can be any object type. + - `json` to add a key and raw json (string) as value into the log structure. + - `array` to add one key and multiple values into the log structure. Note that values can be any object type. - ```java hl_lines="19 20" - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { +=== "OrderFunction.java" + + ```java hl_lines="1 2 11 17" + import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.entry; + import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.array; + + public class OrderFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - Logger log = LogManager.getLogger(); + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogResponse.class); - @Logging(logEvent = true) + @Logging public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... - LoggingUtils.appendKey("test", "willBeLogged"); - ... - Map<String, String> customKeys = new HashMap<>(); - customKeys.put("test1", "value"); - customKeys.put("test2", "value1"); - - LoggingUtils.appendKeys(customKeys); - ... - LoggingUtils.removeKey("test"); - LoggingUtils.removeKeys("test1", "test2"); - ... + // ... + LOGGER.info("Processing order", entry("order", order), array("products", productList)); + // ... } } ``` -### Clearing all state +=== "CloudWatch Logs for OrderFunction" + + ```json hl_lines="7 13" + { + "level": "INFO", + "message": "Processing order", + "service": "payment", + "timestamp": "2023-12-01T14:49:19.293Z", + "xray_trace_id": "1-6569f266-4b0c7f97280dcd8428d3c9b5", + "order": { + "orderId": 23542, + "amount": 459.99, + "date": "2023-12-01T14:49:19.018Z", + "customerId": 328496 + }, + "products": [ + { + "productId": 764330, + "name": "product1", + "quantity": 1, + "price": 300 + }, + { + "productId": 798034, + "name": "product42", + "quantity": 1, + "price": 159.99 + } + ] + } + ``` + +???+ tip "Use arguments without log placeholders" + As shown in the example above, you can use arguments (with `StructuredArguments`) without placeholders (`{}`) in the message. + If you add the placeholders, the arguments will be logged both as an additional field and also as a string in the log message, using the `toString()` method. + + === "Function1.java" + + ```java + LOGGER.info("Processing {}", entry("order", order)); + ``` + + === "Order.java" + + ```java hl_lines="5" + public class Order { + // ... + + @Override + public String toString() { + return "Order{" + + "orderId=" + id + + ", amount=" + amount + + ", date='" + date + '\'' + + ", customerId=" + customerId + + '}'; + } + } + ``` + + === "CloudWatch Logs Function1" + + ```json hl_lines="3 7" + { + "level": "INFO", + "message": "Processing order=Order{orderId=23542, amount=459.99, date='2023-12-01T14:49:19.018Z', customerId=328496}", + "service": "payment", + "timestamp": "2023-12-01T14:49:19.293Z", + "xray_trace_id": "1-6569f266-4b0c7f97280dcd8428d3c9b5", + "order": { + "orderId": 23542, + "amount": 459.99, + "date": "2023-12-01T14:49:19.018Z", + "customerId": 328496 + } + } + ``` + + You can also combine structured arguments with non structured ones. For example: + + === "Function2.java" + ```java + LOGGER.info("Processing order {}", order.getOrderId(), entry("order", order)); + ``` + + === "CloudWatch Logs Function2" + ```json + { + "level": "INFO", + "message": "Processing order 23542", + "service": "payment", + "timestamp": "2023-12-01T14:49:19.293Z", + "xray_trace_id": "1-6569f266-4b0c7f97280dcd8428d3c9b5", + "order": { + "orderId": 23542, + "amount": 459.99, + "date": "2023-12-01T14:49:19.018Z", + "customerId": 328496 + } + } + ``` + +???+ warning "Do not use reserved keys in `StructuredArguments`" + If the key name of your structured argument matches any of the [standard structured keys](#standard-structured-keys) or any of the [additional structured keys](#additional-structured-keys), the Logger will log a warning message and ignore the key. This is to protect you from accidentally overwriting reserved keys such as the log level or Lambda context information. + +**Using MDC** -Logger is commonly initialized in the global scope. Due to [Lambda Execution Context reuse](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html), -this means that custom keys can be persisted across invocations. If you want all custom keys to be deleted, you can use -`clearState=true` attribute on `@Logging` annotation. +Mapped Diagnostic Context (MDC) is essentially a Key-Value store. It is supported by the [SLF4J API](https://www.slf4j.org/manual.html#mdc){target="_blank"}, +[logback](https://logback.qos.ch/manual/mdc.html){target="_blank"} and log4j (known as [ThreadContext](https://logging.apache.org/log4j/2.x/manual/thread-context.html){target="_blank"}). You can use the following standard: +`MDC.put("key", "value");` -=== "App.java" +???+ warning "Custom keys stored in the MDC are persisted across warm invocations" + Always set additional keys as part of your handler method to ensure they have the latest value, or explicitly clear them with [`clearState=true`](#clearing-state). - ```java hl_lines="8 12" - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { +???+ warning "Do not add reserved keys to MDC" + Avoid adding any of the keys listed in [standard structured keys](#standard-structured-keys) and [additional structured keys](#additional-structured-keys) to your MDC. This may cause unindented behavior and will overwrite the context set by the Logger. Unlike with `StructuredArguments`, the Logger will **not** ignore reserved keys set via MDC. + + +### Removing additional keys + +You can remove additional keys added with the MDC using `MDC.remove("key")`. + +#### Clearing state + +Logger is commonly initialized in the global scope. Due to [Lambda Execution Context reuse](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html){target="_blank"}, +this means that custom keys, added with the MDC can be persisted across invocations. You can clear state using `clearState=true` on the `@Logging` annotation, or use the functional API which handles cleanup automatically. + +=== "@Logging annotation" + + ```java hl_lines="5 8" + public class CreditCardFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - Logger log = LogManager.getLogger(); + private static final Logger LOGGER = LoggerFactory.getLogger(CreditCardFunction.class); @Logging(clearState = true) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... - if(input.getHeaders().get("someSpecialHeader")) { - LoggingUtils.appendKey("specialKey", "value"); - } - - log.info("Collecting payment"); - ... + // ... + MDC.put("cardNumber", card.getId()); + LOGGER.info("Updating card information"); + // ... } } ``` + === "#1 Request" - ```json hl_lines="11" - { - "level": "INFO", - "message": "Collecting payment", - "timestamp": "2021-05-03 11:47:12,494+0200", - "service": "payment", - "coldStart": true, - "functionName": "test", - "functionMemorySize": 128, - "functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test", - "lambda_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72", - "specialKey": "value" - } + ```json hl_lines="7" + { + "level": "INFO", + "message": "Updating card information", + "service": "card", + "timestamp": "2023-12-01T14:49:19.293Z", + "xray_trace_id": "1-6569f266-4b0c7f97280dcd8428d3c9b5", + "cardNumber": "6818 8419 9395 5322" + } ``` === "#2 Request" - ```json - { - "level": "INFO", - "message": "Collecting payment", - "timestamp": "2021-05-03 11:47:12,494+0200", - "service": "payment", - "coldStart": true, - "functionName": "test", - "functionMemorySize": 128, - "functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test", - "lambda_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72" - } + ```json hl_lines="7" + { + "level": "INFO", + "message": "Updating card information", + "service": "card", + "timestamp": "2023-12-01T14:49:20.213Z", + "xray_trace_id": "2-7a518f43-5e9d2b1f6cfd5e8b3a4e1f9c", + "cardNumber": "7201 6897 6685 3285" + } ``` -## Override default object mapper +`clearState` is based on `MDC.clear()`. State clearing is automatically done at the end of the execution of the handler if set to `true`. -You can optionally choose to override default object mapper which is used to serialize lambda function events. You might -want to supply custom object mapper in order to control how serialisation is done, for example, when you want to log only -specific fields from received event due to security. +???+ tip + When using the functional API with `PowertoolsLogging.withLogging()`, state is automatically cleared at the end of execution, so you don't need to manage it manually. -=== "App.java" - ```java hl_lines="9 10" - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - - Logger log = LogManager.getLogger(); +## Logging incoming event - static { - ObjectMapper objectMapper = new ObjectMapper(); - LoggingUtils.defaultObjectMapper(objectMapper); - } +When debugging in non-production environments, you can log the incoming event using the `@Logging` annotation with the `logEvent` parameter, via the `POWERTOOLS_LOGGER_LOG_EVENT` environment variable, or manually with the functional API. + +???+ warning + This is disabled by default to prevent sensitive info being logged. + +=== "@Logging annotation" + + ```java hl_lines="5" + public class AppLogEvent implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogEvent.class); + @Logging(logEvent = true) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... + // ... } } ``` -## Sampling debug logs +=== "Functional API" -You can dynamically set a percentage of your logs to **DEBUG** level via env var `POWERTOOLS_LOGGER_SAMPLE_RATE` or -via `samplingRate` attribute on annotation. + ```java hl_lines="1 9" + import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.entry; -!!! info - Configuration on environment variable is given precedence over sampling rate configuration on annotation, provided it's in valid value range. + public class AppLogEvent implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogEvent.class); + + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + return PowertoolsLogging.withLogging(context, () -> { + LOGGER.info("Handler Event", entry("event", input)); + // ... + return new APIGatewayProxyResponseEvent().withStatusCode(200); + }); + } + } + ``` -=== "Sampling via annotation attribute" +???+ note + If you use this on a RequestStreamHandler, the SDK must duplicate input streams in order to log them when used together with the `@Logging` annotation. - ```java hl_lines="8" - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { +## Logging handler response + +When debugging in non-production environments, you can log the response using the `@Logging` annotation with the `logResponse` parameter, via the `POWERTOOLS_LOGGER_LOG_RESPONSE` environment variable, or manually with the functional API. + +???+ warning + This is disabled by default to prevent sensitive info being logged. + +=== "@Logging annotation" + + ```java hl_lines="5" + public class AppLogResponse implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - Logger log = LogManager.getLogger(); + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogResponse.class); + + @Logging(logResponse = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + // ... + } + } + ``` + +=== "Functional API" + + ```java hl_lines="1 11" + import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.entry; + + public class AppLogResponse implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - @Logging(samplingRate = 0.5) + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogResponse.class); + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... + return PowertoolsLogging.withLogging(context, () -> { + // ... + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent().withStatusCode(200); + LOGGER.info("Handler Response", entry("response", response)); + return response; + }); } } ``` -=== "Sampling via environment variable" +???+ note + If you use this on a RequestStreamHandler, Powertools must duplicate output streams in order to log them when used together with the `@Logging` annotation. - ```yaml hl_lines="9" - Resources: - HelloWorldFunction: - Type: AWS::Serverless::Function - Properties: - ... - Runtime: java8 - Environment: - Variables: - POWERTOOLS_LOGGER_SAMPLE_RATE: 0.5 +## Logging handler uncaught exception +By default, AWS Lambda logs any uncaught exception that might happen in the handler. However, this log is not structured +and does not contain any additional context. When using the `@Logging` annotation, you can enable structured exception logging +with `logError` param or via `POWERTOOLS_LOGGER_LOG_ERROR` env var. + +???+ warning + This is disabled by default to prevent double logging. + +???+ note + This feature is only available when using the `@Logging` annotation. When using the functional API, you must catch and log exceptions manually using try-catch blocks. + +=== "@Logging annotation" + + ```java hl_lines="5" + public class AppLogError implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogError.class); + + @Logging(logError = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + // ... + } + } + ``` + +=== "Functional API" + + ```java hl_lines="1 9 12-13" + import org.slf4j.MarkerFactory; + + public class AppLogError implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogError.class); + + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + return PowertoolsLogging.withLogging(context, () -> { + try { + // ... + return new APIGatewayProxyResponseEvent().withStatusCode(200); + } catch (Exception e) { + LOGGER.error(MarkerFactory.getMarker("FATAL"), "Exception in Lambda Handler", e); + throw e; + } + }); + } + } ``` +## Advanced -## Upgrade to JsonTemplateLayout from deprecated LambdaJsonLayout configuration in log4j2.xml +### Buffering logs -Prior to version [1.10.0](https://github.com/awslabs/aws-lambda-powertools-java/releases/tag/v1.10.0), only supported way of configuring `log4j2.xml` was via `<LambdaJsonLayout/>`. This plugin is -deprecated now and will be removed in future version. Switching to `JsonTemplateLayout` is straight forward. +Log buffering enables you to buffer logs for a specific request or invocation. Enable log buffering by configuring the `BufferingAppender` in your logging configuration. You can buffer logs at the `WARNING`, `INFO` or `DEBUG` level, and flush them automatically on error or manually as needed. -Below examples shows deprecated and new configuration of `log4j2.xml`. +!!! tip "This is useful when you want to reduce the number of log messages emitted while still having detailed logs when needed, such as when troubleshooting issues." -=== "Deprecated configuration of log4j2.xml" +=== "log4j2.xml" - ```xml hl_lines="5" + ```xml hl_lines="7-12 16 19" <?xml version="1.0" encoding="UTF-8"?> <Configuration> <Appenders> <Console name="JsonAppender" target="SYSTEM_OUT"> - <LambdaJsonLayout compact="true" eventEol="true"/> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> </Console> + <BufferingAppender name="BufferedJsonAppender" + maxBytes="20480" + bufferAtVerbosity="DEBUG" + flushOnErrorLog="true"> + <AppenderRef ref="JsonAppender"/> + </BufferingAppender> </Appenders> <Loggers> - <Logger name="JsonLogger" level="INFO" additivity="false"> - <AppenderRef ref="JsonAppender"/> + <Logger name="com.example" level="debug" additivity="false"> + <AppenderRef ref="BufferedJsonAppender"/> </Logger> - <Root level="info"> - <AppenderRef ref="JsonAppender"/> + <Root level="debug"> + <AppenderRef ref="BufferedJsonAppender"/> </Root> </Loggers> </Configuration> ``` -=== "New configuration of log4j2.xml" +=== "logback.xml" - ```xml hl_lines="5" + ```xml hl_lines="6-11 13 16" + <?xml version="1.0" encoding="UTF-8"?> + <configuration> + <appender name="JsonAppender" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder" /> + </appender> + <appender name="BufferedJsonAppender" class="software.amazon.lambda.powertools.logging.logback.BufferingAppender"> + <maxBytes>20480</maxBytes> + <bufferAtVerbosity>DEBUG</bufferAtVerbosity> + <flushOnErrorLog>true</flushOnErrorLog> + <appender-ref ref="JsonAppender" /> + </appender> + <logger name="com.example" level="DEBUG" additivity="false"> + <appender-ref ref="BufferedJsonAppender" /> + </logger> + <root level="DEBUG"> + <appender-ref ref="BufferedJsonAppender" /> + </root> + </configuration> + ``` + +=== "PaymentFunction.java" + + ```java hl_lines="8 12" + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.logging.Logging; + // ... other imports + + public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentFunction.class); + + @Logging + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + LOGGER.debug("a debug log"); // this is buffered + LOGGER.info("an info log"); // this is not buffered + + // do stuff + + // Buffer is automatically cleared at the end of the method by @Logging annotation + return new APIGatewayProxyResponseEvent().withStatusCode(200); + } + } + ``` + +#### Configuring the buffer + +When configuring log buffering, you have options to fine-tune how logs are captured, stored, and emitted. You can configure the following parameters in the `BufferingAppender` configuration: + +| Parameter | Description | Configuration | +| --------------------- | ----------------------------------------------- | ---------------------------- | +| `maxBytes` | Maximum size of the log buffer in bytes | `int` (default: 20480 bytes) | +| `bufferAtVerbosity` | Minimum log level to buffer | `DEBUG` (default), `INFO`, `WARNING` | +| `flushOnErrorLog` | Automatically flush buffer when `ERROR` or `FATAL` level logs are emitted | `true` (default), `false` | + +!!! warning "Logger Level Configuration" + To use log buffering effectively, you must set your logger levels to the same level as `bufferAtVerbosity` or more verbose for the logging framework to capture and forward logs to the `BufferingAppender`. For example, if you want to buffer `DEBUG` level logs and emit `INFO`+ level logs directly, you must: + + - Set your logger levels to `DEBUG` in your log4j2.xml or logback.xml configuration + - Set `POWERTOOLS_LOG_LEVEL=DEBUG` if using the environment variable (see [Log level](#log-level) section for more details) + + If you want to sample `INFO` and `WARNING` logs but not `DEBUG` logs, set your log level to `INFO` and `bufferAtVerbosity` to `WARNING`. This allows you to define the lower and upper bounds for buffering. All logs with a more severe level than `bufferAtVerbosity` will be emitted directly. + +=== "log4j2.xml - Buffer at WARNING level" + + ```xml hl_lines="9 14-15 18" <?xml version="1.0" encoding="UTF-8"?> <Configuration> <Appenders> <Console name="JsonAppender" target="SYSTEM_OUT"> <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> </Console> + <BufferingAppender name="BufferedJsonAppender" + maxBytes="20480" + bufferAtVerbosity="WARNING"> + <AppenderRef ref="JsonAppender"/> + </BufferingAppender> </Appenders> <Loggers> - <Logger name="JsonLogger" level="INFO" additivity="false"> + <!-- Intentionally set to DEBUG to forward all logs to BufferingAppender --> + <Logger name="com.example" level="debug" additivity="false"> + <AppenderRef ref="BufferedJsonAppender"/> + </Logger> + <Root level="debug"> + <AppenderRef ref="BufferedJsonAppender"/> + </Root> + </Loggers> + </Configuration> + ``` + +=== "PaymentFunction.java - Buffer at WARNING level" + + ```java hl_lines="7-9 13" + public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentFunction.class); + + @Logging + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + LOGGER.warn("a warning log"); // this is buffered + LOGGER.info("an info log"); // this is buffered + LOGGER.debug("a debug log"); // this is buffered + + // do stuff + + // Buffer is automatically cleared at the end of the method by @Logging annotation + return new APIGatewayProxyResponseEvent().withStatusCode(200); + } + } + ``` + +=== "log4j2.xml - Disable flush on error" + + ```xml hl_lines="9" + <?xml version="1.0" encoding="UTF-8"?> + <Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + <BufferingAppender name="BufferedJsonAppender" + maxBytes="20480" + flushOnErrorLog="false"> <AppenderRef ref="JsonAppender"/> + </BufferingAppender> + </Appenders> + <Loggers> + <Logger name="com.example" level="debug" additivity="false"> + <AppenderRef ref="BufferedJsonAppender"/> </Logger> + <Root level="debug"> + <AppenderRef ref="BufferedJsonAppender"/> + </Root> + </Loggers> + </Configuration> + ``` + +=== "PaymentFunction.java - Manual flush required" + + ```java hl_lines="1 16 19-20" + import software.amazon.lambda.powertools.logging.PowertoolsLogging; + + public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentFunction.class); + + @Logging + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + LOGGER.debug("a debug log"); // this is buffered + + // do stuff + + try { + throw new RuntimeException("Something went wrong"); + } catch (RuntimeException error) { + LOGGER.error("An error occurred", error); // Logs won't be flushed here + } + + // Manually flush buffered logs + PowertoolsLogging.flushBuffer(); + + return new APIGatewayProxyResponseEvent().withStatusCode(200); + } + } + ``` + +!!! note "Disabling `flushOnErrorLog` will not flush the buffer when logging an error. This is useful when you want to control when the buffer is flushed by calling the flush method manually." + +#### Manual buffer control + +You can manually control the log buffer using the `PowertoolsLogging` utility class, which provides a backend-independent API that works with both Log4j2 and Logback: + +=== "Manual flush" + + ```java hl_lines="1 12-13" + import software.amazon.lambda.powertools.logging.PowertoolsLogging; + + public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentFunction.class); + + @Logging + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + LOGGER.debug("Processing payment"); // this is buffered + LOGGER.info("Payment validation complete"); // this is buffered + + // Manually flush all buffered logs + PowertoolsLogging.flushBuffer(); + + return new APIGatewayProxyResponseEvent().withStatusCode(200); + } + } + ``` + +=== "Manual clear" + + ```java hl_lines="1 12-13" + import software.amazon.lambda.powertools.logging.PowertoolsLogging; + + public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentFunction.class); + + @Logging + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + LOGGER.debug("Processing payment"); // this is buffered + LOGGER.info("Payment validation complete"); // this is buffered + + // Manually clear buffered logs without outputting them + PowertoolsLogging.clearBuffer(); + + return new APIGatewayProxyResponseEvent().withStatusCode(200); + } + } + ``` + +**Available methods:** + +- `#!java PowertoolsLogging.flushBuffer()` - Outputs all buffered logs and clears the buffer +- `#!java PowertoolsLogging.clearBuffer()` - Discards all buffered logs without outputting them + +#### Flushing on exceptions + +Use the `@Logging` annotation to automatically flush buffered logs when an uncaught exception is raised in your Lambda function. This is enabled by default (`flushBufferOnUncaughtError = true`), but you can explicitly configure it if needed. + +???+ warning + This feature is only available when using the `@Logging` annotation. When using the functional API, you must manually flush the buffer in exception handlers. + +=== "@Logging annotation" + + ```java hl_lines="5 11" + public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentFunction.class); + + @Logging(flushBufferOnUncaughtError = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + LOGGER.debug("a debug log"); // this is buffered + + // do stuff + + throw new RuntimeException("Something went wrong"); // Logs will be flushed here + } + } + ``` + +=== "Functional API" + + ```java hl_lines="14" + import software.amazon.lambda.powertools.logging.PowertoolsLogging; + + public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentFunction.class); + + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + return PowertoolsLogging.withLogging(context, () -> { + try { + LOGGER.debug("a debug log"); // this is buffered + // do stuff + throw new RuntimeException("Something went wrong"); + } catch (Exception e) { + PowertoolsLogging.flushBuffer(); // Manually flush buffered logs + throw e; + } + }); + } + } + ``` + +#### Buffering workflows + +##### Manual flush + +<center> +```mermaid +sequenceDiagram + participant Client + participant Lambda + participant Logger + participant CloudWatch + Client->>Lambda: Invoke Lambda + Lambda->>Logger: Initialize with DEBUG level buffering + Logger-->>Lambda: Logger buffer ready + Lambda->>Logger: logger.debug("First debug log") + Logger-->>Logger: Buffer first debug log + Lambda->>Logger: logger.info("Info log") + Logger->>CloudWatch: Directly log info message + Lambda->>Logger: logger.debug("Second debug log") + Logger-->>Logger: Buffer second debug log + Lambda->>Logger: Manual flush call + Logger->>CloudWatch: Emit buffered logs to stdout + Lambda->>Client: Return execution result +``` +<i>Flushing buffer manually</i> +</center> + +##### Flushing when logging an error + +<center> +```mermaid +sequenceDiagram + participant Client + participant Lambda + participant Logger + participant CloudWatch + Client->>Lambda: Invoke Lambda + Lambda->>Logger: Initialize with DEBUG level buffering + Logger-->>Lambda: Logger buffer ready + Lambda->>Logger: logger.debug("First log") + Logger-->>Logger: Buffer first debug log + Lambda->>Logger: logger.debug("Second log") + Logger-->>Logger: Buffer second debug log + Lambda->>Logger: logger.debug("Third log") + Logger-->>Logger: Buffer third debug log + Lambda->>Lambda: Exception occurs + Lambda->>Logger: logger.error("Error details") + Logger->>CloudWatch: Emit error log + Logger->>CloudWatch: Emit buffered debug logs + Lambda->>Client: Raise exception +``` +<i>Flushing buffer when an error happens</i> +</center> + +##### Flushing on exception + +This works when using the `@Logging` annotation which automatically clears the buffer at the end of method execution. + +<center> +```mermaid +sequenceDiagram + participant Client + participant Lambda + participant Logger + participant CloudWatch + Client->>Lambda: Invoke Lambda + Lambda->>Logger: Using @Logging annotation + Logger-->>Lambda: Logger context injected + Lambda->>Logger: logger.debug("First log") + Logger-->>Logger: Buffer first debug log + Lambda->>Logger: logger.debug("Second log") + Logger-->>Logger: Buffer second debug log + Lambda->>Lambda: Uncaught Exception + Lambda->>CloudWatch: Automatically emit buffered debug logs + Lambda->>Client: Raise uncaught exception +``` +<i>Flushing buffer when an uncaught exception happens</i> +</center> + +#### Buffering FAQs + +1. **Does the buffer persist across Lambda invocations?** No, each Lambda invocation has its own buffer. The buffer is initialized when the Lambda function is invoked and is cleared after the function execution completes or when flushed manually. +2. **Are my logs buffered during cold starts (INIT phase)?** No, we never buffer logs during cold starts. This is because we want to ensure that logs emitted during this phase are always available for debugging and monitoring purposes. The buffer is only used during the execution of the Lambda function. +3. **How can I prevent log buffering from consuming excessive memory?** You can limit the size of the buffer by setting the `maxBytes` option in the `BufferingAppender` configuration. This will ensure that the buffer does not grow indefinitely. +4. **What happens if the log buffer reaches its maximum size?** Older logs are removed from the buffer to make room for new logs. This means that if the buffer is full, you may lose some logs if they are not flushed before the buffer reaches its maximum size. When this happens, we emit a warning when flushing the buffer to indicate that some logs have been dropped. +5. **How is the log size of a log line calculated?** The log size is calculated based on the size of the log line in bytes. This includes the size of the log message, any exception (if present), the log line location, additional keys, and the timestamp. +6. **What timestamp is used when I flush the logs?** The timestamp is the original time when the log record was created. If you create a log record at 11:00:10 and flush it at 11:00:25, the log line will retain its original timestamp of 11:00:10. +7. **What happens if I try to add a log line that is bigger than max buffer size?** The log will be emitted directly to standard output and not buffered. When this happens, we emit a warning to indicate that the log line was too big to be buffered. +8. **What happens if Lambda times out without flushing the buffer?** Logs that are still in the buffer will be lost. +9. **How does the `BufferingAppender` work with different appenders?** The `BufferingAppender` is designed to wrap arbitrary appenders, providing maximum flexibility. You can wrap console appenders, file appenders, or any custom appenders with buffering functionality. + +## Sampling debug logs + +You can dynamically set a percentage of your logs to`DEBUG` level to be included in the logger output, regardless of configured log level, using the`POWERTOOLS_LOGGER_SAMPLE_RATE` environment variable, +via the `samplingRate` attribute on the `@Logging` annotation, or as a parameter in the functional API. + +!!! info + Configuration via environment variable is given precedence over sampling rate configuration, provided it's in valid value range. + +=== "@Logging annotation" + + ```java hl_lines="5" + public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + @Logging(samplingRate = 0.5) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + // will eventually be logged based on the sampling rate + LOGGER.debug("Handle payment"); + } + } + ``` + +=== "Functional API" + + ```java hl_lines="6" + public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + return PowertoolsLogging.withLogging(context, 0.5, () -> { + // will eventually be logged based on the sampling rate + LOGGER.debug("Handle payment"); + return new APIGatewayProxyResponseEvent().withStatusCode(200); + }); + } + } + ``` + +=== "Sampling via environment variable" + + ```yaml hl_lines="8" + Resources: + PaymentFunction: + Type: AWS::Serverless::Function + Properties: + ... + Environment: + Variables: + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.5 + + ``` + +## Built-in Correlation ID expressions + +You can use any of the following built-in JMESPath expressions with the `@Logging` annotation or the functional API: + +???+ note "Note: Any object key named with `-` must be escaped" + For example, **`request.headers."x-amzn-trace-id"`**. + +| Name | Expression | Description | +|-------------------------------|-------------------------------------|---------------------------------| +| **API_GATEWAY_REST** | `"requestContext.requestId"` | API Gateway REST API request ID | +| **API_GATEWAY_HTTP** | `"requestContext.requestId"` | API Gateway HTTP API request ID | +| **APPSYNC_RESOLVER** | `request.headers."x-amzn-trace-id"` | AppSync X-Ray Trace ID | +| **APPLICATION_LOAD_BALANCER** | `headers."x-amzn-trace-id"` | ALB X-Ray Trace ID | +| **EVENT_BRIDGE** | `"id"` | EventBridge Event ID | + + +## Customising fields in logs + +Powertools for AWS Lambda comes with default json structure ([standard fields](#standard-structured-keys) & [lambda context fields](#logging-lambda-context-information)). + +You can go further and customize which fields you want to keep in your logs or not. The configuration varies according to the underlying logging library. + +### Log4j2 configuration +Log4j2 configuration is done in _log4j2.xml_ and leverages `JsonTemplateLayout`: + +```xml + <Console name="console" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> +``` + +The `JsonTemplateLayout` is automatically configured with the provided template: + +??? example "LambdaJsonLayout.json" + ```json + { + "level": { + "$resolver": "level", + "field": "name" + }, + "message": { + "$resolver": "message" + }, + "error": { + "message": { + "$resolver": "exception", + "field": "message" + }, + "name": { + "$resolver": "exception", + "field": "className" + }, + "stack": { + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": true + } + } + }, + "cold_start": { + "$resolver": "powertools", + "field": "cold_start" + }, + "function_arn": { + "$resolver": "powertools", + "field": "function_arn" + }, + "function_memory_size": { + "$resolver": "powertools", + "field": "function_memory_size" + }, + "function_name": { + "$resolver": "powertools", + "field": "function_name" + }, + "function_request_id": { + "$resolver": "powertools", + "field": "function_request_id" + }, + "function_version": { + "$resolver": "powertools", + "field": "function_version" + }, + "sampling_rate": { + "$resolver": "powertools", + "field": "sampling_rate" + }, + "service": { + "$resolver": "powertools", + "field": "service" + }, + "timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + } + }, + "xray_trace_id": { + "$resolver": "powertools", + "field": "xray_trace_id" + }, + "correlation_id": { + "$resolver": "powertools", + "field": "correlation_id" + }, + "": { + "$resolver": "powertools" + } + } + ``` + +You can create your own template and leverage the [PowertoolsResolver](https://github.com/aws-powertools/powertools-lambda-java/tree/v2/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java){target="_blank"} +and any other resolver to log the desired fields with the desired format. Some examples of customization are given below: + +#### Customising date format + +Utility by default emits `timestamp` field in the logs in format `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` and in system default timezone. +If you need to customize format and timezone, you can update your template.json or by configuring `log4j2.component.properties` as shown in examples below: + +=== "my-custom-template.json" + + ```json + { + "timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd HH:mm:ss", + "timeZone": "Europe/Paris", + } + }, + } + ``` + +=== "log4j2.component.properties" + + ```properties hl_lines="1 2" + log4j.layout.jsonTemplate.timestampFormatPattern=yyyy-MM-dd'T'HH:mm:ss.SSSZz + log4j.layout.jsonTemplate.timeZone=Europe/Oslo + ``` + +See [`TimestampResolver` documentation](https://logging.apache.org/log4j/2.x/manual/json-template-layout.html#event-template-resolver-timestamp){target="_blank"} for more details. + +???+ warning "Lambda Advanced Logging Controls date format" + When using the Lambda ALC, you must have a date format compatible with the [RFC3339](https://www.rfc-editor.org/rfc/rfc3339) + +#### More customization +You can also customize how [exceptions are logged](https://logging.apache.org/log4j/2.x/manual/json-template-layout.html#event-template-resolver-exception){target="_blank"}, and much more. +See the [JSON Layout template documentation](https://logging.apache.org/log4j/2.x/manual/json-template-layout.html){target="_blank"} for more details. + +### Logback configuration +Logback configuration is done in _logback.xml_ and the `LambdaJsonEncoder`: + +```xml + <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + </encoder> + </appender> +``` + +The `LambdaJsonEncoder` can be customized in different ways: + +#### Customising date format +Utility by default emits `timestamp` field in the logs in format `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` and in system default timezone. +If you need to customize format and timezone, you can change use the following: + +```xml + <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + <timestampFormat>yyyy-MM-dd HH:mm:ss</timestampFormat> + <timestampFormatTimezoneId>Europe/Paris</timestampFormatTimezoneId> + </encoder> +``` + +#### More customization + +- You can use a standard `ThrowableHandlingConverter` to customize the exception format (default is no converter). Example: + +```xml + <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter"> + <maxDepthPerThrowable>30</maxDepthPerThrowable> + <maxLength>2048</maxLength> + <shortenedClassNameLength>20</shortenedClassNameLength> + <exclude>sun\.reflect\..*\.invoke.*</exclude> + <exclude>net\.sf\.cglib\.proxy\.MethodProxy\.invoke</exclude> + <evaluator class="myorg.MyCustomEvaluator"/> + <rootCauseFirst>true</rootCauseFirst> + <inlineHash>true</inlineHash> + </throwableConverter> + </encoder> +``` + +- You can choose to add information about threads (default is `false`): + +```xml + <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + <includeThreadInfo>true</includeThreadInfo> + </encoder> +``` + +- You can even choose to remove Powertools information from the logs like function name, arn: + +```xml + <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + <includePowertoolsInfo>false</includePowertoolsInfo> + </encoder> +``` + +## Elastic Common Schema (ECS) Support + +Utility also supports [Elastic Common Schema(ECS)](https://www.elastic.co/guide/en/ecs/current/ecs-reference.html){target="_blank"} format. +The field emitted in logs will follow specs from [ECS](https://www.elastic.co/guide/en/ecs/current/ecs-reference.html){target="_blank"} together with field captured by utility as mentioned [above](#standard-structured-keys). + +### Log4j2 configuration + +Use `LambdaEcsLayout.json` as `eventTemplateUri` when configuring `JsonTemplateLayout`. + +=== "log4j2.xml" + + ```xml hl_lines="5" + <?xml version="1.0" encoding="UTF-8"?> + <Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaEcsLayout.json" /> + </Console> + </Appenders> + <Loggers> <Root level="info"> <AppenderRef ref="JsonAppender"/> </Root> @@ -513,3 +1616,20 @@ Below examples shows deprecated and new configuration of `log4j2.xml`. </Configuration> ``` +### Logback configuration + +Use the `LambdaEcsEncoder` rather than the `LambdaJsonEncoder` when configuring the appender: + +=== "logback.xml" + + ```xml hl_lines="3" + <configuration> + <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaEcsEncoder"> + </encoder> + </appender> + <root level="INFO"> + <appender-ref ref="console" /> + </root> + </configuration> + ``` diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 4d250df48..e7f7bd87f 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -3,42 +3,177 @@ title: Metrics description: Core utility --- -Metrics creates custom metrics asynchronously by logging metrics to standard output following Amazon CloudWatch Embedded Metric Format (EMF). +Metrics creates custom metrics asynchronously by logging metrics to standard output following [Amazon CloudWatch Embedded Metric Format (EMF)](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format.html). -These metrics can be visualized through [Amazon CloudWatch Console](https://console.aws.amazon.com/cloudwatch/). +These metrics can be visualized through [Amazon CloudWatch Console](https://aws.amazon.com/cloudwatch/). -**Key features** +## Key features -* Aggregate up to 100 metrics using a single CloudWatch EMF object (large JSON blob). -* Validate against common metric definitions mistakes (metric unit, values, max dimensions, max metrics, etc). -* Metrics are created asynchronously by the CloudWatch service, no custom stacks needed. -* Context manager to create a one off metric with a different dimension. +- Aggregate up to 100 metrics using a single [CloudWatch EMF](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html){target="\_blank"} object (large JSON blob) +- Validating your metrics against common metric definitions mistakes (for example, metric unit, values, max dimensions, max metrics) +- Metrics are created asynchronously by the CloudWatch service. You do not need any custom stacks, and there is no impact to Lambda function latency +- Support for creating one off metrics with different dimensions +- GraalVM support ## Terminologies -If you're new to Amazon CloudWatch, there are two terminologies you must be aware of before using this utility: +If you're new to Amazon CloudWatch, there are some terminologies you must be aware of before using this utility: -* **Namespace**. It's the highest level container that will group multiple metrics from multiple services for a given application, for example `ServerlessEcommerce`. -* **Dimensions**. Metrics metadata in key-value format. They help you slice and dice metrics visualization, for example `ColdStart` metric by Payment `service`. +- **Namespace**. It's the highest level container that will group multiple metrics from multiple services for a given application, for example `ServerlessAirline`. +- **Dimensions**. Metrics metadata in key-value format. They help you slice and dice metrics visualization, for example `ColdStart` metric by `service`. +- **Metric**. It's the name of the metric, for example: `SuccessfulBooking` or `UpdatedBooking`. +- **Unit**. It's a value representing the unit of measure for the corresponding metric, for example: `Count` or `Seconds`. +- **Resolution**. It's a value representing the storage resolution for the corresponding metric. Metrics can be either `Standard` or `High` resolution. Read more about CloudWatch Periods [here](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Resolution_definition). + +Visit the AWS documentation for a complete explanation for [Amazon CloudWatch concepts](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html). <figure> <img src="../../media/metrics_terminology.png" /> <figcaption>Metric terminology, visually explained</figcaption> </figure> +## Install + +=== "Maven" + + ```xml hl_lines="3-7 16 18 24-27" + <dependencies> + ... + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + <version>{{ powertools.version }}</version> + </dependency> + ... + </dependencies> + ... + <!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project --> + <!-- Note: This AspectJ configuration is not needed when using the functional approach --> + <build> + <plugins> + ... + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14</version> + <configuration> + <source>11</source> <!-- or higher --> + <target>11</target> <!-- or higher --> + <complianceLevel>11</complianceLevel> <!-- or higher --> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <!-- AspectJ compiler version, in sync with runtime --> + <version>1.9.22</version> + </dependency> + </dependencies> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + ... + </plugins> + </build> + ``` + +=== "Gradle" + + ```groovy hl_lines="3 11 12" + plugins { + id 'java' + id 'io.freefair.aspectj.post-compile-weaving' version '8.1.0' // Not needed when using the functional approach + } + + repositories { + mavenCentral() + } + + dependencies { + aspect 'software.amazon.lambda:powertools-metrics:{{ powertools.version }}' // Not needed when using the functional approach + implementation 'software.amazon.lambda:powertools-metrics:{{ powertools.version }}' // Use this instead of 'aspect' when using the functional approach + } + + sourceCompatibility = 11 + targetCompatibility = 11 + ``` ## Getting started -Metric has two global settings that will be used across all metrics emitted: +Metrics has three global settings that will be used across all metrics emitted. Use your application or main service as the metric namespace to easily group all metrics: -Setting | Description | Environment variable | Constructor parameter -------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------- -**Metric namespace** | Logical container where all metrics will be placed e.g. `ServerlessAirline` | `POWERTOOLS_METRICS_NAMESPACE` | `namespace` -**Service** | Optionally, sets **service** metric dimension across all metrics e.g. `payment` | `POWERTOOLS_SERVICE_NAME` | `service` +| Setting | Description | Environment variable | Decorator parameter | +| -------------------- | ------------------------------------------------------------------------------- | ---------------------------------- | ------------------- | +| **Metric namespace** | Logical container where all metrics will be placed e.g. `ServerlessAirline` | `POWERTOOLS_METRICS_NAMESPACE` | `namespace` | +| **Service** | Optionally, sets **service** metric dimension across all metrics e.g. `payment` | `POWERTOOLS_SERVICE_NAME` | `service` | +| **Function name** | Function name used as dimension for the cold start metric | `POWERTOOLS_METRICS_FUNCTION_NAME` | `functionName` | +| **Disable Metrics** | Optionally, disables all metrics flushing | `POWERTOOLS_METRICS_DISABLED` | N/A | !!! tip "Use your application or main service as the metric namespace to easily group all metrics" -=== "template.yaml" +!!! info "`POWERTOOLS_METRICS_DISABLED` will not disable default metrics created by AWS services." + +### Order of Precedence of `Metrics` configuration + +The `Metrics` Singleton can be configured by three different interfaces. The following order of precedence applies: + +1. `@FlushMetrics` annotation +2. `MetricsBuilder` using Builder pattern (see [Advanced section](#usage-without-flushmetrics-annotation)) +3. Environment variables (recommended) + +For most use-cases, we recommend using Environment variables and only overwrite settings in code where needed using either the `@FlushMetrics` annotation or `MetricsBuilder` if the annotation cannot be used. + +=== "@FlushMetrics annotation" + + ```java hl_lines="9" + import software.amazon.lambda.powertools.metrics.FlushMetrics; + import software.amazon.lambda.powertools.metrics.MetricsFactory; + + public class MetricsEnabledHandler implements RequestHandler<Object, Object> { + + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Override + @FlushMetrics(namespace = "ServerlessAirline", service = "payment") + public Object handleRequest(Object input, Context context) { + // ... + } + } + ``` + +=== "MetricsBuilder" + + ```java hl_lines="7-8" + import software.amazon.lambda.powertools.metrics.Metrics; + import software.amazon.lambda.powertools.metrics.MetricsBuilder; + + public class MetricsEnabledHandler implements RequestHandler<Object, Object> { + + private static final Metrics metrics = MetricsBuilder.builder() + .withNamespace("ServerlessAirline") + .withService("payment") + .build(); + + @Override + public Object handleRequest(Object input, Context context) { + // ... + metrics.flush(); + } + } + ``` + +=== "Environment variables" ```yaml hl_lines="9 10" Resources: @@ -46,81 +181,150 @@ Setting | Description | Environment variable | Constructor parameter Type: AWS::Serverless::Function Properties: ... - Runtime: java8 + Runtime: java11 Environment: Variables: POWERTOOLS_SERVICE_NAME: payment POWERTOOLS_METRICS_NAMESPACE: ServerlessAirline ``` +`Metrics` is implemented as a Singleton to keep track of your aggregate metrics in memory and make them accessible anywhere in your code. The `@FlushMetrics` annotation automatically flushes metrics at the end of the Lambda handler execution. Alternatively, you can use the functional approach and manually flush metrics using `metrics.flush()`. + +!!!info "Read more about the functional approach in the [advanced section below](#usage-without-flushmetrics-annotation)." + +## Creating metrics + +You can create metrics using `addMetric`, and manually create dimensions for all your aggregate metrics using `addDimension`. Anywhere in your code, you can access the current `Metrics` Singleton using the `MetricsFactory`. + === "MetricsEnabledHandler.java" - ```java hl_lines="8" + ```java hl_lines="13" + import software.amazon.lambda.powertools.metrics.FlushMetrics; import software.amazon.lambda.powertools.metrics.Metrics; - + import software.amazon.lambda.powertools.metrics.MetricsFactory; + import software.amazon.lambda.powertools.metrics.model.MetricUnit; + public class MetricsEnabledHandler implements RequestHandler<Object, Object> { - - MetricsLogger metricsLogger = MetricsUtils.metricsLogger(); - + + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + @Override - @Metrics(namespace = "ExampleApplication", service = "booking") + @FlushMetrics(namespace = "ServerlessAirline", service = "payment") public Object handleRequest(Object input, Context context) { - ... + metrics.addDimension("environment", "prod"); + metrics.addMetric("SuccessfulBooking", 1, MetricUnit.COUNT); + // ... } } ``` -You can initialize Metrics anywhere in your code as many times as you need - It'll keep track of your aggregate metrics in memory. +!!! tip "The `MetricUnit` enum facilitates finding a supported metric unit by CloudWatch." -## Creating metrics +<!-- prettier-ignore-start --> +!!! note "Metrics dimensions" + CloudWatch EMF supports a max of 9 dimensions per metric. The Metrics utility will validate this limit when adding dimensions. +<!-- prettier-ignore-end --> -You can create metrics using `putMetric`, and manually create dimensions for all your aggregate metrics using `putDimensions`. +### Adding high-resolution metrics -=== "MetricsEnabledHandler.java" +You can create [high-resolution metrics](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/publishingMetrics.html#high-resolution-metrics) +passing a `#!java MetricResolution.HIGH` to the `addMetric` method. If nothing is passed `#!java MetricResolution.STANDARD` will be used. + +=== "HigResMetricsHandler.java" - ```java hl_lines="11 12" + ```java hl_lines="3 13" + import software.amazon.lambda.powertools.metrics.FlushMetrics; import software.amazon.lambda.powertools.metrics.Metrics; - import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; + import software.amazon.lambda.powertools.metrics.model.MetricResolution; public class MetricsEnabledHandler implements RequestHandler<Object, Object> { - - MetricsLogger metricsLogger = MetricsUtils.metricsLogger(); - + + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + @Override - @Metrics(namespace = "ExampleApplication", service = "booking") + @FlushMetrics(namespace = "ServerlessAirline", service = "payment") public Object handleRequest(Object input, Context context) { - metricsLogger.putDimensions(DimensionSet.of("environment", "prod")); - metricsLogger.putMetric("SuccessfulBooking", 1, Unit.COUNT); - ... + // ... + metrics.addMetric("SuccessfulBooking", 1, MetricUnit.COUNT, MetricResolution.HIGH); } } ``` -!!! tip "The `Unit` enum facilitate finding a supported metric unit by CloudWatch." +<!-- prettier-ignore-start --> +!!! info "When is it useful?" + High-resolution metrics are data with a granularity of one second and are very useful in several situations such as telemetry, time series, real-time incident management, and others. +<!-- prettier-ignore-end --> + +### Adding dimensions + +You can add dimensions to your metrics using the `addDimension` method. You can either pass key-value pairs or you can create higher cardinality dimensions using `DimensionSet`. + +=== "KeyValueDimensionHandler.java" + + ```java hl_lines="3 13" + import software.amazon.lambda.powertools.metrics.FlushMetrics; + import software.amazon.lambda.powertools.metrics.Metrics; + import software.amazon.lambda.powertools.metrics.model.MetricResolution; + + public class MetricsEnabledHandler implements RequestHandler<Object, Object> { + + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Override + @FlushMetrics(namespace = "ServerlessAirline", service = "payment") + public Object handleRequest(Object input, Context context) { + metrics.addDimension("Dimension", "Value"); + metrics.addMetric("SuccessfulBooking", 1, MetricUnit.COUNT); + } + } + ``` + +=== "HighCardinalityDimensionHandler.java" + + ```java hl_lines="4 13-14" + import software.amazon.lambda.powertools.metrics.FlushMetrics; + import software.amazon.lambda.powertools.metrics.Metrics; + import software.amazon.lambda.powertools.metrics.model.MetricResolution; + import software.amazon.lambda.powertools.metrics.model.DimensionSet; + + public class MetricsEnabledHandler implements RequestHandler<Object, Object> { + + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); -!!! note "Metrics overflow" - CloudWatch EMF supports a max of 100 metrics. Metrics utility will flush all metrics when adding the 100th metric while subsequent metrics will be aggregated into a new EMF object, for your convenience. + @Override + @FlushMetrics(namespace = "ServerlessAirline", service = "payment") + public Object handleRequest(Object input, Context context) { + // You can add up to 30 dimensions in a single DimensionSet + metrics.addDimension(DimensionSet.of("Dimension1", "Value1", "Dimension2", "Value2")); + metrics.addMetric("SuccessfulBooking", 1, MetricUnit.COUNT); + } + } + ``` ### Flushing metrics -The `@Metrics` annotation **validates**, **serializes**, and **flushes** all your metrics. During metrics validation, -if no metrics are provided no exception will be raised. If metrics are provided, and any of the following criteria are -not met, `ValidationException` exception will be raised. +The `@FlushMetrics` annotation **validates**, **serializes**, and **flushes** all your metrics. During metrics validation, +if no metrics are provided no exception will be raised. If metrics are provided, and any of the following criteria are +not met, `IllegalStateException` or `IllegalArgumentException` exceptions will be raised. +<!-- prettier-ignore-start --> !!! tip "Metric validation" - * Maximum of 9 dimensions + - Maximum of 30 dimensions (`Service` default dimension counts as a regular dimension) + - Dimension keys and values cannot be null or empty + - Metric values must be valid numbers +<!-- prettier-ignore-end --> -If you want to ensure that at least one metric is emitted, you can pass `raiseOnEmptyMetrics = true` to the **@Metrics** annotation: +If you want to ensure that at least one metric is emitted, you can pass `raiseOnEmptyMetrics = true` to the `@FlushMetrics` annotation: === "MetricsRaiseOnEmpty.java" ```java hl_lines="6" - import software.amazon.lambda.powertools.metrics.Metrics; + import software.amazon.lambda.powertools.metrics.FlushMetrics; public class MetricsRaiseOnEmpty implements RequestHandler<Object, Object> { @Override - @Metrics(raiseOnEmptyMetrics = true) + @FlushMetrics(raiseOnEmptyMetrics = true) public Object handleRequest(Object input, Context context) { ... } @@ -129,17 +333,17 @@ If you want to ensure that at least one metric is emitted, you can pass `raiseOn ## Capturing cold start metric -You can capture cold start metrics automatically with `@Metrics` via the `captureColdStart` variable. +You can capture cold start metrics automatically with `@FlushMetrics` via the `captureColdStart` variable. === "MetricsColdStart.java" ```java hl_lines="6" - import software.amazon.lambda.powertools.metrics.Metrics; + import software.amazon.lambda.powertools.metrics.FlushMetrics; public class MetricsColdStart implements RequestHandler<Object, Object> { @Override - @Metrics(captureColdStart = true) + @FlushMetrics(captureColdStart = true) public Object handleRequest(Object input, Context context) { ... } @@ -148,33 +352,77 @@ You can capture cold start metrics automatically with `@Metrics` via the `captur If it's a cold start invocation, this feature will: -* Create a separate EMF blob solely containing a metric named `ColdStart` -* Add `FunctionName` and `Service` dimensions +- Create a separate EMF blob solely containing a metric named `ColdStart` +- Add `FunctionName` and `Service` dimensions This has the advantage of keeping cold start metric separate from your application metrics. +You can also specify a custom function name to be used in the cold start metric: + +=== "MetricsColdStartCustomFunction.java" + + ```java hl_lines="6" + import software.amazon.lambda.powertools.metrics.FlushMetrics; + + public class MetricsColdStartCustomFunction implements RequestHandler<Object, Object> { + + @Override + @FlushMetrics(captureColdStart = true, functionName = "CustomFunction") + public Object handleRequest(Object input, Context context) { + ... + } + } + ``` + +<!-- prettier-ignore-start --> +!!!tip "You can overwrite the default `Service` and `FunctionName` dimensions of the cold start metric" + Set `#!java @FlushMetrics(captureColdStart = false)` and use the `captureColdStartMetric` method manually: + + ```java hl_lines="6 8" + public class MetricsColdStartCustomFunction implements RequestHandler<Object, Object> { + + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Override + @FlushMetrics(captureColdStart = false) + public Object handleRequest(Object input, Context context) { + metrics.captureColdStartMetric(context, DimensionSet.of("CustomDimension", "CustomValue")); + ... + } + } + ``` +<!-- prettier-ignore-end --> + ## Advanced -## Adding metadata +### Adding metadata + +You can use `addMetadata` for advanced use cases, where you want to add metadata as part of the serialized metrics object. -You can use `putMetadata` for advanced use cases, where you want to metadata as part of the serialized metrics object. +<!-- prettier-ignore-start --> +!!! info + This will not be available during metrics visualization, use Dimensions for this purpose. !!! info - **This will not be available during metrics visualization, use `dimensions` for this purpose.** + Adding metadata with a key that is the same as an existing metric will be ignored. +<!-- prettier-ignore-end --> === "App.java" - ```java hl_lines="8 9" + ```java hl_lines="13" + import software.amazon.lambda.powertools.metrics.FlushMetrics; import software.amazon.lambda.powertools.metrics.Metrics; - import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; + import software.amazon.lambda.powertools.metrics.MetricsFactory; public class App implements RequestHandler<Object, Object> { + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + @Override - @Metrics(namespace = "ServerlessAirline", service = "payment") + @FlushMetrics(namespace = "ServerlessAirline", service = "booking-service") public Object handleRequest(Object input, Context context) { - metricsLogger().putMetric("CustomMetric1", 1, Unit.COUNT); - metricsLogger().putMetadata("booking_id", "1234567890"); + metrics.addMetric("CustomMetric1", 1, MetricUnit.COUNT); + metrics.addMetadata("booking_id", "1234567890"); // Needs to be added BEFORE flushing ... } } @@ -182,55 +430,262 @@ You can use `putMetadata` for advanced use cases, where you want to metadata as This will be available in CloudWatch Logs to ease operations on high cardinal data. -## Overriding default dimension set +### Setting default dimensions + +By default, all metrics emitted via module captures `Service` as one of the default dimensions. This is either specified via `POWERTOOLS_SERVICE_NAME` environment variable or via `service` attribute on `Metrics` annotation. -By default, all metrics emitted via module captures `Service` as one of the default dimension. This is either specified via -`POWERTOOLS_SERVICE_NAME` environment variable or via `service` attribute on `Metrics` annotation. If you wish to override the default -Dimension, it can be done via `#!java MetricsUtils.defaultDimensions()`. +If you wish to set custom default dimensions, it can be done via `#!java metrics.setDefaultDimensions()`. You can also use the `MetricsBuilder` instead of the `MetricsFactory` to configure **and** retrieve the `Metrics` Singleton at the same time (see `MetricsBuilder.java` tab). === "App.java" - ```java hl_lines="8 9 10" + ```java hl_lines="13" + import software.amazon.lambda.powertools.metrics.FlushMetrics; import software.amazon.lambda.powertools.metrics.Metrics; - import static software.amazon.lambda.powertools.metrics.MetricsUtils; - + import software.amazon.lambda.powertools.metrics.MetricsFactory; + import software.amazon.lambda.powertools.metrics.model.DimensionSet; + public class App implements RequestHandler<Object, Object> { - - MetricsLogger metricsLogger = MetricsUtils.metricsLogger(); - - static { - MetricsUtils.defaultDimensions(DimensionSet.of("CustomDimension", "booking")); + + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Override + @FlushMetrics(namespace = "ServerlessAirline", service = "payment") + public Object handleRequest(Object input, Context context) { + metrics.setDefaultDimensions(DimensionSet.of("CustomDimension", "booking", "Environment", "prod")); + ... } - + } + ``` + +=== "MetricsBuilder.java" + + ```java hl_lines="8-10" + import software.amazon.lambda.powertools.metrics.FlushMetrics; + import software.amazon.lambda.powertools.metrics.Metrics; + import software.amazon.lambda.powertools.metrics.MetricsFactory; + import software.amazon.lambda.powertools.metrics.model.DimensionSet; + + public class App implements RequestHandler<Object, Object> { + + private static final Metrics metrics = MetricsBuilder.builder() + .withDefaultDimensions(DimensionSet.of("CustomDimension", "booking", "Environment", "prod")) + .build(); + @Override - @Metrics(namespace = "ExampleApplication", service = "booking") + @FlushMetrics(namespace = "ServerlessAirline", service = "payment") public Object handleRequest(Object input, Context context) { + metrics.addMetric("CustomMetric1", 1, MetricUnit.COUNT); ... - MetricsUtils.withSingleMetric("Metric2", 1, Unit.COUNT, log -> {}); } } ``` -## Creating a metric with a different dimension +<!-- prettier-ignore-start --> +!!!note + Overwriting the default dimensions will also overwrite the default `Service` dimension. If you wish to keep `Service` in your default dimensions, you need to add it manually. +<!-- prettier-ignore-end --> + +### Creating metrics with different configuration + +You can create metrics with different configurations e.g. different namespace and/or dimensions using `flushMetrics()`: -CloudWatch EMF uses the same dimensions across all your metrics. Use `withSingleMetric` if you have a metric that should have different dimensions. +=== "App.java" + + ```java hl_lines="12-22" + import software.amazon.lambda.powertools.metrics.Metrics; + import software.amazon.lambda.powertools.metrics.MetricsFactory; + import software.amazon.lambda.powertools.metrics.model.DimensionSet; + import software.amazon.lambda.powertools.metrics.model.MetricUnit; + + public class App implements RequestHandler<Object, Object> { + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Override + @FlushMetrics(namespace = "ServerlessAirline", service = "payment") + public Object handleRequest(Object input, Context context) { + metrics.flushMetrics((customMetrics) -> { + customMetrics.addMetric("CustomMetric", 1, MetricUnit.COUNT); + // To optionally set a different namespace + customMetrics.setNamespace("CustomNamespace"); + // To optionally set different default dimensions + customMetrics.setDefaultDimensions(DimensionSet.of("CustomDefaultDimension", "value")); + // To optionally append additional dimensions to default dimensions + customMetrics.addDimension(DimensionSet.of("CustomDimension", "value")); + // To optionally add metadata + customMetrics.addMetadata("CustomMetadata", "value")); + }); + } + } + ``` +<!-- prettier-ignore-start --> !!! info - Generally, this would be an edge case since you [pay for unique metric](https://aws.amazon.com/cloudwatch/pricing/). Keep the following formula in mind: + Generally, this would be an edge case since you [pay for unique metric](https://aws.amazon.com/cloudwatch/pricing). Keep the following formula in mind: + **unique metric = (metric_name + dimension_name + dimension_value)** +<!-- prettier-ignore-end --> + +### Usage without `@FlushMetrics` annotation + +You can use the **functional API** approach (see [usage patterns](../usage-patterns.md#functional-approach)) to work with Metrics without the `@FlushMetrics` annotation. The `Metrics` Singleton provides all configuration options via `MetricsBuilder`. This approach eliminates the AspectJ runtime dependency and is useful if you work in an environment or with a framework that does not leverage the vanilla Lambda `handleRequest` method. + +!!!info "The environment variables for Service and Namespace configuration still apply but can be overwritten with `MetricsBuilder` if needed." + +The following example shows how to configure a custom `Metrics` Singleton using the Builder pattern. With the functional approach, you must manually flush metrics using `metrics.flush()`. === "App.java" - ```java hl_lines="7 8 9" - import static software.amazon.lambda.powertools.metrics.MetricsUtils.withSingleMetric; + ```java hl_lines="7-12 19 24" + import software.amazon.lambda.powertools.metrics.Metrics; + import software.amazon.lambda.powertools.metrics.MetricsBuilder; + import software.amazon.lambda.powertools.metrics.model.DimensionSet; + import software.amazon.lambda.powertools.metrics.model.MetricUnit; public class App implements RequestHandler<Object, Object> { + // Create and configure a Metrics singleton using the functional approach + private static final Metrics metrics = MetricsBuilder.builder() + .withNamespace("ServerlessAirline") + .withRaiseOnEmptyMetrics(true) + .withService("payment") + .build(); @Override public Object handleRequest(Object input, Context context) { - withSingleMetric("CustomMetrics2", 1, Unit.COUNT, "Another", (metric) -> { - metric.setDimensions(DimensionSet.of("AnotherService", "CustomService")); - }); + // You can manually capture the cold start metric + // Lambda context is an optional argument if not available in your environment + // Dimensions are also optional. + metrics.captureColdStartMetric(context, DimensionSet.of("FunctionName", "MyFunction", "Service", "payment")); + + // Add metrics + metrics.addMetric("CustomMetric", 1, MetricUnit.COUNT); + // Manually flush metrics + metrics.flush(); + } + } + ``` + +## Testing your code + +### Suppressing metrics output + +If you would like to suppress metrics output during your unit tests, you can use the `POWERTOOLS_METRICS_DISABLED` environment variable. For example, using Maven you can set in your build plugins: + +```xml +<plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <POWERTOOLS_METRICS_DISABLED>true</POWERTOOLS_METRICS_DISABLED> + </environmentVariables> + </configuration> +</plugin> +``` + +### Asserting EMF output + +When unit testing your code, you can run assertions against the output generated by the `Metrics` Singleton. For the `EmfMetricsLogger`, you can assert the generated JSON blob following the [CloudWatch EMF specification](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format.html) against your expected output. + +Make sure to set a test metrics namespace and service name to run assertions against metrics. For example, by setting the following environment variables in your tests: + +```xml +<plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <POWERTOOLS_SERVICE_NAME>TestService</POWERTOOLS_SERVICE_NAME> + <POWERTOOLS_METRICS_NAMESPACE>TestNamespace</POWERTOOLS_METRICS_NAMESPACE> + </environmentVariables> + </configuration> +</plugin> +``` + +Consider the following example where we redirect the standard output to a custom `PrintStream`. We use the Jackson library to parse the EMF output into a `JsonNode` and run assertions against that. + +```java hl_lines="35 40 56-72" +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.metrics.model.MetricUnit; + +@ExtendWith(MockitoExtension.class) +class MetricsTestExample { + + @Mock + Context lambdaContext; + + private final PrintStream standardOut = System.out; + private ByteArrayOutputStream outputStreamCaptor; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @BeforeEach + void setUp() { + outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + } + + @AfterEach + void tearDown() throws Exception { + System.setOut(standardOut); + } + + @Test + void shouldCaptureMetricsFromAnnotatedHandler() throws Exception { + // Given + RequestHandler<Map<String, Object>, String> handler = new HandlerWithMetricsAnnotation(); + Map<String, Object> input = new HashMap<>(); + + // When + handler.handleRequest(input, lambdaContext); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + String[] jsonLines = emfOutput.split("\n"); + + // First JSON object should be the cold start metric + JsonNode coldStartNode = objectMapper.readTree(jsonLines[0]); + assertThat(coldStartNode.has("ColdStart")).isTrue(); + assertThat(coldStartNode.get("ColdStart").asDouble()).isEqualTo(1.0); + assertThat(coldStartNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("TestNamespace"); + assertThat(coldStartNode.has("Service")).isTrue(); + assertThat(coldStartNode.get("Service").asText()).isEqualTo("TestService"); + + // Second JSON object should be the regular metric + JsonNode regularNode = objectMapper.readTree(jsonLines[1]); + assertThat(regularNode.has("test-metric")).isTrue(); + assertThat(regularNode.get("test-metric").asDouble()).isEqualTo(100.0); + assertThat(regularNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("TestNamespace"); + assertThat(regularNode.has("Service")).isTrue(); + assertThat(regularNode.get("Service").asText()).isEqualTo("TestService"); + } + + static class HandlerWithMetricsAnnotation implements RequestHandler<Map<String, Object>, String> { + @Override + @FlushMetrics(captureColdStart = true) + public String handleRequest(Map<String, Object> input, Context context) { + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + return "OK"; } } - ``` \ No newline at end of file +} +``` diff --git a/docs/core/tracing.md b/docs/core/tracing.md index 5c08d5c5a..95fbe6d06 100644 --- a/docs/core/tracing.md +++ b/docs/core/tracing.md @@ -3,7 +3,7 @@ title: Tracing description: Core utility --- -Powertools tracing is an opinionated thin wrapper for [AWS X-Ray Java SDK](https://github.com/aws/aws-xray-sdk-java/) +The Tracing utility is an opinionated thin wrapper for [AWS X-Ray Java SDK](https://github.com/aws/aws-xray-sdk-java/) a provides functionality to reduce the overhead of performing common tracing tasks. ![Tracing showcase](../media/tracing_utility_showcase.png) @@ -14,8 +14,86 @@ a provides functionality to reduce the overhead of performing common tracing tas * Helper methods to improve the developer experience of creating new X-Ray subsegments. * Better developer experience when developing with multiple threads. * Auto patch supported modules by AWS X-Ray + * GraalVM support -Initialization +## Install + +=== "Maven" + + ```xml hl_lines="3-7 25-28" + <dependencies> + ... + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + <version>{{ powertools.version }}</version> + </dependency> + ... + </dependencies> + ... + <!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project --> + <!-- Note: This AspectJ configuration is not needed when using the functional approach --> + <build> + <plugins> + ... + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14</version> + <configuration> + <source>11</source> <!-- or higher --> + <target>11</target> <!-- or higher --> + <complianceLevel>11</complianceLevel> <!-- or higher --> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <!-- AspectJ compiler version, in sync with runtime --> + <version>1.9.22</version> + </dependency> + </dependencies> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + ... + </plugins> + </build> + ``` + +=== "Gradle" + + ```groovy hl_lines="3 11 12" + plugins { + id 'java' + id 'io.freefair.aspectj.post-compile-weaving' version '8.1.0' // Not needed when using the functional approach + } + + repositories { + mavenCentral() + } + + dependencies { + aspect 'software.amazon.lambda:powertools-tracing:{{ powertools.version }}' // Not needed when using the functional approach + implementation 'software.amazon.lambda:powertools-tracing:{{ powertools.version }}' // Use this instead of 'aspect' when using the functional approach + } + + sourceCompatibility = 11 // or higher + targetCompatibility = 11 // or higher + ``` + +## Initialization Before your use this utility, your AWS Lambda function [must have permissions](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html#services-xray-permissions) to send traces to AWS X-Ray. @@ -29,7 +107,7 @@ Before your use this utility, your AWS Lambda function [must have permissions](h Type: AWS::Serverless::Function Properties: ... - Runtime: java8 + Runtime: java11 Tracing: Active Environment: @@ -37,16 +115,18 @@ Before your use this utility, your AWS Lambda function [must have permissions](h POWERTOOLS_SERVICE_NAME: example ``` -The Powertools service name is used as the X-Ray namespace. This can be set using the environment variable +The Powertools for AWS Lambda (Java) service name is used as the X-Ray namespace. This can be set using the environment variable `POWERTOOLS_SERVICE_NAME` ### Lambda handler -To enable Powertools tracing to your function add the `@Tracing` annotation to your `handleRequest` method or on -any method will capture the method as a separate subsegment automatically. You can optionally choose to customize -segment name that appears in traces. +You can enable tracing using either the `@Tracing` annotation or the functional API. + +**With the `@Tracing` annotation**, add it to your `handleRequest` method or any method to capture it as a separate subsegment automatically. You can optionally customize the segment name that appears in traces. + +**With the functional API**, use `TracingUtils.withSubsegment()` to manually create subsegments without AspectJ configuration. -=== "Tracing annotation" +=== "@Tracing annotation" ```java hl_lines="3 10 15" public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { @@ -70,6 +150,25 @@ segment name that appears in traces. } ``` +=== "Functional API" + + ```java hl_lines="1 6 7 8 10 11 12" + import software.amazon.lambda.powertools.tracing.TracingUtils; + + public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + TracingUtils.withSubsegment("businessLogic1", subsegment -> { + // Business logic 1 + }); + + TracingUtils.withSubsegment("businessLogic2", subsegment -> { + // Business logic 2 + }); + } + } + ``` + === "Custom Segment names" ```java hl_lines="3" @@ -81,22 +180,25 @@ segment name that appears in traces. } ``` -When using this `@Tracing` annotation, Utility performs these additional tasks to ease operations: +When using the `@Tracing` annotation, the utility performs these additional tasks to ease operations: * Creates a `ColdStart` annotation to easily filter traces that have had an initialization overhead. * Creates a `Service` annotation if service parameter or `POWERTOOLS_SERVICE_NAME` is set. * Captures any response, or full exceptions generated by the handler, and include as tracing metadata. +By default, the `@Tracing` annotation uses `captureMode=ENVIRONMENT_VAR`, which means it will only record method responses and exceptions if you set +the environment variables `POWERTOOLS_TRACER_CAPTURE_RESPONSE` and `POWERTOOLS_TRACER_CAPTURE_ERROR` to `true`. You can override this behavior by +specifying a different `captureMode` to always record response, exception, both, or neither. -By default, this annotation will automatically record method responses and exceptions. You can change the default behavior by setting -the environment variables `POWERTOOLS_TRACER_CAPTURE_RESPONSE` and `POWERTOOLS_TRACER_CAPTURE_ERROR` as needed. Optionally, you can override behavior by -different supported `captureMode` to record response, exception or both. +!!! note + When using the functional API with `TracingUtils.withSubsegment()`, response and exception capture is not automatic. You can manually add metadata using `TracingUtils.putMetadata()` as needed. -!!! warning "Returning sensitive information from your Lambda handler or functions, where `Tracing` is used?" - You can disable annotation from capturing their responses and exception as tracing metadata with **`captureMode=DISABLED`** - or globally by setting environment variables **`POWERTOOLS_TRACER_CAPTURE_RESPONSE`** and **`POWERTOOLS_TRACER_CAPTURE_ERROR`** to **`false`** +!!! warning "Returning sensitive information from your Lambda handler or functions?" + When using the `@Tracing` annotation, you can disable it from capturing responses and exceptions as tracing metadata with **`captureMode=DISABLED`** + or globally by setting the environment variables **`POWERTOOLS_TRACER_CAPTURE_RESPONSE`** and **`POWERTOOLS_TRACER_CAPTURE_ERROR`** to **`false`**. + When using the functional API, you have full control over what metadata is captured. -=== "Disable on annotation" +=== "@Tracing annotation - Disable on method" ```java hl_lines="3" public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { @@ -107,7 +209,7 @@ different supported `captureMode` to record response, exception or both. } ``` -=== "Disable Globally" +=== "@Tracing annotation - Disable Globally" ```yaml hl_lines="11 12" Resources: @@ -115,7 +217,7 @@ different supported `captureMode` to record response, exception or both. Type: AWS::Serverless::Function Properties: ... - Runtime: java8 + Runtime: java11 Tracing: Active Environment: @@ -124,6 +226,20 @@ different supported `captureMode` to record response, exception or both. POWERTOOLS_TRACER_CAPTURE_ERROR: false ``` +=== "Functional API" + + ```java hl_lines="6 7 8" + import software.amazon.lambda.powertools.tracing.TracingUtils; + + public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + TracingUtils.withSubsegment("businessLogic", subsegment -> { + // With functional API, you control what metadata is captured + }); + } + ``` + ### Annotations & Metadata **Annotations** are key-values associated with traces and indexed by AWS X-Ray. You can use them to filter traces and to @@ -196,32 +312,13 @@ specific fields from received event due to security. } ``` -## Utilities +## Advanced usage -Tracing modules comes with certain utility method when you don't want to use annotation for capturing a code block -under a subsegment, or you are doing multithreaded programming. Refer examples below. +### Multi-threaded programming -=== "Functional Api" +When working with multiple threads, you need to pass the trace entity to ensure proper trace context propagation. - ```java hl_lines="7 8 9 11 12 13" - import software.amazon.lambda.powertools.tracing.Tracing; - import software.amazon.lambda.powertools.tracing.TracingUtils; - - public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - - public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { - TracingUtils.withSubsegment("loggingResponse", subsegment -> { - // Some business logic - }); - - TracingUtils.withSubsegment("localNamespace", "loggingResponse", subsegment -> { - // Some business logic - }); - } - } - ``` - -=== "Multi Threaded Programming" +=== "Multi-threaded example" ```java hl_lines="7 9 10 11" import static software.amazon.lambda.powertools.tracing.TracingUtils.withEntitySubsegment; @@ -241,5 +338,112 @@ under a subsegment, or you are doing multithreaded programming. Refer examples b ## Instrumenting SDK clients and HTTP calls -User should make sure to instrument the SDK clients explicitly based on the function dependency. Refer details on -[how to instrument SDK client with Xray](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-java-awssdkclients.html) and [outgoing http calls](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-java-httpclients.html). +### AWS SDK for Java 2.x + +Powertools for AWS Lambda (Java) includes the `aws-xray-recorder-sdk-aws-sdk-v2-instrumentor` library, which **automatically instruments all AWS SDK v2 clients** when you add the `powertools-tracing` dependency to your project. This means downstream calls to AWS services are traced without any additional configuration. + +If you need more control over which clients are instrumented, you can manually add the `TracingInterceptor` to specific clients: + +=== "Manual instrumentation (optional)" + + ```java hl_lines="1 2 3 8 9 10 11" + import com.amazonaws.xray.interceptors.TracingInterceptor; + import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; + import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + + public class LambdaHandler { + private DynamoDbClient client = DynamoDbClient.builder() + .region(Region.US_WEST_2) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(new TracingInterceptor()) + .build() + ) + .build(); + // ... + } + ``` + +For more details, refer to the [AWS X-Ray documentation on tracing AWS SDK calls](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-java-awssdkclients.html) and [outgoing HTTP calls](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-java-httpclients.html). + +## Testing your code + +When using `@Tracing` annotation, your Junit test cases needs to be configured to create parent Segment required by [AWS X-Ray SDK for Java](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-java.html). + +Below are two ways in which you can configure your tests. + +#### Configure environment variable on project level (Recommended) + +You can choose to configure environment variable on project level for your test cases run. This is recommended approach as it will avoid the need of configuring each test case specifically. + +Below are examples configuring your maven/gradle projects. You can choose to configure it differently as well as long as you are making sure that environment variable `LAMBDA_TASK_ROOT` is set. This variable is +used internally via AWS X-Ray SDK to configure itself properly for lambda runtime. + +=== "Maven (pom.xml)" + + ```xml + <build> + ... + <plugins> + <!-- Configures environment variable to avoid initialization of AWS X-Ray segments for each tests--> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <LAMBDA_TASK_ROOT>handler</LAMBDA_TASK_ROOT> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> + + ``` + +=== "Gradle (build.gradle)" + + ```json + // Configures environment variable to avoid initialization of AWS X-Ray segments for each tests + test { + environment "LAMBDA_TASK_ROOT", "handler" + } + ``` + +#### Configure test cases (Not Recommended) + +You can choose to configure each of your test case instead as well if you choose not to configure environment variable on project level. +Below is an example configuration needed for each test case. + +=== "AppTest.java" + + ```java hl_lines="10 11 12 17 18 19 20 21 22 23 24" + import com.amazonaws.xray.AWSXRay; + import org.junit.After; + import org.junit.Before; + import org.junit.Test; + + public class AppTest { + + @Before + public void setup() { + if(null == System.getenv("LAMBDA_TASK_ROOT")) { + AWSXRay.beginSegment("test"); + } + } + + @After + public void tearDown() { + // Needed when using sam build --use-container + if (AWSXRay.getCurrentSubsegmentOptional().isPresent()) { + AWSXRay.endSubsegment(); + } + + if(null == System.getenv("LAMBDA_TASK_ROOT")) { + AWSXRay.endSegment(); + } + } + + @Test + public void successfulResponse() { + // test logic + } + ``` diff --git a/docs/index.md b/docs/index.md index 115edc566..655c16e03 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,14 +1,17 @@ --- title: Homepage -description: AWS Lambda Powertools Java +description: Powertools for AWS Lambda (Java) --- -![aws provider](https://img.shields.io/badge/provider-AWS-orange?logo=amazon-aws&color=ff9900) ![Build status](https://github.com/awslabs/aws-lambda-powertools-java/actions/workflows/build.yml/badge.svg) ![Maven Central](https://img.shields.io/maven-central/v/software.amazon.lambda/powertools-parent) +Powertools for AWS Lambda (Java) is a developer toolkit to implement Serverless best practices and increase developer velocity. -Powertools is a suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. +???+ tip + Powertools for AWS Lambda is also available for [Python](https://docs.powertools.aws.dev/lambda/python/latest/){target="_blank"}, [TypeScript](https://docs.powertools.aws.dev/lambda/typescript/latest/){target="_blank"}, and [.NET](https://docs.powertools.aws.dev/lambda/dotnet/){target="_blank"} -!!! tip "Looking for a quick run through of the core utilities?" - Check out [this detailed blog post](https://aws.amazon.com/blogs/opensource/simplifying-serverless-best-practices-with-aws-lambda-powertools-java/) with a practical example. + +???+ tip "Looking for a quick run through of the core utilities?" + Check out [this detailed blog post](https://aws.amazon.com/blogs/compute/introducing-v2-of-powertools-for-aws-lambda-java/) with a practical example. To dive deeper, + the [Powertools for AWS Lambda (Java) workshop](https://catalog.us-east-1.prod.workshops.aws/workshops/a7011c82-e4af-4a52-80fa-fcd61f1dacd9/en-US/introduction) is a great next step. ## Tenets @@ -23,24 +26,62 @@ This project separates core utilities that will be available in other runtimes v ## Install -Powertools dependencies are available in Maven Central. You can use your favourite dependency management tool to install it - -* [Maven](https://maven.apache.org/) -* [Gradle](https://gradle.org) - -**Quick hello world examples using SAM CLI** +<!-- TODO: Uncomment when SAM template is updated - https://github.com/aws-powertools/powertools-lambda-java/issues/1889 +**Quick hello world example using SAM CLI** -You can use [SAM](https://aws.amazon.com/serverless/sam/) to quickly setup a serverless project including AWS Lambda Powertools Java. +You can use [SAM](https://aws.amazon.com/serverless/sam/) to quickly setup a serverless project including Powertools for AWS Lambda (Java). ```bash - sam init --location gh:aws-samples/cookiecutter-aws-sam-powertools-java +sam init + +Which template source would you like to use? + 1 - AWS Quick Start Templates + 2 - Custom Template Location +Choice: 1 + +Choose an AWS Quick Start application template + 1 - Hello World Example + 2 - Data processing + 3 - Hello World Example with Powertools for AWS Lambda + 4 - Multi-step workflow + 5 - Scheduled task + 6 - Standalone function + 7 - Serverless API + 8 - Infrastructure event management + 9 - Lambda Response Streaming + 10 - Serverless Connector Hello World Example + 11 - Multi-step workflow with Connectors + 12 - Full Stack + 13 - Lambda EFS example + 14 - DynamoDB Example + 15 - Machine Learning +Template: 3 + +Which runtime would you like to use? + 1 - dotnet6 + 2 - java17 + 3 - java11 + 4 - java8.al2 + 5 - java8 + 6 - nodejs18.x + 7 - nodejs16.x + 8 - nodejs14.x + 9 - python3.9 + 10 - python3.8 + 11 - python3.7 + 12 - python3.10 +Runtime: 2, 3, 4 or 5 ``` +--> -For more information about the project and available options refer to this [repository](https://github.com/aws-samples/cookiecutter-aws-sam-powertools-java/blob/main/README.md) +Powertools for AWS Lambda (Java) dependencies are available in Maven Central. You can use your favourite dependency management tool to install it + +* [Maven](https://maven.apache.org/) +* [Gradle](https://gradle.org) === "Maven" - ```xml hl_lines="3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55" + ```xml <dependencies> ... <dependency> @@ -50,7 +91,7 @@ For more information about the project and available options refer to this [repo </dependency> <dependency> <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-logging</artifactId> + <artifactId>powertools-logging-log4j</artifactId> <version>{{ powertools.version }}</version> </dependency> <dependency> @@ -58,21 +99,28 @@ For more information about the project and available options refer to this [repo <artifactId>powertools-metrics</artifactId> <version>{{ powertools.version }}</version> </dependency> + <!-- Make sure to include AspectJ runtime compatible with plugin version --> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>1.9.22</version> + </dependency> ... </dependencies> ... - <!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project --> + <!-- Configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project --> + <!-- Note: This AspectJ configuration is not needed when using the functional approach --> <build> <plugins> ... <plugin> - <groupId>org.codehaus.mojo</groupId> + <groupId>dev.aspectj</groupId> <artifactId>aspectj-maven-plugin</artifactId> - <version>1.14.0</version> + <version>1.14</version> <configuration> - <source>1.8</source> - <target>1.8</target> - <complianceLevel>1.8</complianceLevel> + <source>11</source> <!-- or higher --> + <target>11</target> <!-- or higher --> + <complianceLevel>11</complianceLevel> <!-- or higher --> <aspectLibraries> <aspectLibrary> <groupId>software.amazon.lambda</groupId> @@ -88,6 +136,14 @@ For more information about the project and available options refer to this [repo </aspectLibrary> </aspectLibraries> </configuration> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <!-- AspectJ compiler version, in sync with runtime --> + <version>1.9.22</version> + </dependency> + </dependencies> <executions> <execution> <goals> @@ -104,32 +160,72 @@ For more information about the project and available options refer to this [repo === "Gradle" ```groovy - plugins{ - id 'java' - id 'io.freefair.aspectj.post-compile-weaving' version '6.3.0' - } - - repositories { - mavenCentral() - } - - dependencies { - aspect 'software.amazon.lambda:powertools-logging:{{ powertools.version }}' - aspect 'software.amazon.lambda:powertools-tracing:{{ powertools.version }}' - aspect 'software.amazon.lambda:powertools-metrics:{{ powertools.version }}' - } + + plugins { + id 'java' + id 'io.freefair.aspectj.post-compile-weaving' version '8.2.2' + } + + // the freefair aspect plugins targets gradle 8.2.1 + // https://docs.freefair.io/gradle-plugins/8.2.2/reference/ + wrapper { + gradleVersion = "8.2.1" + } + + repositories { + mavenCentral() + } + + dependencies { + // Note: This AspectJ configuration is not needed when using the functional approach + aspect 'software.amazon.lambda:powertools-logging-log4j:{{ powertools.version }}' + aspect 'software.amazon.lambda:powertools-tracing:{{ powertools.version }}' + aspect 'software.amazon.lambda:powertools-metrics:{{ powertools.version }}' + } + + sourceCompatibility = 11 + targetCompatibility = 11 ``` +???+ tip "Don't want to use AspectJ?" + Powertools for AWS Lambda (Java) now provides a functional API that doesn't require AspectJ configuration. Learn more about the [functional approach](./usage-patterns.md#functional-approach). + +### Java Compatibility +Powertools for AWS Lambda (Java) supports all Java versions from 11 to 25 in line with the [corresponding Lambda runtimes](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html). + +In addition to the functional approach, [Logging](./core/logging.md), [Metrics](./core/metrics.md), [Tracing](./core/tracing.md), [Parameters](./utilities/parameters.md), [Idempotency](./utilities/idempotency.md), [Validation](./utilities/validation.md), and [Large Messages](./utilities/large_messages.md) utilities support annotations using AspectJ, which require configuration of the `aspectjrt` runtime library. + +You may need to add the appropriate version of `aspectjrt` to your dependencies based on the JDK used for building your function: + +```xml +<dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>1.9.??</version> +</dependency> +``` + +Use the following [dependency matrix](https://github.com/eclipse-aspectj/aspectj/blob/master/docs/release/JavaVersionCompatibility.adoc) to understand which AspectJ version to use based on your JDK version: + +| JDK version | aspectj version | +|-------------|------------------------| +| `11-17` | `1.9.20.1` (or higher) | +| `21` | `1.9.21` (or higher) | +| `25` | `1.9.25` (or higher) | + ## Environment variables !!! info - **Explicit parameters take precedence over environment variables.** - -| Environment variable | Description | Utility | -| ------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -| **POWERTOOLS_SERVICE_NAME** | Sets service name used for tracing namespace, metrics dimension and structured logging | All | -| **POWERTOOLS_METRICS_NAMESPACE** | Sets namespace used for metrics | [Metrics](./core/metrics) | -| **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logging](./core/logging) | -| **POWERTOOLS_LOG_LEVEL** | Sets logging level | [Logging](./core/logging) | -| **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Enables/Disables tracing mode to capture method response | [Tracing](./core/tracing) | -| **POWERTOOLS_TRACER_CAPTURE_ERROR** | Enables/Disables tracing mode to capture method error | [Tracing](./core/tracing) | \ No newline at end of file + Explicit parameters take precedence over environment variables. + +| Environment variable | Description | Utility | +| -------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------- | +| **POWERTOOLS_SERVICE_NAME** | Sets service name used for tracing namespace, metrics dimension and structured logging | All | +| **POWERTOOLS_METRICS_NAMESPACE** | Sets namespace used for metrics | [Metrics](./core/metrics) | +| **POWERTOOLS_METRICS_FUNCTION_NAME** | Function name used as dimension for the cold start metric | [Metrics](./core/metrics) | +| **POWERTOOLS_METRICS_DISABLED** | Disables all flushing of metrics | [Metrics](./core/metrics) | +| **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logging](./core/logging) | +| **POWERTOOLS_LOG_LEVEL** | Sets logging level | [Logging](./core/logging) | +| **POWERTOOLS_LOGGER_LOG_EVENT** | Enables/Disables whether to log the incoming event when using the aspect | [Logging](./core/logging) | +| **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Enables/Disables tracing mode to capture method response | [Tracing](./core/tracing) | +| **POWERTOOLS_TRACER_CAPTURE_ERROR** | Enables/Disables tracing mode to capture method error | [Tracing](./core/tracing) | diff --git a/docs/javascript/aws-amplify.min.js b/docs/javascript/aws-amplify.min.js deleted file mode 100644 index f19b36a50..000000000 --- a/docs/javascript/aws-amplify.min.js +++ /dev/null @@ -1,108 +0,0 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("aws_amplify",[],t):"object"==typeof exports?exports.aws_amplify=t():e.aws_amplify=t()}(this,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(r,i,function(t){return e[t]}.bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=157)}([function(e,t,n){"use strict";n.d(t,"a",(function(){return a})),n.d(t,"b",(function(){return u})),n.d(t,"f",(function(){return c})),n.d(t,"g",(function(){return f})),n.d(t,"h",(function(){return l})),n.d(t,"c",(function(){return h})),n.d(t,"e",(function(){return g})),n.d(t,"d",(function(){return m}));var r=n(1),i=function(){var e=[],t=[],n=new Set,a=function(n){return e.forEach((function(e){n.add(e.middleware,Object(r.__assign)({},e))})),t.forEach((function(e){n.addRelativeTo(e.middleware,Object(r.__assign)({},e))})),n},u=function(e){var t=[];return e.before.forEach((function(e){0===e.before.length&&0===e.after.length?t.push(e):t.push.apply(t,Object(r.__spread)(u(e)))})),t.push(e),e.after.reverse().forEach((function(e){0===e.before.length&&0===e.after.length?t.push(e):t.push.apply(t,Object(r.__spread)(u(e)))})),t},c=function(){var n,i=[],a=[],c={};return e.forEach((function(e){var t=Object(r.__assign)(Object(r.__assign)({},e),{before:[],after:[]});t.name&&(c[t.name]=t),i.push(t)})),t.forEach((function(e){var t=Object(r.__assign)(Object(r.__assign)({},e),{before:[],after:[]});t.name&&(c[t.name]=t),a.push(t)})),a.forEach((function(e){if(e.toMiddleware){var t=c[e.toMiddleware];if(void 0===t)throw new Error(e.toMiddleware+" is not found when adding "+(e.name||"anonymous")+" middleware "+e.relation+" "+e.toMiddleware);"after"===e.relation&&t.after.push(e),"before"===e.relation&&t.before.push(e)}})),(n=i,n.sort((function(e,t){return o[t.step]-o[e.step]||s[t.priority||"normal"]-s[e.priority||"normal"]}))).map(u).reduce((function(e,t){return e.push.apply(e,Object(r.__spread)(t)),e}),[]).map((function(e){return e.middleware}))},f={add:function(t,i){void 0===i&&(i={});var o=i.name,s=Object(r.__assign)({step:"initialize",priority:"normal",middleware:t},i);if(o){if(n.has(o))throw new Error("Duplicate middleware name '"+o+"'");n.add(o)}e.push(s)},addRelativeTo:function(e,i){var o=i.name,s=Object(r.__assign)({middleware:e},i);if(o){if(n.has(o))throw new Error("Duplicated middleware name '"+o+"'");n.add(o)}t.push(s)},clone:function(){return a(i())},use:function(e){e.applyToStack(f)},remove:function(r){return"string"==typeof r?function(r){var i=!1,o=function(e){return!e.name||e.name!==r||(i=!0,n.delete(r),!1)};return e=e.filter(o),t=t.filter(o),i}(r):function(r){var i=!1,o=function(e){return e.middleware!==r||(i=!0,e.name&&n.delete(e.name),!1)};return e=e.filter(o),t=t.filter(o),i}(r)},removeByTag:function(r){var i=!1,o=function(e){var t=e.tags,o=e.name;return!t||!t.includes(r)||(o&&n.delete(o),i=!0,!1)};return e=e.filter(o),t=t.filter(o),i},concat:function(e){var t=a(i());return t.use(e),t},applyToStack:a,resolve:function(e,t){var n,i;try{for(var o=Object(r.__values)(c().reverse()),s=o.next();!s.done;s=o.next()){e=(0,s.value)(e,t)}}catch(e){n={error:e}}finally{try{s&&!s.done&&(i=o.return)&&i.call(o)}finally{if(n)throw n.error}}return e}};return f},o={initialize:5,serialize:4,build:3,finalizeRequest:2,deserialize:1},s={high:3,normal:2,low:1},a=function(){function e(e){this.middlewareStack=i(),this.config=e}return e.prototype.send=function(e,t,n){var r="function"!=typeof t?t:void 0,i="function"==typeof t?t:n,o=e.resolveMiddleware(this.middlewareStack,this.config,r);if(!i)return o(e).then((function(e){return e.output}));o(e).then((function(e){return i(null,e.output)}),(function(e){return i(e)})).catch((function(){}))},e.prototype.destroy=function(){this.config.requestHandler.destroy&&this.config.requestHandler.destroy()},e}(),u=function(){this.middlewareStack=i()};function c(e){return encodeURIComponent(e).replace(/[!'()*]/g,(function(e){return"%"+e.charCodeAt(0).toString(16)}))}var f=function(e){return Array.isArray(e)?e:[e]},l=function(e){for(var t in e)e.hasOwnProperty(t)&&void 0!==e[t]["#text"]?e[t]=e[t]["#text"]:"object"==typeof e[t]&&null!==e[t]&&(e[t]=l(e[t]));return e},d=function(){var e=Object.getPrototypeOf(this).constructor,t=Function.bind.apply(String,Object(r.__spread)([null],arguments)),n=new t;return Object.setPrototypeOf(n,e.prototype),n};d.prototype=Object.create(String.prototype,{constructor:{value:d,enumerable:!1,writable:!0,configurable:!0}}),Object.setPrototypeOf(d,String);var h=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(r.__extends)(t,e),t.prototype.deserializeJSON=function(){return JSON.parse(e.prototype.toString.call(this))},t.prototype.toJSON=function(){return e.prototype.toString.call(this)},t.fromObject=function(e){return e instanceof t?e:new t(e instanceof String||"string"==typeof e?e:JSON.stringify(e))},t}(d),p=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],v=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function g(e){var t=e.getUTCFullYear(),n=e.getUTCMonth(),r=e.getUTCDay(),i=e.getUTCDate(),o=e.getUTCHours(),s=e.getUTCMinutes(),a=e.getUTCSeconds();return p[r]+", "+(i<10?"0"+i:""+i)+" "+v[n]+" "+t+" "+(o<10?"0"+o:""+o)+":"+(s<10?"0"+s:""+s)+":"+(a<10?"0"+a:""+a)+" GMT"}var m="***SensitiveInformation***"},function(e,t,n){"use strict";n.r(t),n.d(t,"__extends",(function(){return i})),n.d(t,"__assign",(function(){return o})),n.d(t,"__rest",(function(){return s})),n.d(t,"__decorate",(function(){return a})),n.d(t,"__param",(function(){return u})),n.d(t,"__metadata",(function(){return c})),n.d(t,"__awaiter",(function(){return f})),n.d(t,"__generator",(function(){return l})),n.d(t,"__createBinding",(function(){return d})),n.d(t,"__exportStar",(function(){return h})),n.d(t,"__values",(function(){return p})),n.d(t,"__read",(function(){return v})),n.d(t,"__spread",(function(){return g})),n.d(t,"__spreadArrays",(function(){return m})),n.d(t,"__await",(function(){return b})),n.d(t,"__asyncGenerator",(function(){return y})),n.d(t,"__asyncDelegator",(function(){return w})),n.d(t,"__asyncValues",(function(){return _})),n.d(t,"__makeTemplateObject",(function(){return S})),n.d(t,"__importStar",(function(){return E})),n.d(t,"__importDefault",(function(){return M})),n.d(t,"__classPrivateFieldGet",(function(){return A})),n.d(t,"__classPrivateFieldSet",(function(){return I})); -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -***************************************************************************** */ -var r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)};function i(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var o=function(){return(o=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};function s(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]])}return n}function a(e,t,n,r){var i,o=arguments.length,s=o<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,n):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,n,r);else for(var a=e.length-1;a>=0;a--)(i=e[a])&&(s=(o<3?i(s):o>3?i(t,n,s):i(t,n))||s);return o>3&&s&&Object.defineProperty(t,n,s),s}function u(e,t){return function(n,r){t(n,r,e)}}function c(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}function f(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))}function l(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}}function d(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}function h(e,t){for(var n in e)"default"===n||t.hasOwnProperty(n)||(t[n]=e[n])}function p(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function v(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s}function g(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(v(arguments[t]));return e}function m(){for(var e=0,t=0,n=arguments.length;t<n;t++)e+=arguments[t].length;var r=Array(e),i=0;for(t=0;t<n;t++)for(var o=arguments[t],s=0,a=o.length;s<a;s++,i++)r[i]=o[s];return r}function b(e){return this instanceof b?(this.v=e,this):new b(e)}function y(e,t,n){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var r,i=n.apply(e,t||[]),o=[];return r={},s("next"),s("throw"),s("return"),r[Symbol.asyncIterator]=function(){return this},r;function s(e){i[e]&&(r[e]=function(t){return new Promise((function(n,r){o.push([e,t,n,r])>1||a(e,t)}))})}function a(e,t){try{(n=i[e](t)).value instanceof b?Promise.resolve(n.value.v).then(u,c):f(o[0][2],n)}catch(e){f(o[0][3],e)}var n}function u(e){a("next",e)}function c(e){a("throw",e)}function f(e,t){e(t),o.shift(),o.length&&a(o[0][0],o[0][1])}}function w(e){var t,n;return t={},r("next"),r("throw",(function(e){throw e})),r("return"),t[Symbol.iterator]=function(){return this},t;function r(r,i){t[r]=e[r]?function(t){return(n=!n)?{value:b(e[r](t)),done:"return"===r}:i?i(t):t}:i}}function _(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,n=e[Symbol.asyncIterator];return n?n.call(e):(e=p(e),t={},r("next"),r("throw"),r("return"),t[Symbol.asyncIterator]=function(){return this},t);function r(n){t[n]=e[n]&&function(t){return new Promise((function(r,i){(function(e,t,n,r){Promise.resolve(r).then((function(t){e({value:t,done:n})}),t)})(r,i,(t=e[n](t)).done,t.value)}))}}}function S(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e}function E(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}function M(e){return e&&e.__esModule?e:{default:e}}function A(e,t){if(!t.has(e))throw new TypeError("attempted to get private field on non-instance");return t.get(e)}function I(e,t,n){if(!t.has(e))throw new TypeError("attempted to set private field on non-instance");return t.set(e,n),n}},function(e,t,n){"use strict";n.d(t,"b",(function(){return r})),n.d(t,"a",(function(){return o}));var r=function(){function e(e){this.statusCode=e.statusCode,this.headers=e.headers||{},this.body=e.body}return e.isInstance=function(e){if(!e)return!1;var t=e;return"number"==typeof t.statusCode&&"object"==typeof t.headers},e}(),i=n(1),o=function(){function e(e){this.method=e.method||"GET",this.hostname=e.hostname||"localhost",this.port=e.port,this.query=e.query||{},this.headers=e.headers||{},this.body=e.body,this.protocol=e.protocol?":"!==e.protocol.substr(-1)?e.protocol+":":e.protocol:"https:",this.path=e.path?"/"!==e.path.charAt(0)?"/"+e.path:e.path:"/"}return e.isInstance=function(e){if(!e)return!1;var t=e;return"method"in t&&"protocol"in t&&"hostname"in t&&"path"in t&&"object"==typeof t.query&&"object"==typeof t.headers},e.prototype.clone=function(){var t,n=new e(Object(i.__assign)(Object(i.__assign)({},this),{headers:Object(i.__assign)({},this.headers)}));return n.query&&(n.query=(t=n.query,Object.keys(t).reduce((function(e,n){var r,o=t[n];return Object(i.__assign)(Object(i.__assign)({},e),((r={})[n]=Array.isArray(o)?Object(i.__spread)(o):o,r))}),{}))),n},e}()},function(e,t,n){"use strict";n.d(t,"f",(function(){return A})),n.d(t,"t",(function(){return I})),n.d(t,"y",(function(){return k})),n.d(t,"s",(function(){return x})),n.d(t,"e",(function(){return C})),n.d(t,"x",(function(){return P})),n.d(t,"g",(function(){return N})),n.d(t,"h",(function(){return R})),n.d(t,"d",(function(){return D})),n.d(t,"c",(function(){return U})),n.d(t,"b",(function(){return B})),n.d(t,"a",(function(){return j})),n.d(t,"u",(function(){return F})),n.d(t,"v",(function(){return q})),n.d(t,"i",(function(){return K})),n.d(t,"w",(function(){return H})),n.d(t,"j",(function(){return V})),n.d(t,"p",(function(){return G})),n.d(t,"k",(function(){return W})),n.d(t,"q",(function(){return $})),n.d(t,"l",(function(){return Y})),n.d(t,"n",(function(){return J})),n.d(t,"r",(function(){return Z})),n.d(t,"o",(function(){return X})),n.d(t,"m",(function(){return Q}));var r=n(6),i=n(32),o=n.n(i);function s(e){var t=new Error(e);return t.source="ulid",t}var a="0123456789ABCDEFGHJKMNPQRSTVWXYZ",u=a.length,c=Math.pow(2,48)-1;function f(e,t,n){return t>e.length-1?e:e.substr(0,t)+n+e.substr(t+1)}function l(e){var t=Math.floor(e()*u);return t===u&&(t=u-1),a.charAt(t)}function d(e,t){if(isNaN(e))throw new Error(e+" must be a number");if(e>c)throw s("cannot encode time greater than "+c);if(e<0)throw s("time must be positive");if(!1===Number.isInteger(e))throw s("time must be an integer");for(var n=void 0,r="";t>0;t--)r=a.charAt(n=e%u)+r,e=(e-n)/u;return r}function h(e,t){for(var n="";e>0;e--)n=l(t)+n;return n}function p(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=arguments[1];t||(t="undefined"!=typeof window?window:null);var r=t&&(t.crypto||t.msCrypto);if(r)return function(){var e=new Uint8Array(1);return r.getRandomValues(e),e[0]/255};try{var i=n(485);return function(){return i.randomBytes(1).readUInt8()/255}}catch(e){}if(e){try{console.error("secure crypto unusable, falling back to insecure Math.random()!")}catch(e){}return function(){return Math.random()}}throw s("secure crypto unusable, insecure Math.random not allowed")}function v(e){e||(e=p());var t=0,n=void 0;return function(r){if(isNaN(r)&&(r=Date.now()),r<=t){var i=n=function(e){for(var t=void 0,n=e.length,r=void 0,i=void 0,o=u-1;!t&&n-- >=0;){if(r=e[n],-1===(i=a.indexOf(r)))throw s("incorrectly encoded string");i!==o?t=f(e,n,a[i+1]):e=f(e,n,a[0])}if("string"==typeof t)return t;throw s("cannot increment this string")}(n);return d(t,10)+i}t=r;var o=n=h(16,e);return d(r,10)+o}}g||(g=p());var g,m=n(109),b=n(4);function y(e){return(y="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var w,_=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},S=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},E=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},M=function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},A=function(e,t){if(void 0===t&&(t=!0),t)throw new Error("Invalid "+e)},I=function(e){return void 0===e||null==e},k=function e(t,n,r){var i,o=!1;if(0===r.length)return!0;switch(n){case"not":i="every",o=!0;break;case"and":i="every";break;case"or":i="some";break;default:A(n)}var s=r[i]((function(n){if(Object(b.k)(n)){var r=n.field,i=n.operator,o=n.operand,s=t[r];return O(s,i,o)}if(Object(b.j)(n)){var a=n.type,u=n.predicates;return e(t,a,u)}throw new Error("Not a predicate or group")}));return o?!s:s},O=function(e,t,n){switch(t){case"ne":return e!==n;case"eq":return e===n;case"le":return e<=n;case"lt":return e<n;case"ge":return e>=n;case"gt":return e>n;case"between":var r=E(n,2),i=r[0],o=r[1];return e>=i&&e<=o;case"beginsWith":return e.startsWith(n);case"contains":return e.indexOf(n)>-1;case"notContains":return-1===e.indexOf(n);default:return A(t,!1),!1}},x=function(e){return e&&"function"==typeof e.copyOf},C=function(e){var t={};return Object.keys(e.models).forEach((function(n){t[n]={indexes:[],relationTypes:[]};var r=e.models[n];Object.keys(r.fields).forEach((function(e){var i=r.fields[e];if("object"===y(i.type)&&"model"in i.type){var o=i.association.connectionType;t[n].relationTypes.push({fieldName:i.name,modelName:i.type.model,relationType:o,targetName:i.association.targetName,associatedWith:i.association.associatedWith}),"BELONGS_TO"===o&&t[n].indexes.push(i.association.targetName)}})),r.attributes&&r.attributes.forEach((function(e){if("key"===e.type){var r=e.properties.fields;r&&r.forEach((function(e){t[n].indexes.includes(e)||t[n].indexes.push(e)}))}}))})),t},T=new WeakMap,P=function(e,t,n,r,i){var o=n.relationships,s=i(n.name,e),a=o[e],u=[],c=s.copyOf(t,(function(e){a.relationTypes.forEach((function(o){var s=i(n.name,o.modelName);switch(o.relationType){case"HAS_ONE":if(t[o.fieldName]){var a=void 0;try{a=r(s,t[o.fieldName])}catch(e){}u.push({modelName:o.modelName,item:t[o.fieldName],instance:a}),e[o.fieldName]=e[o.fieldName].id}break;case"BELONGS_TO":if(t[o.fieldName]){a=void 0;try{a=r(s,t[o.fieldName])}catch(e){}e[o.fieldName]._deleted||u.push({modelName:o.modelName,item:t[o.fieldName],instance:a})}e[o.targetName]=e[o.fieldName]?e[o.fieldName].id:null,delete e[o.fieldName];break;case"HAS_MANY":break;default:A(o.relationType)}}))}));u.unshift({modelName:e,item:c,instance:c}),T.has(n)||T.set(n,Array.from(n.modelTopologicalOrdering.keys()));var f=T.get(n);return u.sort((function(e,t){return f.indexOf(e.modelName)-f.indexOf(t.modelName)})),u},N=function(e,t){var n="";return e.some((function(e){e.modelName===t&&(n=e.targetName)})),n},R=function(e,t){return e.find((function(e){return e===t}))};!function(e){e.DATASTORE="datastore",e.USER="user",e.SYNC="sync",e.STORAGE="storage"}(w||(w={}));var L,j=w.DATASTORE,D=w.USER,U=w.SYNC,B=w.STORAGE,F=function(){return new Promise((function(e){var t,n=Object(m.v4)(),r=function(){L=!1,e(!0)},i=function(){return _(void 0,void 0,void 0,(function(){return S(this,(function(r){switch(r.label){case 0:return t&&t.result&&"function"==typeof t.result.close?[4,t.result.close()]:[3,2];case 1:r.sent(),r.label=2;case 2:return[4,indexedDB.deleteDatabase(n)];case 3:return r.sent(),L=!0,[2,e(!1)]}}))}))};return!0===L?i():!1===L||null===indexedDB?r():((t=indexedDB.open(n)).onerror=r,void(t.onsuccess=i))}))},z=function(){return(e=1,r.Buffer.from(o.a.lib.WordArray.random(e).toString(),"hex")).readUInt8(0)/255;var e};function q(e){var t=v(z);return function(){return t(e)}}function K(){return"undefined"!=typeof performance&&performance&&"function"==typeof performance.now?0|performance.now():Date.now()}function H(e){return function(t,n){var r,i;try{for(var o=M(e),s=o.next();!s.done;s=o.next()){var a=s.value,u=a.field,c=a.sortDirection===b.e.ASCENDING?1:-1;if(t[u]<n[u])return-1*c;if(t[u]>n[u])return 1*c}}catch(e){r={error:e}}finally{try{s&&!s.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return 0}}var V=function(e){return!!/^\d{4}-\d{2}-\d{2}(Z|[+-]\d{2}:\d{2}($|:\d{2}))?$/.exec(e)},G=function(e){return!!/^\d{2}:\d{2}(:\d{2}(.\d+)?)?(Z|[+-]\d{2}:\d{2}($|:\d{2}))?$/.exec(e)},W=function(e){return!!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(.\d+)?)?(Z|[+-]\d{2}:\d{2}($|:\d{2}))?$/.exec(e)},$=function(e){return!!/^\d+$/.exec(String(e))},Y=function(e){return!!/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.exec(e)},J=function(e){try{return JSON.parse(e),!0}catch(e){return!1}},Z=function(e){try{return!!new URL(e)}catch(e){return!1}},X=function(e){return!!/^\+?\d[\d\s-]+$/.exec(e)},Q=function(e){return!!/((^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?$))$/.exec(e)}},function(e,t,n){"use strict";n.d(t,"l",(function(){return f})),n.d(t,"m",(function(){return l})),n.d(t,"b",(function(){return r})),n.d(t,"g",(function(){return d})),n.d(t,"h",(function(){return h})),n.d(t,"i",(function(){return p})),n.d(t,"f",(function(){return v})),n.d(t,"c",(function(){return i})),n.d(t,"k",(function(){return g})),n.d(t,"j",(function(){return m})),n.d(t,"d",(function(){return o})),n.d(t,"e",(function(){return s})),n.d(t,"n",(function(){return b})),n.d(t,"a",(function(){return y}));var r,i,o,s,a=n(3),u=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},c=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}};function f(e){return e&&void 0!==e.pluralName}function l(e){return e&&e.targetName}function d(e){return e&&void 0!==r[e]}function h(e){return!(!e||!e.model)}function p(e){return!(!e||!e.nonModel)}function v(e){return!(!e||!e.enum)}function g(e){return e&&void 0!==e.field}function m(e){return e&&void 0!==e.type}function b(e,t){return u(this,void 0,void 0,(function(){return c(this,(function(n){return[2,{modelConstructor:e,conditionProducer:t}]}))}))}!function(e){e[e.ID=0]="ID",e[e.String=1]="String",e[e.Int=2]="Int",e[e.Float=3]="Float",e[e.Boolean=4]="Boolean",e[e.AWSDate=5]="AWSDate",e[e.AWSTime=6]="AWSTime",e[e.AWSDateTime=7]="AWSDateTime",e[e.AWSTimestamp=8]="AWSTimestamp",e[e.AWSEmail=9]="AWSEmail",e[e.AWSJSON=10]="AWSJSON",e[e.AWSURL=11]="AWSURL",e[e.AWSPhone=12]="AWSPhone",e[e.AWSIPAddress=13]="AWSIPAddress"}(r||(r={})),function(e){e.getJSType=function(e){switch(e){case"Boolean":return"boolean";case"ID":case"String":case"AWSDate":case"AWSTime":case"AWSDateTime":case"AWSEmail":case"AWSJSON":case"AWSURL":case"AWSPhone":case"AWSIPAddress":return"string";case"Int":case"Float":case"AWSTimestamp":return"number";default:Object(a.f)(e)}},e.getValidationFunction=function(e){switch(e){case"AWSDate":return a.j;case"AWSTime":return a.p;case"AWSDateTime":return a.k;case"AWSTimestamp":return a.q;case"AWSEmail":return a.l;case"AWSJSON":return a.n;case"AWSURL":return a.r;case"AWSPhone":return a.o;case"AWSIPAddress":return a.m;default:return}}}(r||(r={})),function(e){e.INSERT="INSERT",e.UPDATE="UPDATE",e.DELETE="DELETE"}(i||(i={})),function(e){e[e.FIRST=0]="FIRST",e[e.LAST=1]="LAST"}(o||(o={})),function(e){e.ASCENDING="ASCENDING",e.DESCENDING="DESCENDING"}(s||(s={}));var y=Symbol("DISCARD")},function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));var r=n(19),i=n(50),o=(n(142),n(89),{userAgent:i.a.userAgent});r.a},function(e,t,n){"use strict";(function(e){ -/*! - * The buffer module from node.js, for the browser. - * - * @author Feross Aboukhadijeh <http://feross.org> - * @license MIT - */ -var r=n(269),i=n(270),o=n(160);function s(){return u.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function a(e,t){if(s()<t)throw new RangeError("Invalid typed array length");return u.TYPED_ARRAY_SUPPORT?(e=new Uint8Array(t)).__proto__=u.prototype:(null===e&&(e=new u(t)),e.length=t),e}function u(e,t,n){if(!(u.TYPED_ARRAY_SUPPORT||this instanceof u))return new u(e,t,n);if("number"==typeof e){if("string"==typeof t)throw new Error("If encoding is specified then the first argument must be a string");return l(this,e)}return c(this,e,t,n)}function c(e,t,n,r){if("number"==typeof t)throw new TypeError('"value" argument must not be a number');return"undefined"!=typeof ArrayBuffer&&t instanceof ArrayBuffer?function(e,t,n,r){if(t.byteLength,n<0||t.byteLength<n)throw new RangeError("'offset' is out of bounds");if(t.byteLength<n+(r||0))throw new RangeError("'length' is out of bounds");t=void 0===n&&void 0===r?new Uint8Array(t):void 0===r?new Uint8Array(t,n):new Uint8Array(t,n,r);u.TYPED_ARRAY_SUPPORT?(e=t).__proto__=u.prototype:e=d(e,t);return e}(e,t,n,r):"string"==typeof t?function(e,t,n){"string"==typeof n&&""!==n||(n="utf8");if(!u.isEncoding(n))throw new TypeError('"encoding" must be a valid string encoding');var r=0|p(t,n),i=(e=a(e,r)).write(t,n);i!==r&&(e=e.slice(0,i));return e}(e,t,n):function(e,t){if(u.isBuffer(t)){var n=0|h(t.length);return 0===(e=a(e,n)).length||t.copy(e,0,0,n),e}if(t){if("undefined"!=typeof ArrayBuffer&&t.buffer instanceof ArrayBuffer||"length"in t)return"number"!=typeof t.length||(r=t.length)!=r?a(e,0):d(e,t);if("Buffer"===t.type&&o(t.data))return d(e,t.data)}var r;throw new TypeError("First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.")}(e,t)}function f(e){if("number"!=typeof e)throw new TypeError('"size" argument must be a number');if(e<0)throw new RangeError('"size" argument must not be negative')}function l(e,t){if(f(t),e=a(e,t<0?0:0|h(t)),!u.TYPED_ARRAY_SUPPORT)for(var n=0;n<t;++n)e[n]=0;return e}function d(e,t){var n=t.length<0?0:0|h(t.length);e=a(e,n);for(var r=0;r<n;r+=1)e[r]=255&t[r];return e}function h(e){if(e>=s())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+s().toString(16)+" bytes");return 0|e}function p(e,t){if(u.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return F(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return z(e).length;default:if(r)return F(e).length;t=(""+t).toLowerCase(),r=!0}}function v(e,t,n){var r=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return x(this,t,n);case"utf8":case"utf-8":return I(this,t,n);case"ascii":return k(this,t,n);case"latin1":case"binary":return O(this,t,n);case"base64":return A(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return C(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}function g(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function m(e,t,n,r,i){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=i?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(i)return-1;n=e.length-1}else if(n<0){if(!i)return-1;n=0}if("string"==typeof t&&(t=u.from(t,r)),u.isBuffer(t))return 0===t.length?-1:b(e,t,n,r,i);if("number"==typeof t)return t&=255,u.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):b(e,[t],n,r,i);throw new TypeError("val must be string, number or Buffer")}function b(e,t,n,r,i){var o,s=1,a=e.length,u=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;s=2,a/=2,u/=2,n/=2}function c(e,t){return 1===s?e[t]:e.readUInt16BE(t*s)}if(i){var f=-1;for(o=n;o<a;o++)if(c(e,o)===c(t,-1===f?0:o-f)){if(-1===f&&(f=o),o-f+1===u)return f*s}else-1!==f&&(o-=o-f),f=-1}else for(n+u>a&&(n=a-u),o=n;o>=0;o--){for(var l=!0,d=0;d<u;d++)if(c(e,o+d)!==c(t,d)){l=!1;break}if(l)return o}return-1}function y(e,t,n,r){n=Number(n)||0;var i=e.length-n;r?(r=Number(r))>i&&(r=i):r=i;var o=t.length;if(o%2!=0)throw new TypeError("Invalid hex string");r>o/2&&(r=o/2);for(var s=0;s<r;++s){var a=parseInt(t.substr(2*s,2),16);if(isNaN(a))return s;e[n+s]=a}return s}function w(e,t,n,r){return q(F(t,e.length-n),e,n,r)}function _(e,t,n,r){return q(function(e){for(var t=[],n=0;n<e.length;++n)t.push(255&e.charCodeAt(n));return t}(t),e,n,r)}function S(e,t,n,r){return _(e,t,n,r)}function E(e,t,n,r){return q(z(t),e,n,r)}function M(e,t,n,r){return q(function(e,t){for(var n,r,i,o=[],s=0;s<e.length&&!((t-=2)<0);++s)n=e.charCodeAt(s),r=n>>8,i=n%256,o.push(i),o.push(r);return o}(t,e.length-n),e,n,r)}function A(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function I(e,t,n){n=Math.min(e.length,n);for(var r=[],i=t;i<n;){var o,s,a,u,c=e[i],f=null,l=c>239?4:c>223?3:c>191?2:1;if(i+l<=n)switch(l){case 1:c<128&&(f=c);break;case 2:128==(192&(o=e[i+1]))&&(u=(31&c)<<6|63&o)>127&&(f=u);break;case 3:o=e[i+1],s=e[i+2],128==(192&o)&&128==(192&s)&&(u=(15&c)<<12|(63&o)<<6|63&s)>2047&&(u<55296||u>57343)&&(f=u);break;case 4:o=e[i+1],s=e[i+2],a=e[i+3],128==(192&o)&&128==(192&s)&&128==(192&a)&&(u=(15&c)<<18|(63&o)<<12|(63&s)<<6|63&a)>65535&&u<1114112&&(f=u)}null===f?(f=65533,l=1):f>65535&&(f-=65536,r.push(f>>>10&1023|55296),f=56320|1023&f),r.push(f),i+=l}return function(e){var t=e.length;if(t<=4096)return String.fromCharCode.apply(String,e);var n="",r=0;for(;r<t;)n+=String.fromCharCode.apply(String,e.slice(r,r+=4096));return n}(r)}t.Buffer=u,t.SlowBuffer=function(e){+e!=e&&(e=0);return u.alloc(+e)},t.INSPECT_MAX_BYTES=50,u.TYPED_ARRAY_SUPPORT=void 0!==e.TYPED_ARRAY_SUPPORT?e.TYPED_ARRAY_SUPPORT:function(){try{var e=new Uint8Array(1);return e.__proto__={__proto__:Uint8Array.prototype,foo:function(){return 42}},42===e.foo()&&"function"==typeof e.subarray&&0===e.subarray(1,1).byteLength}catch(e){return!1}}(),t.kMaxLength=s(),u.poolSize=8192,u._augment=function(e){return e.__proto__=u.prototype,e},u.from=function(e,t,n){return c(null,e,t,n)},u.TYPED_ARRAY_SUPPORT&&(u.prototype.__proto__=Uint8Array.prototype,u.__proto__=Uint8Array,"undefined"!=typeof Symbol&&Symbol.species&&u[Symbol.species]===u&&Object.defineProperty(u,Symbol.species,{value:null,configurable:!0})),u.alloc=function(e,t,n){return function(e,t,n,r){return f(t),t<=0?a(e,t):void 0!==n?"string"==typeof r?a(e,t).fill(n,r):a(e,t).fill(n):a(e,t)}(null,e,t,n)},u.allocUnsafe=function(e){return l(null,e)},u.allocUnsafeSlow=function(e){return l(null,e)},u.isBuffer=function(e){return!(null==e||!e._isBuffer)},u.compare=function(e,t){if(!u.isBuffer(e)||!u.isBuffer(t))throw new TypeError("Arguments must be Buffers");if(e===t)return 0;for(var n=e.length,r=t.length,i=0,o=Math.min(n,r);i<o;++i)if(e[i]!==t[i]){n=e[i],r=t[i];break}return n<r?-1:r<n?1:0},u.isEncoding=function(e){switch(String(e).toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"latin1":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return!0;default:return!1}},u.concat=function(e,t){if(!o(e))throw new TypeError('"list" argument must be an Array of Buffers');if(0===e.length)return u.alloc(0);var n;if(void 0===t)for(t=0,n=0;n<e.length;++n)t+=e[n].length;var r=u.allocUnsafe(t),i=0;for(n=0;n<e.length;++n){var s=e[n];if(!u.isBuffer(s))throw new TypeError('"list" argument must be an Array of Buffers');s.copy(r,i),i+=s.length}return r},u.byteLength=p,u.prototype._isBuffer=!0,u.prototype.swap16=function(){var e=this.length;if(e%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(var t=0;t<e;t+=2)g(this,t,t+1);return this},u.prototype.swap32=function(){var e=this.length;if(e%4!=0)throw new RangeError("Buffer size must be a multiple of 32-bits");for(var t=0;t<e;t+=4)g(this,t,t+3),g(this,t+1,t+2);return this},u.prototype.swap64=function(){var e=this.length;if(e%8!=0)throw new RangeError("Buffer size must be a multiple of 64-bits");for(var t=0;t<e;t+=8)g(this,t,t+7),g(this,t+1,t+6),g(this,t+2,t+5),g(this,t+3,t+4);return this},u.prototype.toString=function(){var e=0|this.length;return 0===e?"":0===arguments.length?I(this,0,e):v.apply(this,arguments)},u.prototype.equals=function(e){if(!u.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===u.compare(this,e)},u.prototype.inspect=function(){var e="",n=t.INSPECT_MAX_BYTES;return this.length>0&&(e=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(e+=" ... ")),"<Buffer "+e+">"},u.prototype.compare=function(e,t,n,r,i){if(!u.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===i&&(i=this.length),t<0||n>e.length||r<0||i>this.length)throw new RangeError("out of range index");if(r>=i&&t>=n)return 0;if(r>=i)return-1;if(t>=n)return 1;if(this===e)return 0;for(var o=(i>>>=0)-(r>>>=0),s=(n>>>=0)-(t>>>=0),a=Math.min(o,s),c=this.slice(r,i),f=e.slice(t,n),l=0;l<a;++l)if(c[l]!==f[l]){o=c[l],s=f[l];break}return o<s?-1:s<o?1:0},u.prototype.includes=function(e,t,n){return-1!==this.indexOf(e,t,n)},u.prototype.indexOf=function(e,t,n){return m(this,e,t,n,!0)},u.prototype.lastIndexOf=function(e,t,n){return m(this,e,t,n,!1)},u.prototype.write=function(e,t,n,r){if(void 0===t)r="utf8",n=this.length,t=0;else if(void 0===n&&"string"==typeof t)r=t,n=this.length,t=0;else{if(!isFinite(t))throw new Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");t|=0,isFinite(n)?(n|=0,void 0===r&&(r="utf8")):(r=n,n=void 0)}var i=this.length-t;if((void 0===n||n>i)&&(n=i),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var o=!1;;)switch(r){case"hex":return y(this,e,t,n);case"utf8":case"utf-8":return w(this,e,t,n);case"ascii":return _(this,e,t,n);case"latin1":case"binary":return S(this,e,t,n);case"base64":return E(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return M(this,e,t,n);default:if(o)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),o=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};function k(e,t,n){var r="";n=Math.min(e.length,n);for(var i=t;i<n;++i)r+=String.fromCharCode(127&e[i]);return r}function O(e,t,n){var r="";n=Math.min(e.length,n);for(var i=t;i<n;++i)r+=String.fromCharCode(e[i]);return r}function x(e,t,n){var r=e.length;(!t||t<0)&&(t=0),(!n||n<0||n>r)&&(n=r);for(var i="",o=t;o<n;++o)i+=B(e[o]);return i}function C(e,t,n){for(var r=e.slice(t,n),i="",o=0;o<r.length;o+=2)i+=String.fromCharCode(r[o]+256*r[o+1]);return i}function T(e,t,n){if(e%1!=0||e<0)throw new RangeError("offset is not uint");if(e+t>n)throw new RangeError("Trying to access beyond buffer length")}function P(e,t,n,r,i,o){if(!u.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>i||t<o)throw new RangeError('"value" argument is out of bounds');if(n+r>e.length)throw new RangeError("Index out of range")}function N(e,t,n,r){t<0&&(t=65535+t+1);for(var i=0,o=Math.min(e.length-n,2);i<o;++i)e[n+i]=(t&255<<8*(r?i:1-i))>>>8*(r?i:1-i)}function R(e,t,n,r){t<0&&(t=4294967295+t+1);for(var i=0,o=Math.min(e.length-n,4);i<o;++i)e[n+i]=t>>>8*(r?i:3-i)&255}function L(e,t,n,r,i,o){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function j(e,t,n,r,o){return o||L(e,0,n,4),i.write(e,t,n,r,23,4),n+4}function D(e,t,n,r,o){return o||L(e,0,n,8),i.write(e,t,n,r,52,8),n+8}u.prototype.slice=function(e,t){var n,r=this.length;if((e=~~e)<0?(e+=r)<0&&(e=0):e>r&&(e=r),(t=void 0===t?r:~~t)<0?(t+=r)<0&&(t=0):t>r&&(t=r),t<e&&(t=e),u.TYPED_ARRAY_SUPPORT)(n=this.subarray(e,t)).__proto__=u.prototype;else{var i=t-e;n=new u(i,void 0);for(var o=0;o<i;++o)n[o]=this[o+e]}return n},u.prototype.readUIntLE=function(e,t,n){e|=0,t|=0,n||T(e,t,this.length);for(var r=this[e],i=1,o=0;++o<t&&(i*=256);)r+=this[e+o]*i;return r},u.prototype.readUIntBE=function(e,t,n){e|=0,t|=0,n||T(e,t,this.length);for(var r=this[e+--t],i=1;t>0&&(i*=256);)r+=this[e+--t]*i;return r},u.prototype.readUInt8=function(e,t){return t||T(e,1,this.length),this[e]},u.prototype.readUInt16LE=function(e,t){return t||T(e,2,this.length),this[e]|this[e+1]<<8},u.prototype.readUInt16BE=function(e,t){return t||T(e,2,this.length),this[e]<<8|this[e+1]},u.prototype.readUInt32LE=function(e,t){return t||T(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},u.prototype.readUInt32BE=function(e,t){return t||T(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},u.prototype.readIntLE=function(e,t,n){e|=0,t|=0,n||T(e,t,this.length);for(var r=this[e],i=1,o=0;++o<t&&(i*=256);)r+=this[e+o]*i;return r>=(i*=128)&&(r-=Math.pow(2,8*t)),r},u.prototype.readIntBE=function(e,t,n){e|=0,t|=0,n||T(e,t,this.length);for(var r=t,i=1,o=this[e+--r];r>0&&(i*=256);)o+=this[e+--r]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*t)),o},u.prototype.readInt8=function(e,t){return t||T(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},u.prototype.readInt16LE=function(e,t){t||T(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},u.prototype.readInt16BE=function(e,t){t||T(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},u.prototype.readInt32LE=function(e,t){return t||T(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},u.prototype.readInt32BE=function(e,t){return t||T(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},u.prototype.readFloatLE=function(e,t){return t||T(e,4,this.length),i.read(this,e,!0,23,4)},u.prototype.readFloatBE=function(e,t){return t||T(e,4,this.length),i.read(this,e,!1,23,4)},u.prototype.readDoubleLE=function(e,t){return t||T(e,8,this.length),i.read(this,e,!0,52,8)},u.prototype.readDoubleBE=function(e,t){return t||T(e,8,this.length),i.read(this,e,!1,52,8)},u.prototype.writeUIntLE=function(e,t,n,r){(e=+e,t|=0,n|=0,r)||P(this,e,t,n,Math.pow(2,8*n)-1,0);var i=1,o=0;for(this[t]=255&e;++o<n&&(i*=256);)this[t+o]=e/i&255;return t+n},u.prototype.writeUIntBE=function(e,t,n,r){(e=+e,t|=0,n|=0,r)||P(this,e,t,n,Math.pow(2,8*n)-1,0);var i=n-1,o=1;for(this[t+i]=255&e;--i>=0&&(o*=256);)this[t+i]=e/o&255;return t+n},u.prototype.writeUInt8=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,1,255,0),u.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},u.prototype.writeUInt16LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):N(this,e,t,!0),t+2},u.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):N(this,e,t,!1),t+2},u.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):R(this,e,t,!0),t+4},u.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):R(this,e,t,!1),t+4},u.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t|=0,!r){var i=Math.pow(2,8*n-1);P(this,e,t,n,i-1,-i)}var o=0,s=1,a=0;for(this[t]=255&e;++o<n&&(s*=256);)e<0&&0===a&&0!==this[t+o-1]&&(a=1),this[t+o]=(e/s>>0)-a&255;return t+n},u.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t|=0,!r){var i=Math.pow(2,8*n-1);P(this,e,t,n,i-1,-i)}var o=n-1,s=1,a=0;for(this[t+o]=255&e;--o>=0&&(s*=256);)e<0&&0===a&&0!==this[t+o+1]&&(a=1),this[t+o]=(e/s>>0)-a&255;return t+n},u.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,1,127,-128),u.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},u.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):N(this,e,t,!0),t+2},u.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):N(this,e,t,!1),t+2},u.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,2147483647,-2147483648),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):R(this,e,t,!0),t+4},u.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):R(this,e,t,!1),t+4},u.prototype.writeFloatLE=function(e,t,n){return j(this,e,t,!0,n)},u.prototype.writeFloatBE=function(e,t,n){return j(this,e,t,!1,n)},u.prototype.writeDoubleLE=function(e,t,n){return D(this,e,t,!0,n)},u.prototype.writeDoubleBE=function(e,t,n){return D(this,e,t,!1,n)},u.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r<n&&(r=n),r===n)return 0;if(0===e.length||0===this.length)return 0;if(t<0)throw new RangeError("targetStart out of bounds");if(n<0||n>=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t<r-n&&(r=e.length-t+n);var i,o=r-n;if(this===e&&n<t&&t<r)for(i=o-1;i>=0;--i)e[i+t]=this[i+n];else if(o<1e3||!u.TYPED_ARRAY_SUPPORT)for(i=0;i<o;++i)e[i+t]=this[i+n];else Uint8Array.prototype.set.call(e,this.subarray(n,n+o),t);return o},u.prototype.fill=function(e,t,n,r){if("string"==typeof e){if("string"==typeof t?(r=t,t=0,n=this.length):"string"==typeof n&&(r=n,n=this.length),1===e.length){var i=e.charCodeAt(0);i<256&&(e=i)}if(void 0!==r&&"string"!=typeof r)throw new TypeError("encoding must be a string");if("string"==typeof r&&!u.isEncoding(r))throw new TypeError("Unknown encoding: "+r)}else"number"==typeof e&&(e&=255);if(t<0||this.length<t||this.length<n)throw new RangeError("Out of range index");if(n<=t)return this;var o;if(t>>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(o=t;o<n;++o)this[o]=e;else{var s=u.isBuffer(e)?e:F(new u(e,r).toString()),a=s.length;for(o=0;o<n-t;++o)this[o+t]=s[o%a]}return this};var U=/[^+\/0-9A-Za-z-_]/g;function B(e){return e<16?"0"+e.toString(16):e.toString(16)}function F(e,t){var n;t=t||1/0;for(var r=e.length,i=null,o=[],s=0;s<r;++s){if((n=e.charCodeAt(s))>55295&&n<57344){if(!i){if(n>56319){(t-=3)>-1&&o.push(239,191,189);continue}if(s+1===r){(t-=3)>-1&&o.push(239,191,189);continue}i=n;continue}if(n<56320){(t-=3)>-1&&o.push(239,191,189),i=n;continue}n=65536+(i-55296<<10|n-56320)}else i&&(t-=3)>-1&&o.push(239,191,189);if(i=null,n<128){if((t-=1)<0)break;o.push(n)}else if(n<2048){if((t-=2)<0)break;o.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;o.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;o.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return o}function z(e){return r.toByteArray(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(U,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function q(e,t,n,r){for(var i=0;i<r&&!(i+n>=t.length||i>=e.length);++i)t[i+n]=e[i];return i}}).call(this,n(31))},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:e.exports=function(e,t){if(t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}}},function(e,t,n){ -/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */ -var r=n(6),i=r.Buffer;function o(e,t){for(var n in e)t[n]=e[n]}function s(e,t,n){return i(e,t,n)}i.from&&i.alloc&&i.allocUnsafe&&i.allocUnsafeSlow?e.exports=r:(o(r,t),t.Buffer=s),s.prototype=Object.create(i.prototype),o(i,s),s.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return i(e,t,n)},s.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=i(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},s.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return i(e)},s.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){"use strict";n.d(t,"c",(function(){return o})),n.d(t,"b",(function(){return s})),n.d(t,"a",(function(){return a}));var r=n(3),i=new WeakSet;function o(e){return i.has(e)}Symbol("A predicate that matches all records");var s=function(){function e(){}return Object.defineProperty(e,"ALL",{get:function(){var e=function(e){return e};return i.add(e),e},enumerable:!0,configurable:!0}),e}(),a=function(){function e(){}return e.createPredicateBuilder=function(t){var n,i=t.name,o=new Set(Object.keys(t.fields)),s=new Proxy({},n={get:function(t,s,a){var u=s;switch(u){case"and":case"or":case"not":return function(t){var r={type:u,predicates:[]},i=new Proxy({},n);return e.predicateGroupsMap.set(i,r),t(i),e.predicateGroupsMap.get(a).predicates.push(r),a};default:Object(r.f)(u,!1)}var c=s;if(!o.has(c))throw new Error("Invalid field for model. field: "+c+", model: "+i);return function(t,n){return e.predicateGroupsMap.get(a).predicates.push({field:c,operator:t,operand:n}),a}}});return e.predicateGroupsMap.set(s,{type:"and",predicates:[]}),s},e.isValidPredicate=function(t){return e.predicateGroupsMap.has(t)},e.getPredicates=function(t,n){if(void 0===n&&(n=!0),n&&!e.isValidPredicate(t))throw new Error("The predicate is not valid");return e.predicateGroupsMap.get(t)},e.createFromExisting=function(t,n){if(n&&t)return n(e.createPredicateBuilder(t))},e.createForId=function(t,n){return e.createPredicateBuilder(t).id("eq",n)},e.predicateGroupsMap=new WeakMap,e}()},function(e,t,n){"use strict";n.d(t,"a",(function(){return s}));var r=n(1),i={name:"deserializerMiddleware",step:"deserialize",tags:["DESERIALIZER"]},o={name:"serializerMiddleware",step:"serialize",tags:["SERIALIZER"]};function s(e,t,n){return{applyToStack:function(s){s.add(function(e,t){return function(n,i){return function(o){return Object(r.__awaiter)(void 0,void 0,void 0,(function(){var s,a,u,c,f;return Object(r.__generator)(this,(function(l){switch(l.label){case 0:return s=i.logger,a=i.outputFilterSensitiveLog,[4,n(o)];case 1:return u=l.sent().response,"function"==typeof(null==s?void 0:s.debug)&&s.debug({httpResponse:u}),[4,t(u,e)];case 2:return c=l.sent(),c.$metadata,f=Object(r.__rest)(c,["$metadata"]),"function"==typeof(null==s?void 0:s.info)&&s.info({output:a(f)}),[2,{response:u,output:c}]}}))}))}}}(e,n),i),s.add(function(e,t){return function(n,i){return function(o){return Object(r.__awaiter)(void 0,void 0,void 0,(function(){var s,a,u;return Object(r.__generator)(this,(function(c){switch(c.label){case 0:return s=i.logger,a=i.inputFilterSensitiveLog,"function"==typeof(null==s?void 0:s.info)&&s.info({input:a(o.input)}),[4,t(o.input,e)];case 1:return u=c.sent(),"function"==typeof(null==s?void 0:s.debug)&&s.debug({httpRequest:u}),[2,n(Object(r.__assign)(Object(r.__assign)({},o),{request:u}))]}}))}))}}}(e,t),o)}}}},function(e,t,n){"use strict";n.d(t,"b",(function(){return o})),n.d(t,"a",(function(){return v})),n.d(t,"c",(function(){return m}));var r=n(1),i={name:"retryMiddleware",tags:["RETRY"],step:"finalizeRequest",priority:"high"},o=function(e){return{applyToStack:function(t){t.add(function(e){return function(t){return function(n){return Object(r.__awaiter)(void 0,void 0,void 0,(function(){return Object(r.__generator)(this,(function(r){return[2,e.retryStrategy.retry(t,n)]}))}))}}}(e),i)}}},s=n(2),a=["AuthFailure","InvalidSignatureException","RequestExpired","RequestInTheFuture","RequestTimeTooSkewed","SignatureDoesNotMatch"],u=["Throttling","ThrottlingException","ThrottledException","RequestThrottledException","TooManyRequestsException","ProvisionedThroughputExceededException","TransactionInProgressException","RequestLimitExceeded","BandwidthLimitExceeded","LimitExceededException","RequestThrottled","SlowDown","PriorRequestNotComplete","EC2ThrottledException"],c=["AbortError","TimeoutError","RequestTimeout","RequestTimeoutException"],f=[500,502,503,504],l=function(e){var t;return u.includes(e.name)||1==(null===(t=e.$retryable)||void 0===t?void 0:t.throttling)},d=n(27),h=function(e,t){return Math.floor(Math.min(2e4,Math.random()*Math.pow(2,t)*e))},p=function(e){return!!e&&(function(e){return void 0!==e.$retryable}(e)||function(e){return a.includes(e.name)}(e)||l(e)||function(e){var t;return c.includes(e.name)||f.includes((null===(t=e.$metadata)||void 0===t?void 0:t.httpStatusCode)||0)}(e))},v=3,g=function(){function e(e,t){var n,r,i,o,s,a,u,c;this.maxAttemptsProvider=e,this.retryDecider=null!==(n=null==t?void 0:t.retryDecider)&&void 0!==n?n:p,this.delayDecider=null!==(r=null==t?void 0:t.delayDecider)&&void 0!==r?r:h,this.retryQuota=null!==(i=null==t?void 0:t.retryQuota)&&void 0!==i?i:(s=o=500,a=o,u=function(e){return"TimeoutError"===e.name?10:5},c=function(e){return u(e)<=a},Object.freeze({hasRetryTokens:c,retrieveRetryTokens:function(e){if(!c(e))throw new Error("No retry token available");var t=u(e);return a-=t,t},releaseRetryTokens:function(e){a+=null!=e?e:1,a=Math.min(a,s)}}))}return e.prototype.shouldRetry=function(e,t,n){return t<n&&this.retryDecider(e)&&this.retryQuota.hasRetryTokens(e)},e.prototype.getMaxAttempts=function(){return Object(r.__awaiter)(this,void 0,void 0,(function(){var e;return Object(r.__generator)(this,(function(t){switch(t.label){case 0:return t.trys.push([0,2,,3]),[4,this.maxAttemptsProvider()];case 1:return e=t.sent(),[3,3];case 2:return t.sent(),e=v,[3,3];case 3:return[2,e]}}))}))},e.prototype.retry=function(e,t){return Object(r.__awaiter)(this,void 0,void 0,(function(){var n,i,o,a,u,c,f,h;return Object(r.__generator)(this,(function(p){switch(p.label){case 0:return i=0,o=0,[4,this.getMaxAttempts()];case 1:a=p.sent(),u=t.request,s.a.isInstance(u)&&(u.headers["amz-sdk-invocation-id"]=Object(d.v4)()),c=function(){var c,d,h,p,v;return Object(r.__generator)(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,5]),s.a.isInstance(u)&&(u.headers["amz-sdk-request"]="attempt="+(i+1)+"; max="+a),[4,e(t)];case 1:return c=r.sent(),d=c.response,h=c.output,f.retryQuota.releaseRetryTokens(n),h.$metadata.attempts=i+1,h.$metadata.totalRetryDelay=o,[2,{value:{response:d,output:h}}];case 2:return p=r.sent(),i++,f.shouldRetry(p,i,a)?(n=f.retryQuota.retrieveRetryTokens(p),v=f.delayDecider(l(p)?500:100,i),o+=v,[4,new Promise((function(e){return setTimeout(e,v)}))]):[3,4];case 3:return r.sent(),[2,"continue"];case 4:throw p.$metadata||(p.$metadata={}),p.$metadata.attempts=i,p.$metadata.totalRetryDelay=o,p;case 5:return[2]}}))},f=this,p.label=2;case 2:return[5,c()];case 3:return"object"==typeof(h=p.sent())?[2,h.value]:[3,2];case 4:return[2]}}))}))},e}(),m=function(e){var t=b(e.maxAttempts);return Object(r.__assign)(Object(r.__assign)({},e),{maxAttempts:t,retryStrategy:e.retryStrategy||new g(t)})},b=function(e){if(void 0===e&&(e=v),"number"==typeof e){var t=Promise.resolve(e);return function(){return t}}return e}},function(e,t,n){"use strict";var r,i;function o(e){return e&&!!["provider"].find((function(t){return e.hasOwnProperty(t)}))}function s(e){return e&&!!["customProvider"].find((function(t){return e.hasOwnProperty(t)}))}function a(e){return e&&!!["customState"].find((function(t){return e.hasOwnProperty(t)}))}function u(e){return void 0!==e.redirectSignIn}function c(e){return!!e.username}n.d(t,"b",(function(){return r})),n.d(t,"e",(function(){return o})),n.d(t,"f",(function(){return s})),n.d(t,"c",(function(){return a})),n.d(t,"d",(function(){return u})),n.d(t,"a",(function(){return i})),n.d(t,"g",(function(){return c})),function(e){e.Cognito="COGNITO",e.Google="Google",e.Facebook="Facebook",e.Amazon="LoginWithAmazon",e.Apple="SignInWithApple"}(r||(r={})),function(e){e.NoConfig="noConfig",e.MissingAuthConfig="missingAuthConfig",e.EmptyUsername="emptyUsername",e.InvalidUsername="invalidUsername",e.EmptyPassword="emptyPassword",e.EmptyCode="emptyCode",e.SignUpError="signUpError",e.NoMFA="noMFA",e.InvalidMFA="invalidMFA",e.EmptyChallengeResponse="emptyChallengeResponse",e.NoUserSession="noUserSession",e.Default="default"}(i||(i={}))},function(e,t,n){"use strict";n.d(t,"a",(function(){return i})),n.d(t,"e",(function(){return d})),n.d(t,"c",(function(){return h})),n.d(t,"b",(function(){return p})),n.d(t,"d",(function(){return v})),n.d(t,"g",(function(){return g})),n.d(t,"h",(function(){return m})),n.d(t,"f",(function(){return b}));var r,i,o=n(4),s=n(3),a=function(){return(a=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};!function(e){e.LIST="query",e.CREATE="mutation",e.UPDATE="mutation",e.DELETE="mutation",e.GET="query"}(r||(r={})),function(e){e.CREATE="Create",e.UPDATE="Update",e.DELETE="Delete",e.GET="Get"}(i||(i={}));var u={_version:void 0,_lastChangedAt:void 0,_deleted:void 0},c=Object.keys(u);function f(e,t){var n=l(t),r=function(e,t){var n=[];return Object.values(t.fields).forEach((function(t){var r=t.name,i=t.type;if(Object(o.i)(i)){var s=e.nonModels[i.nonModel],a=Object.values(l(s)).map((function(e){return e.name})),u=[];Object.values(s.fields).forEach((function(t){var n=t.type,r=t.name;if(Object(o.i)(n)){var i=e.nonModels[n.nonModel];u.push(r+" { "+f(e,i)+" }")}})),n.push(r+" { "+a.join(" ")+" "+u.join(" ")+" }")}})),n}(e,t),i=function(e,t){var n=function(e){var t=[];Object(o.l)(e)&&e.attributes&&e.attributes.forEach((function(e){if(e.properties&&e.properties.rules){var n=e.properties.rules.find((function(e){return"owner"===e.allow}));n&&n.ownerField&&t.push(n.ownerField)}}));return t}(e);if(!t.owner&&n.includes("owner"))return["owner"];return[]}(t,n),a=Object.values(n).map((function(e){return e.name})).concat(i).concat(r);return Object(o.l)(t)&&(a=a.concat(c).concat(function(e){var t=[];return Object.values(e.fields).filter((function(e){var t=e.association;return t&&Object.keys(t).length})).forEach((function(e){var n=e.name,r=e.association,i=r.connectionType;switch(i){case"HAS_ONE":case"HAS_MANY":break;case"BELONGS_TO":Object(o.m)(r)&&t.push(n+" { id _deleted }");break;default:Object(s.f)(i)}})),t}(t))),a.join("\n")}function l(e){var t=e.fields;return Object.values(t).filter((function(e){return!(!Object(o.g)(e.type)&&!Object(o.f)(e.type))})).reduce((function(e,t){return e[t.name]=t,e}),{})}function d(e){var t=([].concat(e.attributes).find((function(e){return e&&"auth"===e.type}))||{}).properties,n=(void 0===t?{}:t).rules,r=[];return(void 0===n?[]:n).forEach((function(t){var n=t.identityClaim,i=void 0===n?"cognito:username":n,o=t.ownerField,s=void 0===o?"owner":o,a=t.operations,u=void 0===a?["create","update","delete","read"]:a,c=t.provider,f=void 0===c?"userPools":c,l=t.groupClaim,d=void 0===l?"cognito:groups":l,h=t.allow,p=void 0===h?"iam":h,v=t.groups,g=void 0===v?[]:v,m="owner"===p;if(u.includes("read")||m){var b={identityClaim:i,ownerField:s,provider:f,groupClaim:d,authStrategy:p,groups:g,areSubscriptionsPublic:!1};if(m){var y=([].concat(e.attributes).find((function(e){return e&&"model"===e.type}))||{}).properties,w=(void 0===y?{}:y).subscriptions,_=(void 0===w?{}:w).level,S=void 0===_?"on":_;b.areSubscriptionsPublic=!u.includes("read")||"public"===S}m?r.push(b):r.unshift(b)}})),r}function h(e,t,n,r,i){var o=f(e,t),s=t.name,a=(t.pluralName,"on"+n+s),u="",c="";return r&&(u="($"+i+": String!)",c="("+i+": $"+i+")"),[n,a,"subscription operation"+u+"{\n\t\t\t"+a+c+"{\n\t\t\t\t"+o+"\n\t\t\t}\n\t\t}"]}function p(e,t,n){var o,a,u=f(e,t),c=t.name,l=t.pluralName,d=" ",h=" ";switch(n){case"LIST":o="sync"+l,d="($limit: Int, $nextToken: String, $lastSync: AWSTimestamp, $filter: Model"+c+"FilterInput)",h="(limit: $limit, nextToken: $nextToken, lastSync: $lastSync, filter: $filter)",u="items {\n\t\t\t\t\t\t\t"+u+"\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnextToken\n\t\t\t\t\t\tstartedAt";break;case"CREATE":o="create"+c,d="($input: Create"+c+"Input!)",h="(input: $input)",a=i.CREATE;break;case"UPDATE":o="update"+c,d="($input: Update"+c+"Input!, $condition: Model"+c+"ConditionInput)",h="(input: $input, condition: $condition)",a=i.UPDATE;break;case"DELETE":o="delete"+c,d="($input: Delete"+c+"Input!, $condition: Model"+c+"ConditionInput)",h="(input: $input, condition: $condition)",a=i.DELETE;break;case"GET":o="get"+c,d="($id: ID!)",h="(id: $id)",a=i.GET;break;default:Object(s.f)(n)}return[[a,o,r[n]+" operation"+d+"{\n\t\t"+o+h+"{\n\t\t\t"+u+"\n\t\t}\n\t}"]]}function v(e,t,n,r,u,c,f,l,d){var h;switch(n){case o.c.INSERT:h=i.CREATE;break;case o.c.UPDATE:h=i.UPDATE;break;case o.c.DELETE:h=i.DELETE;break;default:Object(s.f)(n)}return l(f,a(a({},d?{id:d}:{}),{data:JSON.stringify(u),modelId:u.id,model:r.name,operation:h,condition:JSON.stringify(c)}))}function g(e){var t={};return e&&Array.isArray(e.predicates)?(e.predicates.forEach((function(e){var n;if(Object(o.k)(e)){var r=e.field,i=e.operator,s=e.operand;if("id"===r)return;t[r]=((n={})[i]=s,n)}else t[e.type]=g(e)})),t):t}function m(e){var t={};if(!e||!Array.isArray(e.predicates))return t;var n=e.type,r=e.predicates,i="and"===n||"or"===n;t[n]=i?[]:{};var s=function(e){return i?t[n].push(e):t[n]=e};return r.forEach((function(e){var t,n;if(Object(o.k)(e)){var r=e.field,i=e.operator,a=e.operand,u=((t={})[r]=((n={})[i]=a,n),t);s(u)}else s(m(e))})),t}function b(e,t){var n=e[t.groupClaim]||[];if("string"==typeof n){var r=void 0;try{r=JSON.parse(n)}catch(e){r=n}n=[].concat(r)}return n}},function(e,t,n){"use strict";var r=n(255),i=n.n(r).a;t.a=i},function(e,t,n){"use strict";n.d(t,"a",(function(){return r})),n.d(t,"b",(function(){return i}));var r=function(e){return"function"==typeof TextEncoder?function(e){return(new TextEncoder).encode(e)}(e):function(e){for(var t=[],n=0,r=e.length;n<r;n++){var i=e.charCodeAt(n);if(i<128)t.push(i);else if(i<2048)t.push(i>>6|192,63&i|128);else if(n+1<e.length&&55296==(64512&i)&&56320==(64512&e.charCodeAt(n+1))){var o=65536+((1023&i)<<10)+(1023&e.charCodeAt(++n));t.push(o>>18|240,o>>12&63|128,o>>6&63|128,63&o|128)}else t.push(i>>12|224,i>>6&63|128,63&i|128)}return Uint8Array.from(t)}(e)},i=function(e){return"function"==typeof TextDecoder?function(e){return new TextDecoder("utf-8").decode(e)}(e):function(e){for(var t="",n=0,r=e.length;n<r;n++){var i=e[n];if(i<128)t+=String.fromCharCode(i);else if(192<=i&&i<224){var o=e[++n];t+=String.fromCharCode((31&i)<<6|63&o)}else if(240<=i&&i<365){var s="%"+[i,e[++n],e[++n],e[++n]].map((function(e){return e.toString(16)})).join("%");t+=decodeURIComponent(s)}else t+=String.fromCharCode((15&i)<<12|(63&e[++n])<<6|63&e[++n])}return t}(e)}},function(e,t,n){"use strict";var r=n(366),i=n(367);function o(){this.protocol=null,this.slashes=null,this.auth=null,this.host=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.query=null,this.pathname=null,this.path=null,this.href=null}t.parse=y,t.resolve=function(e,t){return y(e,!1,!0).resolve(t)},t.resolveObject=function(e,t){return e?y(e,!1,!0).resolveObject(t):t},t.format=function(e){i.isString(e)&&(e=y(e));return e instanceof o?e.format():o.prototype.format.call(e)},t.Url=o;var s=/^([a-z0-9.+-]+:)/i,a=/:[0-9]*$/,u=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,c=["{","}","|","\\","^","`"].concat(["<",">",'"',"`"," ","\r","\n","\t"]),f=["'"].concat(c),l=["%","/","?",";","#"].concat(f),d=["/","?","#"],h=/^[+a-z0-9A-Z_-]{0,63}$/,p=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,v={javascript:!0,"javascript:":!0},g={javascript:!0,"javascript:":!0},m={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},b=n(368);function y(e,t,n){if(e&&i.isObject(e)&&e instanceof o)return e;var r=new o;return r.parse(e,t,n),r}o.prototype.parse=function(e,t,n){if(!i.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+typeof e);var o=e.indexOf("?"),a=-1!==o&&o<e.indexOf("#")?"?":"#",c=e.split(a);c[0]=c[0].replace(/\\/g,"/");var y=e=c.join(a);if(y=y.trim(),!n&&1===e.split("#").length){var w=u.exec(y);if(w)return this.path=y,this.href=y,this.pathname=w[1],w[2]?(this.search=w[2],this.query=t?b.parse(this.search.substr(1)):this.search.substr(1)):t&&(this.search="",this.query={}),this}var _=s.exec(y);if(_){var S=(_=_[0]).toLowerCase();this.protocol=S,y=y.substr(_.length)}if(n||_||y.match(/^\/\/[^@\/]+@[^@\/]+/)){var E="//"===y.substr(0,2);!E||_&&g[_]||(y=y.substr(2),this.slashes=!0)}if(!g[_]&&(E||_&&!m[_])){for(var M,A,I=-1,k=0;k<d.length;k++){-1!==(O=y.indexOf(d[k]))&&(-1===I||O<I)&&(I=O)}-1!==(A=-1===I?y.lastIndexOf("@"):y.lastIndexOf("@",I))&&(M=y.slice(0,A),y=y.slice(A+1),this.auth=decodeURIComponent(M)),I=-1;for(k=0;k<l.length;k++){var O;-1!==(O=y.indexOf(l[k]))&&(-1===I||O<I)&&(I=O)}-1===I&&(I=y.length),this.host=y.slice(0,I),y=y.slice(I),this.parseHost(),this.hostname=this.hostname||"";var x="["===this.hostname[0]&&"]"===this.hostname[this.hostname.length-1];if(!x)for(var C=this.hostname.split(/\./),T=(k=0,C.length);k<T;k++){var P=C[k];if(P&&!P.match(h)){for(var N="",R=0,L=P.length;R<L;R++)P.charCodeAt(R)>127?N+="x":N+=P[R];if(!N.match(h)){var j=C.slice(0,k),D=C.slice(k+1),U=P.match(p);U&&(j.push(U[1]),D.unshift(U[2])),D.length&&(y="/"+D.join(".")+y),this.hostname=j.join(".");break}}}this.hostname.length>255?this.hostname="":this.hostname=this.hostname.toLowerCase(),x||(this.hostname=r.toASCII(this.hostname));var B=this.port?":"+this.port:"",F=this.hostname||"";this.host=F+B,this.href+=this.host,x&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==y[0]&&(y="/"+y))}if(!v[S])for(k=0,T=f.length;k<T;k++){var z=f[k];if(-1!==y.indexOf(z)){var q=encodeURIComponent(z);q===z&&(q=escape(z)),y=y.split(z).join(q)}}var K=y.indexOf("#");-1!==K&&(this.hash=y.substr(K),y=y.slice(0,K));var H=y.indexOf("?");if(-1!==H?(this.search=y.substr(H),this.query=y.substr(H+1),t&&(this.query=b.parse(this.query)),y=y.slice(0,H)):t&&(this.search="",this.query={}),y&&(this.pathname=y),m[S]&&this.hostname&&!this.pathname&&(this.pathname="/"),this.pathname||this.search){B=this.pathname||"";var V=this.search||"";this.path=B+V}return this.href=this.format(),this},o.prototype.format=function(){var e=this.auth||"";e&&(e=(e=encodeURIComponent(e)).replace(/%3A/i,":"),e+="@");var t=this.protocol||"",n=this.pathname||"",r=this.hash||"",o=!1,s="";this.host?o=e+this.host:this.hostname&&(o=e+(-1===this.hostname.indexOf(":")?this.hostname:"["+this.hostname+"]"),this.port&&(o+=":"+this.port)),this.query&&i.isObject(this.query)&&Object.keys(this.query).length&&(s=b.stringify(this.query));var a=this.search||s&&"?"+s||"";return t&&":"!==t.substr(-1)&&(t+=":"),this.slashes||(!t||m[t])&&!1!==o?(o="//"+(o||""),n&&"/"!==n.charAt(0)&&(n="/"+n)):o||(o=""),r&&"#"!==r.charAt(0)&&(r="#"+r),a&&"?"!==a.charAt(0)&&(a="?"+a),t+o+(n=n.replace(/[?#]/g,(function(e){return encodeURIComponent(e)})))+(a=a.replace("#","%23"))+r},o.prototype.resolve=function(e){return this.resolveObject(y(e,!1,!0)).format()},o.prototype.resolveObject=function(e){if(i.isString(e)){var t=new o;t.parse(e,!1,!0),e=t}for(var n=new o,r=Object.keys(this),s=0;s<r.length;s++){var a=r[s];n[a]=this[a]}if(n.hash=e.hash,""===e.href)return n.href=n.format(),n;if(e.slashes&&!e.protocol){for(var u=Object.keys(e),c=0;c<u.length;c++){var f=u[c];"protocol"!==f&&(n[f]=e[f])}return m[n.protocol]&&n.hostname&&!n.pathname&&(n.path=n.pathname="/"),n.href=n.format(),n}if(e.protocol&&e.protocol!==n.protocol){if(!m[e.protocol]){for(var l=Object.keys(e),d=0;d<l.length;d++){var h=l[d];n[h]=e[h]}return n.href=n.format(),n}if(n.protocol=e.protocol,e.host||g[e.protocol])n.pathname=e.pathname;else{for(var p=(e.pathname||"").split("/");p.length&&!(e.host=p.shift()););e.host||(e.host=""),e.hostname||(e.hostname=""),""!==p[0]&&p.unshift(""),p.length<2&&p.unshift(""),n.pathname=p.join("/")}if(n.search=e.search,n.query=e.query,n.host=e.host||"",n.auth=e.auth,n.hostname=e.hostname||e.host,n.port=e.port,n.pathname||n.search){var v=n.pathname||"",b=n.search||"";n.path=v+b}return n.slashes=n.slashes||e.slashes,n.href=n.format(),n}var y=n.pathname&&"/"===n.pathname.charAt(0),w=e.host||e.pathname&&"/"===e.pathname.charAt(0),_=w||y||n.host&&e.pathname,S=_,E=n.pathname&&n.pathname.split("/")||[],M=(p=e.pathname&&e.pathname.split("/")||[],n.protocol&&!m[n.protocol]);if(M&&(n.hostname="",n.port=null,n.host&&(""===E[0]?E[0]=n.host:E.unshift(n.host)),n.host="",e.protocol&&(e.hostname=null,e.port=null,e.host&&(""===p[0]?p[0]=e.host:p.unshift(e.host)),e.host=null),_=_&&(""===p[0]||""===E[0])),w)n.host=e.host||""===e.host?e.host:n.host,n.hostname=e.hostname||""===e.hostname?e.hostname:n.hostname,n.search=e.search,n.query=e.query,E=p;else if(p.length)E||(E=[]),E.pop(),E=E.concat(p),n.search=e.search,n.query=e.query;else if(!i.isNullOrUndefined(e.search)){if(M)n.hostname=n.host=E.shift(),(x=!!(n.host&&n.host.indexOf("@")>0)&&n.host.split("@"))&&(n.auth=x.shift(),n.host=n.hostname=x.shift());return n.search=e.search,n.query=e.query,i.isNull(n.pathname)&&i.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")),n.href=n.format(),n}if(!E.length)return n.pathname=null,n.search?n.path="/"+n.search:n.path=null,n.href=n.format(),n;for(var A=E.slice(-1)[0],I=(n.host||e.host||E.length>1)&&("."===A||".."===A)||""===A,k=0,O=E.length;O>=0;O--)"."===(A=E[O])?E.splice(O,1):".."===A?(E.splice(O,1),k++):k&&(E.splice(O,1),k--);if(!_&&!S)for(;k--;k)E.unshift("..");!_||""===E[0]||E[0]&&"/"===E[0].charAt(0)||E.unshift(""),I&&"/"!==E.join("/").substr(-1)&&E.push("");var x,C=""===E[0]||E[0]&&"/"===E[0].charAt(0);M&&(n.hostname=n.host=C?"":E.length?E.shift():"",(x=!!(n.host&&n.host.indexOf("@")>0)&&n.host.split("@"))&&(n.auth=x.shift(),n.host=n.hostname=x.shift()));return(_=_||n.host&&E.length)&&!C&&E.unshift(""),E.length?n.pathname=E.join("/"):(n.pathname=null,n.path=null),i.isNull(n.pathname)&&i.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")),n.auth=e.auth||n.auth,n.slashes=n.slashes||e.slashes,n.href=n.format(),n},o.prototype.parseHost=function(){var e=this.host,t=a.exec(e);t&&(":"!==(t=t[0])&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)}},function(e,t,n){"use strict";n.d(t,"a",(function(){return f})),n.d(t,"b",(function(){return l}));for(var r={},i=new Array(64),o=0,s="A".charCodeAt(0),a="Z".charCodeAt(0);o+s<=a;o++){var u=String.fromCharCode(o+s);r[u]=o,i[o]=u}for(o=0,s="a".charCodeAt(0),a="z".charCodeAt(0);o+s<=a;o++){u=String.fromCharCode(o+s);var c=o+26;r[u]=c,i[c]=u}for(o=0;o<10;o++){r[o.toString(10)]=o+52;u=o.toString(10),c=o+52;r[u]=c,i[c]=u}r["+"]=62,i[62]="+",r["/"]=63,i[63]="/";function f(e){var t=e.length/4*3;"=="===e.substr(-2)?t-=2:"="===e.substr(-1)&&t--;for(var n=new ArrayBuffer(t),i=new DataView(n),o=0;o<e.length;o+=4){for(var s=0,a=0,u=o,c=o+3;u<=c;u++)"="!==e[u]?(s|=r[e[u]]<<6*(c-u),a+=6):s>>=6;var f=o/4*3;s>>=a%8;for(var l=Math.floor(a/8),d=0;d<l;d++){var h=8*(l-d-1);i.setUint8(f+d,(s&255<<h)>>h)}}return new Uint8Array(n)}function l(e){for(var t="",n=0;n<e.length;n+=3){for(var r=0,o=0,s=n,a=Math.min(n+3,e.length);s<a;s++)r|=e[s]<<8*(a-s-1),o+=8;var u=Math.ceil(o/6);r<<=6*u-o;for(var c=1;c<=u;c++){var f=6*(u-c);t+=i[(r&63<<f)>>f]}t+="==".slice(0,4-u)}return t}},function(e,t,n){"use strict";n.d(t,"a",(function(){return s})),n.d(t,"b",(function(){return u}));var r=n(1),i=n(2),o=n(74);var s=function(){function e(e){void 0===e&&(e={}),this.httpOptions=e}return e.prototype.destroy=function(){},e.prototype.handle=function(e,t){var n=null==t?void 0:t.abortSignal,s=this.httpOptions.requestTimeout;if(null==n?void 0:n.aborted){var a=new Error("Request aborted");return a.name="AbortError",Promise.reject(a)}var u=e.path;if(e.query){var c=Object(o.a)(e.query);c&&(u+="?"+c)}var f=e.port,l=e.protocol+"//"+e.hostname+(f?":"+f:"")+u,d={body:e.body,headers:new Headers(e.headers),method:e.method};"undefined"!=typeof AbortController&&(d.signal=n);var h,p=new Request(l,d),v=[fetch(p).then((function(e){var t,n,o=e.headers,s={};try{for(var a=Object(r.__values)(o.entries()),u=a.next();!u.done;u=a.next()){var c=u.value;s[c[0]]=c[1]}}catch(e){t={error:e}}finally{try{u&&!u.done&&(n=a.return)&&n.call(a)}finally{if(t)throw t.error}}return void 0!==e.body?{response:new i.b({headers:s,statusCode:e.status,body:e.body})}:e.blob().then((function(t){return{response:new i.b({headers:s,statusCode:e.status,body:t})}}))})),(h=s,void 0===h&&(h=0),new Promise((function(e,t){h&&setTimeout((function(){var e=new Error("Request did not complete within "+h+" ms");e.name="TimeoutError",t(e)}),h)})))];return n&&v.push(new Promise((function(e,t){n.onabort=function(){var e=new Error("Request aborted");e.name="AbortError",t(e)}}))),Promise.race(v)},e}(),a=n(17),u=function(e){return"function"==typeof Blob&&e instanceof Blob?function(e){return Object(r.__awaiter)(this,void 0,void 0,(function(){var t,n;return Object(r.__generator)(this,(function(r){switch(r.label){case 0:return[4,c(e)];case 1:return t=r.sent(),n=Object(a.a)(t),[2,new Uint8Array(n)]}}))}))}(e):function(e){return Object(r.__awaiter)(this,void 0,void 0,(function(){var t,n,i,o,s,a,u;return Object(r.__generator)(this,(function(r){switch(r.label){case 0:t=new Uint8Array(0),n=e.getReader(),i=!1,r.label=1;case 1:return i?[3,3]:[4,n.read()];case 2:return o=r.sent(),s=o.done,(a=o.value)&&(u=t,(t=new Uint8Array(u.length+a.length)).set(u),t.set(a,u.length)),i=s,[3,1];case 3:return[2,t]}}))}))}(e)};function c(e){return new Promise((function(t,n){var r=new FileReader;r.onloadend=function(){var e;if(2!==r.readyState)return n(new Error("Reader aborted too early"));var i=null!==(e=r.result)&&void 0!==e?e:"",o=i.indexOf(","),s=o>-1?o+1:i.length;t(i.substring(s))},r.onabort=function(){return n(new Error("Read aborted"))},r.onerror=function(){return n(r.error)},r.readAsDataURL(e)}))}},function(e,t,n){"use strict";n.d(t,"b",(function(){return s})),n.d(t,"a",(function(){return a}));var r=n(44),i=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},o=new r.a("Amplify"),s=function(){function e(){this._components=[],this._config={},this._modules={},this.Auth=null,this.Analytics=null,this.API=null,this.Credentials=null,this.Storage=null,this.I18n=null,this.Cache=null,this.PubSub=null,this.Interactions=null,this.Pushnotification=null,this.UI=null,this.XR=null,this.Predictions=null,this.DataStore=null,this.Logger=r.a,this.ServiceWorker=null}return e.prototype.register=function(e){o.debug("component registered in amplify",e),this._components.push(e),"function"==typeof e.getModuleName?(this._modules[e.getModuleName()]=e,this[e.getModuleName()]=e):o.debug("no getModuleName method for component",e),e.configure(this._config)},e.prototype.configure=function(e){var t=this;return e?(this._config=Object.assign(this._config,e),o.debug("amplify config",this._config),Object.entries(this._modules).forEach((function(e){var n=i(e,2),r=(n[0],n[1]);Object.keys(r).forEach((function(e){t._modules[e]&&(r[e]=t._modules[e])}))})),this._components.map((function(e){e.configure(t._config)})),this._config):this._config},e.prototype.addPluggable=function(e){e&&e.getCategory&&"function"==typeof e.getCategory&&this._components.map((function(t){t.addPluggable&&"function"==typeof t.addPluggable&&t.addPluggable(e)}))},e}(),a=new s},function(e,t){var n,r,i=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function a(e){if(n===setTimeout)return setTimeout(e,0);if((n===o||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:o}catch(e){n=o}try{r="function"==typeof clearTimeout?clearTimeout:s}catch(e){r=s}}();var u,c=[],f=!1,l=-1;function d(){f&&u&&(f=!1,u.length?c=u.concat(c):l=-1,c.length&&h())}function h(){if(!f){var e=a(d);f=!0;for(var t=c.length;t;){for(u=c,c=[];++l<t;)u&&u[l].run();l=-1,t=c.length}u=null,f=!1,function(e){if(r===clearTimeout)return clearTimeout(e);if((r===s||!r)&&clearTimeout)return r=clearTimeout,clearTimeout(e);try{r(e)}catch(t){try{return r.call(null,e)}catch(t){return r.call(this,e)}}}(e)}}function p(e,t){this.fun=e,this.array=t}function v(){}i.nextTick=function(e){var t=new Array(arguments.length-1);if(arguments.length>1)for(var n=1;n<arguments.length;n++)t[n-1]=arguments[n];c.push(new p(e,t)),1!==c.length||f||a(h)},p.prototype.run=function(){this.fun.apply(null,this.array)},i.title="browser",i.browser=!0,i.env={},i.argv=[],i.version="",i.versions={},i.on=v,i.addListener=v,i.once=v,i.off=v,i.removeListener=v,i.removeAllListeners=v,i.emit=v,i.prependListener=v,i.prependOnceListener=v,i.listeners=function(e){return[]},i.binding=function(e){throw new Error("process.binding is not supported")},i.cwd=function(){return"/"},i.chdir=function(e){throw new Error("process.chdir is not supported")},i.umask=function(){return 0}},function(e,t,n){"use strict";n.d(t,"b",(function(){return o})),n.d(t,"a",(function(){return a}));var r=n(1),i=n(2);function o(e){return e}var s={name:"hostHeaderMiddleware",step:"build",priority:"low",tags:["HOST"]},a=function(e){return{applyToStack:function(t){t.add(function(e){return function(t){return function(n){return Object(r.__awaiter)(void 0,void 0,void 0,(function(){var o,s;return Object(r.__generator)(this,(function(r){return i.a.isInstance(n.request)?(o=n.request,s=(e.requestHandler.metadata||{}).handlerProtocol,(void 0===s?"":s).indexOf("h2")>=0&&!o.headers[":authority"]?(delete o.headers.host,o.headers[":authority"]=""):o.headers.host||(o.headers.host=o.hostname),[2,t(n)]):[2,t(n)]}))}))}}}(e),s)}}}},function(e,t,n){"use strict";n.d(t,"a",(function(){return i})),n.d(t,"b",(function(){return a}));var r=n(1),i=function(e){var t;return Object(r.__assign)(Object(r.__assign)({},e),{tls:null===(t=e.tls)||void 0===t||t,endpoint:e.endpoint?o(e):function(){return s(e)},isCustomEndpoint:!!e.endpoint})},o=function(e){var t=e.endpoint,n=e.urlParser;if("string"==typeof t){var r=Promise.resolve(n(t));return function(){return r}}if("object"==typeof t){var i=Promise.resolve(t);return function(){return i}}return t},s=function(e){return Object(r.__awaiter)(void 0,void 0,void 0,(function(){var t,n,i,o,s;return Object(r.__generator)(this,(function(r){switch(r.label){case 0:return t=e.tls,n=void 0===t||t,[4,e.region()];case 1:if(i=r.sent(),!new RegExp(/^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/).test(i))throw new Error("Invalid region in client config");return[4,e.regionInfoProvider(i)];case 2:if(!(o=(null!==(s=r.sent())&&void 0!==s?s:{}).hostname))throw new Error("Cannot resolve hostname from client config");return[2,e.urlParser((n?"https:":"http:")+"//"+o)]}}))}))},a=function(e){if(!e.region)throw new Error("Region is missing");return Object(r.__assign)(Object(r.__assign)({},e),{region:u(e.region)})},u=function(e){if("string"==typeof e){var t=Promise.resolve(e);return function(){return t}}return e}},function(e,t,n){"use strict";function r(e){return e}n.d(t,"b",(function(){return r})),n.d(t,"a",(function(){return a}));var i=n(1),o=n(2);var s={name:"getUserAgentMiddleware",step:"build",tags:["SET_USER_AGENT","USER_AGENT"]},a=function(e){return{applyToStack:function(t){var n;t.add((n=e,function(e){return function(t){var r=t.request;if(!o.a.isInstance(r))return e(t);var s=r.headers,a="node"===n.runtime?"user-agent":"x-amz-user-agent";return s[a]?s[a]+=" "+n.defaultUserAgent:s[a]=""+n.defaultUserAgent,n.customUserAgent&&(s[a]+=" "+n.customUserAgent),e(Object(i.__assign)(Object(i.__assign)({},t),{request:r}))}}),s)}}}},function(e,t,n){"use strict";n.d(t,"a",(function(){return r}));var r=function(e){return function(){throw new Error(e)}}},function(e,t,n){"use strict";n.d(t,"b",(function(){return o})),n.d(t,"a",(function(){return l}));var r=n(1),i=n(111);function o(e){var t,n=this,o=s(e.credentials||e.credentialDefaultProvider(e)),a=e.signingEscapePath,u=void 0===a||a,c=e.systemClockOffset,f=void 0===c?e.systemClockOffset||0:c,l=e.sha256;return t=e.signer?s(e.signer):function(){return s(e.region)().then((function(t){return Object(r.__awaiter)(n,void 0,void 0,(function(){return Object(r.__generator)(this,(function(n){switch(n.label){case 0:return[4,e.regionInfoProvider(t)];case 1:return[2,[n.sent()||{},t]]}}))}))})).then((function(t){var n=Object(r.__read)(t,2),s=n[0],a=n[1],c=s.signingRegion,f=void 0===c?e.signingRegion:c,d=s.signingService,h=void 0===d?e.signingName:d;return e.signingRegion=e.signingRegion||f||a,e.signingName=e.signingName||h,new i.a({credentials:o,region:e.signingRegion,service:e.signingName,sha256:l,uriEscapePath:u})}))},Object(r.__assign)(Object(r.__assign)({},e),{systemClockOffset:f,signingEscapePath:u,credentials:o,signer:t})}function s(e){if("object"==typeof e){var t=Promise.resolve(e);return function(){return t}}return e}var a=n(2),u=function(e){return new Date(Date.now()+e)};function c(e){return function(t,n){return function(i){return Object(r.__awaiter)(this,void 0,void 0,(function(){var o,s,c,f,l,d,h,p,v;return Object(r.__generator)(this,(function(g){switch(g.label){case 0:return a.a.isInstance(i.request)?"function"!=typeof e.signer?[3,2]:[4,e.signer()]:[2,t(i)];case 1:return s=g.sent(),[3,3];case 2:s=e.signer,g.label=3;case 3:return o=s,f=t,l=[Object(r.__assign)({},i)],v={},[4,o.sign(i.request,{signingDate:new Date(Date.now()+e.systemClockOffset),signingRegion:n.signing_region,signingService:n.signing_service})];case 4:return[4,f.apply(void 0,[r.__assign.apply(void 0,l.concat([(v.request=g.sent(),v)]))])];case 5:return c=g.sent(),d=c.response.headers,(h=d&&(d.date||d.Date))&&(p=Date.parse(h),m=p,b=e.systemClockOffset,Math.abs(u(b).getTime()-m)>=3e5&&(e.systemClockOffset=p-Date.now())),[2,c]}var m,b}))}))}}}var f={name:"awsAuthMiddleware",tags:["SIGNATURE","AWSAUTH"],relation:"after",toMiddleware:"retryMiddleware"},l=function(e){return{applyToStack:function(t){t.addRelativeTo(c(e),f)}}}},function(e,t,n){"use strict";var r=n(19),i={keyPrefix:"aws-amplify-cache",capacityInBytes:1048576,itemMaxSize:21e4,defaultTTL:2592e5,defaultPriority:5,warningThreshold:.8,storage:(new(n(86).a)).getStorage()};function o(e){var t=0;t=e.length;for(var n=e.length;n>=0;n-=1){var r=e.charCodeAt(n);r>127&&r<=2047?t+=1:r>2047&&r<=65535&&(t+=2),r>=56320&&r<=57343&&(n-=1)}return t}function s(){return(new Date).getTime()}function a(e){return Number.isInteger?Number.isInteger(e):function(e){return"number"==typeof e&&isFinite(e)&&Math.floor(e)===e}(e)}var u={},c=(function(){function e(){}e.clear=function(){u={}},e.getItem=function(e){return u[e]||null},e.setItem=function(e,t){u[e]=t},e.removeItem=function(e){delete u[e]}}(),n(44));function f(e){return(f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var l,d=new c.a("StorageCache"),h=function(){function e(e){this.config=Object.assign({},e),this.cacheCurSizeKey=this.config.keyPrefix+"CurSize",this.checkConfig()}return e.prototype.getModuleName=function(){return"Cache"},e.prototype.checkConfig=function(){a(this.config.capacityInBytes)||(d.error("Invalid parameter: capacityInBytes. It should be an Integer. Setting back to default."),this.config.capacityInBytes=i.capacityInBytes),a(this.config.itemMaxSize)||(d.error("Invalid parameter: itemMaxSize. It should be an Integer. Setting back to default."),this.config.itemMaxSize=i.itemMaxSize),a(this.config.defaultTTL)||(d.error("Invalid parameter: defaultTTL. It should be an Integer. Setting back to default."),this.config.defaultTTL=i.defaultTTL),a(this.config.defaultPriority)||(d.error("Invalid parameter: defaultPriority. It should be an Integer. Setting back to default."),this.config.defaultPriority=i.defaultPriority),this.config.itemMaxSize>this.config.capacityInBytes&&(d.error("Invalid parameter: itemMaxSize. It should be smaller than capacityInBytes. Setting back to default."),this.config.itemMaxSize=i.itemMaxSize),(this.config.defaultPriority>5||this.config.defaultPriority<1)&&(d.error("Invalid parameter: defaultPriority. It should be between 1 and 5. Setting back to default."),this.config.defaultPriority=i.defaultPriority),(Number(this.config.warningThreshold)>1||Number(this.config.warningThreshold)<0)&&(d.error("Invalid parameter: warningThreshold. It should be between 0 and 1. Setting back to default."),this.config.warningThreshold=i.warningThreshold);this.config.capacityInBytes>5242880&&(d.error("Cache Capacity should be less than 5MB. Setting back to default. Setting back to default."),this.config.capacityInBytes=i.capacityInBytes)},e.prototype.fillCacheItem=function(e,t,n){var r={key:e,data:t,timestamp:s(),visitedTime:s(),priority:n.priority,expires:n.expires,type:f(t),byteSize:0};return r.byteSize=o(JSON.stringify(r)),r.byteSize=o(JSON.stringify(r)),r},e.prototype.configure=function(e){return e?(e.keyPrefix&&d.warn("Don't try to configure keyPrefix!"),this.config=Object.assign({},this.config,e,e.Cache),this.checkConfig(),this.config):this.config},e}(),p=(l=function(e,t){return(l=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)},function(e,t){function n(){this.constructor=e}l(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),v=new c.a("Cache"),g=new(function(e){function t(t){var n=this,r=t?Object.assign({},i,t):i;return(n=e.call(this,r)||this).config.storage=r.storage,n.getItem=n.getItem.bind(n),n.setItem=n.setItem.bind(n),n.removeItem=n.removeItem.bind(n),n}return p(t,e),t.prototype._decreaseCurSizeInBytes=function(e){var t=this.getCacheCurSize();this.config.storage.setItem(this.cacheCurSizeKey,(t-e).toString())},t.prototype._increaseCurSizeInBytes=function(e){var t=this.getCacheCurSize();this.config.storage.setItem(this.cacheCurSizeKey,(t+e).toString())},t.prototype._refreshItem=function(e,t){return e.visitedTime=s(),this.config.storage.setItem(t,JSON.stringify(e)),e},t.prototype._isExpired=function(e){var t=this.config.storage.getItem(e),n=JSON.parse(t);return s()>=n.expires},t.prototype._removeItem=function(e,t){var n=t||JSON.parse(this.config.storage.getItem(e)).byteSize;this._decreaseCurSizeInBytes(n),this.config.storage.removeItem(e)},t.prototype._setItem=function(e,t){this._increaseCurSizeInBytes(t.byteSize);try{this.config.storage.setItem(e,JSON.stringify(t))}catch(e){this._decreaseCurSizeInBytes(t.byteSize),v.error("Failed to set item "+e)}},t.prototype._sizeToPop=function(e){var t=this.getCacheCurSize()+e-this.config.capacityInBytes,n=(1-this.config.warningThreshold)*this.config.capacityInBytes;return t>n?t:n},t.prototype._isCacheFull=function(e){return e+this.getCacheCurSize()>this.config.capacityInBytes},t.prototype._findValidKeys=function(){for(var e=[],t=[],n=0;n<this.config.storage.length;n+=1)t.push(this.config.storage.key(n));for(n=0;n<t.length;n+=1){var r=t[n];0===r.indexOf(this.config.keyPrefix)&&r!==this.cacheCurSizeKey&&(this._isExpired(r)?this._removeItem(r):e.push(r))}return e},t.prototype._popOutItems=function(e,t){for(var n=[],r=t,i=0;i<e.length;i+=1){var o=this.config.storage.getItem(e[i]);if(null!=o){var s=JSON.parse(o);n.push(s)}}n.sort((function(e,t){return e.priority>t.priority?-1:e.priority<t.priority?1:e.visitedTime<t.visitedTime?-1:1}));for(i=0;i<n.length;i+=1)if(this._removeItem(n[i].key,n[i].byteSize),(r-=n[i].byteSize)<=0)return},t.prototype.setItem=function(e,t,n){v.log("Set item: key is "+e+", value is "+t+" with options: "+n);var r=this.config.keyPrefix+e;if(r!==this.config.keyPrefix&&r!==this.cacheCurSizeKey)if(void 0!==t){var i={priority:n&&void 0!==n.priority?n.priority:this.config.defaultPriority,expires:n&&void 0!==n.expires?n.expires:this.config.defaultTTL+s()};if(i.priority<1||i.priority>5)v.warn("Invalid parameter: priority due to out or range. It should be within 1 and 5.");else{var o=this.fillCacheItem(r,t,i);if(o.byteSize>this.config.itemMaxSize)v.warn("Item with key: "+e+" you are trying to put into is too big!");else try{var a=this.config.storage.getItem(r);if(a&&this._removeItem(r,JSON.parse(a).byteSize),this._isCacheFull(o.byteSize)){var u=this._findValidKeys();if(this._isCacheFull(o.byteSize)){var c=this._sizeToPop(o.byteSize);this._popOutItems(u,c)}}this._setItem(r,o)}catch(e){v.warn("setItem failed! "+e)}}}else v.warn("The value of item should not be undefined!");else v.warn("Invalid key: should not be empty or 'CurSize'")},t.prototype.getItem=function(e,t){v.log("Get item: key is "+e+" with options "+t);var n=null,r=this.config.keyPrefix+e;if(r===this.config.keyPrefix||r===this.cacheCurSizeKey)return v.warn("Invalid key: should not be empty or 'CurSize'"),null;try{if(null!=(n=this.config.storage.getItem(r))){if(!this._isExpired(r)){var i=JSON.parse(n);return(i=this._refreshItem(i,r)).data}this._removeItem(r,JSON.parse(n).byteSize),n=null}if(t&&void 0!==t.callback){var o=t.callback();return null!==o&&this.setItem(e,o,t),o}return null}catch(e){return v.warn("getItem failed! "+e),null}},t.prototype.removeItem=function(e){v.log("Remove item: key is "+e);var t=this.config.keyPrefix+e;if(t!==this.config.keyPrefix&&t!==this.cacheCurSizeKey)try{var n=this.config.storage.getItem(t);n&&this._removeItem(t,JSON.parse(n).byteSize)}catch(e){v.warn("removeItem failed! "+e)}},t.prototype.clear=function(){v.log("Clear Cache");for(var e=[],t=0;t<this.config.storage.length;t+=1){var n=this.config.storage.key(t);0===n.indexOf(this.config.keyPrefix)&&e.push(n)}try{for(t=0;t<e.length;t+=1)this.config.storage.removeItem(e[t])}catch(e){v.warn("clear failed! "+e)}},t.prototype.getAllKeys=function(){for(var e=[],t=0;t<this.config.storage.length;t+=1){var n=this.config.storage.key(t);0===n.indexOf(this.config.keyPrefix)&&n!==this.cacheCurSizeKey&&e.push(n.substring(this.config.keyPrefix.length))}return e},t.prototype.getCacheCurSize=function(){var e=this.config.storage.getItem(this.cacheCurSizeKey);return e||(this.config.storage.setItem(this.cacheCurSizeKey,"0"),e="0"),Number(e)},t.prototype.createInstance=function(e){return e.keyPrefix&&e.keyPrefix!==i.keyPrefix||(v.error("invalid keyPrefix, setting keyPrefix with timeStamp"),e.keyPrefix=s.toString()),new t(e)},t}(h));t.a=g;r.a.register(g)},function(e,t,n){var r=n(371),i=n(372),o=i;o.v1=r,o.v4=i,e.exports=o},function(e,t,n){"use strict";n.d(t,"a",(function(){return a})),n.d(t,"b",(function(){return u}));for(var r={},i={},o=0;o<256;o++){var s=o.toString(16).toLowerCase();1===s.length&&(s="0"+s),r[o]=s,i[s]=o}function a(e){if(e.length%2!=0)throw new Error("Hex encoded strings must have an even number length");for(var t=new Uint8Array(e.length/2),n=0;n<e.length;n+=2){var r=e.substr(n,2).toLowerCase();if(!(r in i))throw new Error("Cannot decode unrecognized sequence "+r+" as hexadecimal");t[n/2]=i[r]}return t}function u(e){for(var t="",n=0;n<e.byteLength;n++)t+=r[e[n]];return t}},function(e,t,n){(function(e){!function(e,t){"use strict";function r(e,t){if(!e)throw new Error(t||"Assertion failed")}function i(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}function o(e,t,n){if(o.isBN(e))return e;this.negative=0,this.words=null,this.length=0,this.red=null,null!==e&&("le"!==t&&"be"!==t||(n=t,t=10),this._init(e||0,t||10,n||"be"))}var s;"object"==typeof e?e.exports=o:t.BN=o,o.BN=o,o.wordSize=26;try{s=n(315).Buffer}catch(e){}function a(e,t,n){for(var r=0,i=Math.min(e.length,n),o=t;o<i;o++){var s=e.charCodeAt(o)-48;r<<=4,r|=s>=49&&s<=54?s-49+10:s>=17&&s<=22?s-17+10:15&s}return r}function u(e,t,n,r){for(var i=0,o=Math.min(e.length,n),s=t;s<o;s++){var a=e.charCodeAt(s)-48;i*=r,i+=a>=49?a-49+10:a>=17?a-17+10:a}return i}o.isBN=function(e){return e instanceof o||null!==e&&"object"==typeof e&&e.constructor.wordSize===o.wordSize&&Array.isArray(e.words)},o.max=function(e,t){return e.cmp(t)>0?e:t},o.min=function(e,t){return e.cmp(t)<0?e:t},o.prototype._init=function(e,t,n){if("number"==typeof e)return this._initNumber(e,t,n);if("object"==typeof e)return this._initArray(e,t,n);"hex"===t&&(t=16),r(t===(0|t)&&t>=2&&t<=36);var i=0;"-"===(e=e.toString().replace(/\s+/g,""))[0]&&i++,16===t?this._parseHex(e,i):this._parseBase(e,t,i),"-"===e[0]&&(this.negative=1),this.strip(),"le"===n&&this._initArray(this.toArray(),t,n)},o.prototype._initNumber=function(e,t,n){e<0&&(this.negative=1,e=-e),e<67108864?(this.words=[67108863&e],this.length=1):e<4503599627370496?(this.words=[67108863&e,e/67108864&67108863],this.length=2):(r(e<9007199254740992),this.words=[67108863&e,e/67108864&67108863,1],this.length=3),"le"===n&&this._initArray(this.toArray(),t,n)},o.prototype._initArray=function(e,t,n){if(r("number"==typeof e.length),e.length<=0)return this.words=[0],this.length=1,this;this.length=Math.ceil(e.length/3),this.words=new Array(this.length);for(var i=0;i<this.length;i++)this.words[i]=0;var o,s,a=0;if("be"===n)for(i=e.length-1,o=0;i>=0;i-=3)s=e[i]|e[i-1]<<8|e[i-2]<<16,this.words[o]|=s<<a&67108863,this.words[o+1]=s>>>26-a&67108863,(a+=24)>=26&&(a-=26,o++);else if("le"===n)for(i=0,o=0;i<e.length;i+=3)s=e[i]|e[i+1]<<8|e[i+2]<<16,this.words[o]|=s<<a&67108863,this.words[o+1]=s>>>26-a&67108863,(a+=24)>=26&&(a-=26,o++);return this.strip()},o.prototype._parseHex=function(e,t){this.length=Math.ceil((e.length-t)/6),this.words=new Array(this.length);for(var n=0;n<this.length;n++)this.words[n]=0;var r,i,o=0;for(n=e.length-6,r=0;n>=t;n-=6)i=a(e,n,n+6),this.words[r]|=i<<o&67108863,this.words[r+1]|=i>>>26-o&4194303,(o+=24)>=26&&(o-=26,r++);n+6!==t&&(i=a(e,t,n+6),this.words[r]|=i<<o&67108863,this.words[r+1]|=i>>>26-o&4194303),this.strip()},o.prototype._parseBase=function(e,t,n){this.words=[0],this.length=1;for(var r=0,i=1;i<=67108863;i*=t)r++;r--,i=i/t|0;for(var o=e.length-n,s=o%r,a=Math.min(o,o-s)+n,c=0,f=n;f<a;f+=r)c=u(e,f,f+r,t),this.imuln(i),this.words[0]+c<67108864?this.words[0]+=c:this._iaddn(c);if(0!==s){var l=1;for(c=u(e,f,e.length,t),f=0;f<s;f++)l*=t;this.imuln(l),this.words[0]+c<67108864?this.words[0]+=c:this._iaddn(c)}},o.prototype.copy=function(e){e.words=new Array(this.length);for(var t=0;t<this.length;t++)e.words[t]=this.words[t];e.length=this.length,e.negative=this.negative,e.red=this.red},o.prototype.clone=function(){var e=new o(null);return this.copy(e),e},o.prototype._expand=function(e){for(;this.length<e;)this.words[this.length++]=0;return this},o.prototype.strip=function(){for(;this.length>1&&0===this.words[this.length-1];)this.length--;return this._normSign()},o.prototype._normSign=function(){return 1===this.length&&0===this.words[0]&&(this.negative=0),this},o.prototype.inspect=function(){return(this.red?"<BN-R: ":"<BN: ")+this.toString(16)+">"};var c=["","0","00","000","0000","00000","000000","0000000","00000000","000000000","0000000000","00000000000","000000000000","0000000000000","00000000000000","000000000000000","0000000000000000","00000000000000000","000000000000000000","0000000000000000000","00000000000000000000","000000000000000000000","0000000000000000000000","00000000000000000000000","000000000000000000000000","0000000000000000000000000"],f=[0,0,25,16,12,11,10,9,8,8,7,7,7,7,6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5],l=[0,0,33554432,43046721,16777216,48828125,60466176,40353607,16777216,43046721,1e7,19487171,35831808,62748517,7529536,11390625,16777216,24137569,34012224,47045881,64e6,4084101,5153632,6436343,7962624,9765625,11881376,14348907,17210368,20511149,243e5,28629151,33554432,39135393,45435424,52521875,60466176];function d(e,t,n){n.negative=t.negative^e.negative;var r=e.length+t.length|0;n.length=r,r=r-1|0;var i=0|e.words[0],o=0|t.words[0],s=i*o,a=67108863&s,u=s/67108864|0;n.words[0]=a;for(var c=1;c<r;c++){for(var f=u>>>26,l=67108863&u,d=Math.min(c,t.length-1),h=Math.max(0,c-e.length+1);h<=d;h++){var p=c-h|0;f+=(s=(i=0|e.words[p])*(o=0|t.words[h])+l)/67108864|0,l=67108863&s}n.words[c]=0|l,u=0|f}return 0!==u?n.words[c]=0|u:n.length--,n.strip()}o.prototype.toString=function(e,t){var n;if(t=0|t||1,16===(e=e||10)||"hex"===e){n="";for(var i=0,o=0,s=0;s<this.length;s++){var a=this.words[s],u=(16777215&(a<<i|o)).toString(16);n=0!==(o=a>>>24-i&16777215)||s!==this.length-1?c[6-u.length]+u+n:u+n,(i+=2)>=26&&(i-=26,s--)}for(0!==o&&(n=o.toString(16)+n);n.length%t!=0;)n="0"+n;return 0!==this.negative&&(n="-"+n),n}if(e===(0|e)&&e>=2&&e<=36){var d=f[e],h=l[e];n="";var p=this.clone();for(p.negative=0;!p.isZero();){var v=p.modn(h).toString(e);n=(p=p.idivn(h)).isZero()?v+n:c[d-v.length]+v+n}for(this.isZero()&&(n="0"+n);n.length%t!=0;)n="0"+n;return 0!==this.negative&&(n="-"+n),n}r(!1,"Base should be between 2 and 36")},o.prototype.toNumber=function(){var e=this.words[0];return 2===this.length?e+=67108864*this.words[1]:3===this.length&&1===this.words[2]?e+=4503599627370496+67108864*this.words[1]:this.length>2&&r(!1,"Number can only safely store up to 53 bits"),0!==this.negative?-e:e},o.prototype.toJSON=function(){return this.toString(16)},o.prototype.toBuffer=function(e,t){return r(void 0!==s),this.toArrayLike(s,e,t)},o.prototype.toArray=function(e,t){return this.toArrayLike(Array,e,t)},o.prototype.toArrayLike=function(e,t,n){var i=this.byteLength(),o=n||Math.max(1,i);r(i<=o,"byte array longer than desired length"),r(o>0,"Requested array length <= 0"),this.strip();var s,a,u="le"===t,c=new e(o),f=this.clone();if(u){for(a=0;!f.isZero();a++)s=f.andln(255),f.iushrn(8),c[a]=s;for(;a<o;a++)c[a]=0}else{for(a=0;a<o-i;a++)c[a]=0;for(a=0;!f.isZero();a++)s=f.andln(255),f.iushrn(8),c[o-a-1]=s}return c},Math.clz32?o.prototype._countBits=function(e){return 32-Math.clz32(e)}:o.prototype._countBits=function(e){var t=e,n=0;return t>=4096&&(n+=13,t>>>=13),t>=64&&(n+=7,t>>>=7),t>=8&&(n+=4,t>>>=4),t>=2&&(n+=2,t>>>=2),n+t},o.prototype._zeroBits=function(e){if(0===e)return 26;var t=e,n=0;return 0==(8191&t)&&(n+=13,t>>>=13),0==(127&t)&&(n+=7,t>>>=7),0==(15&t)&&(n+=4,t>>>=4),0==(3&t)&&(n+=2,t>>>=2),0==(1&t)&&n++,n},o.prototype.bitLength=function(){var e=this.words[this.length-1],t=this._countBits(e);return 26*(this.length-1)+t},o.prototype.zeroBits=function(){if(this.isZero())return 0;for(var e=0,t=0;t<this.length;t++){var n=this._zeroBits(this.words[t]);if(e+=n,26!==n)break}return e},o.prototype.byteLength=function(){return Math.ceil(this.bitLength()/8)},o.prototype.toTwos=function(e){return 0!==this.negative?this.abs().inotn(e).iaddn(1):this.clone()},o.prototype.fromTwos=function(e){return this.testn(e-1)?this.notn(e).iaddn(1).ineg():this.clone()},o.prototype.isNeg=function(){return 0!==this.negative},o.prototype.neg=function(){return this.clone().ineg()},o.prototype.ineg=function(){return this.isZero()||(this.negative^=1),this},o.prototype.iuor=function(e){for(;this.length<e.length;)this.words[this.length++]=0;for(var t=0;t<e.length;t++)this.words[t]=this.words[t]|e.words[t];return this.strip()},o.prototype.ior=function(e){return r(0==(this.negative|e.negative)),this.iuor(e)},o.prototype.or=function(e){return this.length>e.length?this.clone().ior(e):e.clone().ior(this)},o.prototype.uor=function(e){return this.length>e.length?this.clone().iuor(e):e.clone().iuor(this)},o.prototype.iuand=function(e){var t;t=this.length>e.length?e:this;for(var n=0;n<t.length;n++)this.words[n]=this.words[n]&e.words[n];return this.length=t.length,this.strip()},o.prototype.iand=function(e){return r(0==(this.negative|e.negative)),this.iuand(e)},o.prototype.and=function(e){return this.length>e.length?this.clone().iand(e):e.clone().iand(this)},o.prototype.uand=function(e){return this.length>e.length?this.clone().iuand(e):e.clone().iuand(this)},o.prototype.iuxor=function(e){var t,n;this.length>e.length?(t=this,n=e):(t=e,n=this);for(var r=0;r<n.length;r++)this.words[r]=t.words[r]^n.words[r];if(this!==t)for(;r<t.length;r++)this.words[r]=t.words[r];return this.length=t.length,this.strip()},o.prototype.ixor=function(e){return r(0==(this.negative|e.negative)),this.iuxor(e)},o.prototype.xor=function(e){return this.length>e.length?this.clone().ixor(e):e.clone().ixor(this)},o.prototype.uxor=function(e){return this.length>e.length?this.clone().iuxor(e):e.clone().iuxor(this)},o.prototype.inotn=function(e){r("number"==typeof e&&e>=0);var t=0|Math.ceil(e/26),n=e%26;this._expand(t),n>0&&t--;for(var i=0;i<t;i++)this.words[i]=67108863&~this.words[i];return n>0&&(this.words[i]=~this.words[i]&67108863>>26-n),this.strip()},o.prototype.notn=function(e){return this.clone().inotn(e)},o.prototype.setn=function(e,t){r("number"==typeof e&&e>=0);var n=e/26|0,i=e%26;return this._expand(n+1),this.words[n]=t?this.words[n]|1<<i:this.words[n]&~(1<<i),this.strip()},o.prototype.iadd=function(e){var t,n,r;if(0!==this.negative&&0===e.negative)return this.negative=0,t=this.isub(e),this.negative^=1,this._normSign();if(0===this.negative&&0!==e.negative)return e.negative=0,t=this.isub(e),e.negative=1,t._normSign();this.length>e.length?(n=this,r=e):(n=e,r=this);for(var i=0,o=0;o<r.length;o++)t=(0|n.words[o])+(0|r.words[o])+i,this.words[o]=67108863&t,i=t>>>26;for(;0!==i&&o<n.length;o++)t=(0|n.words[o])+i,this.words[o]=67108863&t,i=t>>>26;if(this.length=n.length,0!==i)this.words[this.length]=i,this.length++;else if(n!==this)for(;o<n.length;o++)this.words[o]=n.words[o];return this},o.prototype.add=function(e){var t;return 0!==e.negative&&0===this.negative?(e.negative=0,t=this.sub(e),e.negative^=1,t):0===e.negative&&0!==this.negative?(this.negative=0,t=e.sub(this),this.negative=1,t):this.length>e.length?this.clone().iadd(e):e.clone().iadd(this)},o.prototype.isub=function(e){if(0!==e.negative){e.negative=0;var t=this.iadd(e);return e.negative=1,t._normSign()}if(0!==this.negative)return this.negative=0,this.iadd(e),this.negative=1,this._normSign();var n,r,i=this.cmp(e);if(0===i)return this.negative=0,this.length=1,this.words[0]=0,this;i>0?(n=this,r=e):(n=e,r=this);for(var o=0,s=0;s<r.length;s++)o=(t=(0|n.words[s])-(0|r.words[s])+o)>>26,this.words[s]=67108863&t;for(;0!==o&&s<n.length;s++)o=(t=(0|n.words[s])+o)>>26,this.words[s]=67108863&t;if(0===o&&s<n.length&&n!==this)for(;s<n.length;s++)this.words[s]=n.words[s];return this.length=Math.max(this.length,s),n!==this&&(this.negative=1),this.strip()},o.prototype.sub=function(e){return this.clone().isub(e)};var h=function(e,t,n){var r,i,o,s=e.words,a=t.words,u=n.words,c=0,f=0|s[0],l=8191&f,d=f>>>13,h=0|s[1],p=8191&h,v=h>>>13,g=0|s[2],m=8191&g,b=g>>>13,y=0|s[3],w=8191&y,_=y>>>13,S=0|s[4],E=8191&S,M=S>>>13,A=0|s[5],I=8191&A,k=A>>>13,O=0|s[6],x=8191&O,C=O>>>13,T=0|s[7],P=8191&T,N=T>>>13,R=0|s[8],L=8191&R,j=R>>>13,D=0|s[9],U=8191&D,B=D>>>13,F=0|a[0],z=8191&F,q=F>>>13,K=0|a[1],H=8191&K,V=K>>>13,G=0|a[2],W=8191&G,$=G>>>13,Y=0|a[3],J=8191&Y,Z=Y>>>13,X=0|a[4],Q=8191&X,ee=X>>>13,te=0|a[5],ne=8191&te,re=te>>>13,ie=0|a[6],oe=8191&ie,se=ie>>>13,ae=0|a[7],ue=8191&ae,ce=ae>>>13,fe=0|a[8],le=8191&fe,de=fe>>>13,he=0|a[9],pe=8191&he,ve=he>>>13;n.negative=e.negative^t.negative,n.length=19;var ge=(c+(r=Math.imul(l,z))|0)+((8191&(i=(i=Math.imul(l,q))+Math.imul(d,z)|0))<<13)|0;c=((o=Math.imul(d,q))+(i>>>13)|0)+(ge>>>26)|0,ge&=67108863,r=Math.imul(p,z),i=(i=Math.imul(p,q))+Math.imul(v,z)|0,o=Math.imul(v,q);var me=(c+(r=r+Math.imul(l,H)|0)|0)+((8191&(i=(i=i+Math.imul(l,V)|0)+Math.imul(d,H)|0))<<13)|0;c=((o=o+Math.imul(d,V)|0)+(i>>>13)|0)+(me>>>26)|0,me&=67108863,r=Math.imul(m,z),i=(i=Math.imul(m,q))+Math.imul(b,z)|0,o=Math.imul(b,q),r=r+Math.imul(p,H)|0,i=(i=i+Math.imul(p,V)|0)+Math.imul(v,H)|0,o=o+Math.imul(v,V)|0;var be=(c+(r=r+Math.imul(l,W)|0)|0)+((8191&(i=(i=i+Math.imul(l,$)|0)+Math.imul(d,W)|0))<<13)|0;c=((o=o+Math.imul(d,$)|0)+(i>>>13)|0)+(be>>>26)|0,be&=67108863,r=Math.imul(w,z),i=(i=Math.imul(w,q))+Math.imul(_,z)|0,o=Math.imul(_,q),r=r+Math.imul(m,H)|0,i=(i=i+Math.imul(m,V)|0)+Math.imul(b,H)|0,o=o+Math.imul(b,V)|0,r=r+Math.imul(p,W)|0,i=(i=i+Math.imul(p,$)|0)+Math.imul(v,W)|0,o=o+Math.imul(v,$)|0;var ye=(c+(r=r+Math.imul(l,J)|0)|0)+((8191&(i=(i=i+Math.imul(l,Z)|0)+Math.imul(d,J)|0))<<13)|0;c=((o=o+Math.imul(d,Z)|0)+(i>>>13)|0)+(ye>>>26)|0,ye&=67108863,r=Math.imul(E,z),i=(i=Math.imul(E,q))+Math.imul(M,z)|0,o=Math.imul(M,q),r=r+Math.imul(w,H)|0,i=(i=i+Math.imul(w,V)|0)+Math.imul(_,H)|0,o=o+Math.imul(_,V)|0,r=r+Math.imul(m,W)|0,i=(i=i+Math.imul(m,$)|0)+Math.imul(b,W)|0,o=o+Math.imul(b,$)|0,r=r+Math.imul(p,J)|0,i=(i=i+Math.imul(p,Z)|0)+Math.imul(v,J)|0,o=o+Math.imul(v,Z)|0;var we=(c+(r=r+Math.imul(l,Q)|0)|0)+((8191&(i=(i=i+Math.imul(l,ee)|0)+Math.imul(d,Q)|0))<<13)|0;c=((o=o+Math.imul(d,ee)|0)+(i>>>13)|0)+(we>>>26)|0,we&=67108863,r=Math.imul(I,z),i=(i=Math.imul(I,q))+Math.imul(k,z)|0,o=Math.imul(k,q),r=r+Math.imul(E,H)|0,i=(i=i+Math.imul(E,V)|0)+Math.imul(M,H)|0,o=o+Math.imul(M,V)|0,r=r+Math.imul(w,W)|0,i=(i=i+Math.imul(w,$)|0)+Math.imul(_,W)|0,o=o+Math.imul(_,$)|0,r=r+Math.imul(m,J)|0,i=(i=i+Math.imul(m,Z)|0)+Math.imul(b,J)|0,o=o+Math.imul(b,Z)|0,r=r+Math.imul(p,Q)|0,i=(i=i+Math.imul(p,ee)|0)+Math.imul(v,Q)|0,o=o+Math.imul(v,ee)|0;var _e=(c+(r=r+Math.imul(l,ne)|0)|0)+((8191&(i=(i=i+Math.imul(l,re)|0)+Math.imul(d,ne)|0))<<13)|0;c=((o=o+Math.imul(d,re)|0)+(i>>>13)|0)+(_e>>>26)|0,_e&=67108863,r=Math.imul(x,z),i=(i=Math.imul(x,q))+Math.imul(C,z)|0,o=Math.imul(C,q),r=r+Math.imul(I,H)|0,i=(i=i+Math.imul(I,V)|0)+Math.imul(k,H)|0,o=o+Math.imul(k,V)|0,r=r+Math.imul(E,W)|0,i=(i=i+Math.imul(E,$)|0)+Math.imul(M,W)|0,o=o+Math.imul(M,$)|0,r=r+Math.imul(w,J)|0,i=(i=i+Math.imul(w,Z)|0)+Math.imul(_,J)|0,o=o+Math.imul(_,Z)|0,r=r+Math.imul(m,Q)|0,i=(i=i+Math.imul(m,ee)|0)+Math.imul(b,Q)|0,o=o+Math.imul(b,ee)|0,r=r+Math.imul(p,ne)|0,i=(i=i+Math.imul(p,re)|0)+Math.imul(v,ne)|0,o=o+Math.imul(v,re)|0;var Se=(c+(r=r+Math.imul(l,oe)|0)|0)+((8191&(i=(i=i+Math.imul(l,se)|0)+Math.imul(d,oe)|0))<<13)|0;c=((o=o+Math.imul(d,se)|0)+(i>>>13)|0)+(Se>>>26)|0,Se&=67108863,r=Math.imul(P,z),i=(i=Math.imul(P,q))+Math.imul(N,z)|0,o=Math.imul(N,q),r=r+Math.imul(x,H)|0,i=(i=i+Math.imul(x,V)|0)+Math.imul(C,H)|0,o=o+Math.imul(C,V)|0,r=r+Math.imul(I,W)|0,i=(i=i+Math.imul(I,$)|0)+Math.imul(k,W)|0,o=o+Math.imul(k,$)|0,r=r+Math.imul(E,J)|0,i=(i=i+Math.imul(E,Z)|0)+Math.imul(M,J)|0,o=o+Math.imul(M,Z)|0,r=r+Math.imul(w,Q)|0,i=(i=i+Math.imul(w,ee)|0)+Math.imul(_,Q)|0,o=o+Math.imul(_,ee)|0,r=r+Math.imul(m,ne)|0,i=(i=i+Math.imul(m,re)|0)+Math.imul(b,ne)|0,o=o+Math.imul(b,re)|0,r=r+Math.imul(p,oe)|0,i=(i=i+Math.imul(p,se)|0)+Math.imul(v,oe)|0,o=o+Math.imul(v,se)|0;var Ee=(c+(r=r+Math.imul(l,ue)|0)|0)+((8191&(i=(i=i+Math.imul(l,ce)|0)+Math.imul(d,ue)|0))<<13)|0;c=((o=o+Math.imul(d,ce)|0)+(i>>>13)|0)+(Ee>>>26)|0,Ee&=67108863,r=Math.imul(L,z),i=(i=Math.imul(L,q))+Math.imul(j,z)|0,o=Math.imul(j,q),r=r+Math.imul(P,H)|0,i=(i=i+Math.imul(P,V)|0)+Math.imul(N,H)|0,o=o+Math.imul(N,V)|0,r=r+Math.imul(x,W)|0,i=(i=i+Math.imul(x,$)|0)+Math.imul(C,W)|0,o=o+Math.imul(C,$)|0,r=r+Math.imul(I,J)|0,i=(i=i+Math.imul(I,Z)|0)+Math.imul(k,J)|0,o=o+Math.imul(k,Z)|0,r=r+Math.imul(E,Q)|0,i=(i=i+Math.imul(E,ee)|0)+Math.imul(M,Q)|0,o=o+Math.imul(M,ee)|0,r=r+Math.imul(w,ne)|0,i=(i=i+Math.imul(w,re)|0)+Math.imul(_,ne)|0,o=o+Math.imul(_,re)|0,r=r+Math.imul(m,oe)|0,i=(i=i+Math.imul(m,se)|0)+Math.imul(b,oe)|0,o=o+Math.imul(b,se)|0,r=r+Math.imul(p,ue)|0,i=(i=i+Math.imul(p,ce)|0)+Math.imul(v,ue)|0,o=o+Math.imul(v,ce)|0;var Me=(c+(r=r+Math.imul(l,le)|0)|0)+((8191&(i=(i=i+Math.imul(l,de)|0)+Math.imul(d,le)|0))<<13)|0;c=((o=o+Math.imul(d,de)|0)+(i>>>13)|0)+(Me>>>26)|0,Me&=67108863,r=Math.imul(U,z),i=(i=Math.imul(U,q))+Math.imul(B,z)|0,o=Math.imul(B,q),r=r+Math.imul(L,H)|0,i=(i=i+Math.imul(L,V)|0)+Math.imul(j,H)|0,o=o+Math.imul(j,V)|0,r=r+Math.imul(P,W)|0,i=(i=i+Math.imul(P,$)|0)+Math.imul(N,W)|0,o=o+Math.imul(N,$)|0,r=r+Math.imul(x,J)|0,i=(i=i+Math.imul(x,Z)|0)+Math.imul(C,J)|0,o=o+Math.imul(C,Z)|0,r=r+Math.imul(I,Q)|0,i=(i=i+Math.imul(I,ee)|0)+Math.imul(k,Q)|0,o=o+Math.imul(k,ee)|0,r=r+Math.imul(E,ne)|0,i=(i=i+Math.imul(E,re)|0)+Math.imul(M,ne)|0,o=o+Math.imul(M,re)|0,r=r+Math.imul(w,oe)|0,i=(i=i+Math.imul(w,se)|0)+Math.imul(_,oe)|0,o=o+Math.imul(_,se)|0,r=r+Math.imul(m,ue)|0,i=(i=i+Math.imul(m,ce)|0)+Math.imul(b,ue)|0,o=o+Math.imul(b,ce)|0,r=r+Math.imul(p,le)|0,i=(i=i+Math.imul(p,de)|0)+Math.imul(v,le)|0,o=o+Math.imul(v,de)|0;var Ae=(c+(r=r+Math.imul(l,pe)|0)|0)+((8191&(i=(i=i+Math.imul(l,ve)|0)+Math.imul(d,pe)|0))<<13)|0;c=((o=o+Math.imul(d,ve)|0)+(i>>>13)|0)+(Ae>>>26)|0,Ae&=67108863,r=Math.imul(U,H),i=(i=Math.imul(U,V))+Math.imul(B,H)|0,o=Math.imul(B,V),r=r+Math.imul(L,W)|0,i=(i=i+Math.imul(L,$)|0)+Math.imul(j,W)|0,o=o+Math.imul(j,$)|0,r=r+Math.imul(P,J)|0,i=(i=i+Math.imul(P,Z)|0)+Math.imul(N,J)|0,o=o+Math.imul(N,Z)|0,r=r+Math.imul(x,Q)|0,i=(i=i+Math.imul(x,ee)|0)+Math.imul(C,Q)|0,o=o+Math.imul(C,ee)|0,r=r+Math.imul(I,ne)|0,i=(i=i+Math.imul(I,re)|0)+Math.imul(k,ne)|0,o=o+Math.imul(k,re)|0,r=r+Math.imul(E,oe)|0,i=(i=i+Math.imul(E,se)|0)+Math.imul(M,oe)|0,o=o+Math.imul(M,se)|0,r=r+Math.imul(w,ue)|0,i=(i=i+Math.imul(w,ce)|0)+Math.imul(_,ue)|0,o=o+Math.imul(_,ce)|0,r=r+Math.imul(m,le)|0,i=(i=i+Math.imul(m,de)|0)+Math.imul(b,le)|0,o=o+Math.imul(b,de)|0;var Ie=(c+(r=r+Math.imul(p,pe)|0)|0)+((8191&(i=(i=i+Math.imul(p,ve)|0)+Math.imul(v,pe)|0))<<13)|0;c=((o=o+Math.imul(v,ve)|0)+(i>>>13)|0)+(Ie>>>26)|0,Ie&=67108863,r=Math.imul(U,W),i=(i=Math.imul(U,$))+Math.imul(B,W)|0,o=Math.imul(B,$),r=r+Math.imul(L,J)|0,i=(i=i+Math.imul(L,Z)|0)+Math.imul(j,J)|0,o=o+Math.imul(j,Z)|0,r=r+Math.imul(P,Q)|0,i=(i=i+Math.imul(P,ee)|0)+Math.imul(N,Q)|0,o=o+Math.imul(N,ee)|0,r=r+Math.imul(x,ne)|0,i=(i=i+Math.imul(x,re)|0)+Math.imul(C,ne)|0,o=o+Math.imul(C,re)|0,r=r+Math.imul(I,oe)|0,i=(i=i+Math.imul(I,se)|0)+Math.imul(k,oe)|0,o=o+Math.imul(k,se)|0,r=r+Math.imul(E,ue)|0,i=(i=i+Math.imul(E,ce)|0)+Math.imul(M,ue)|0,o=o+Math.imul(M,ce)|0,r=r+Math.imul(w,le)|0,i=(i=i+Math.imul(w,de)|0)+Math.imul(_,le)|0,o=o+Math.imul(_,de)|0;var ke=(c+(r=r+Math.imul(m,pe)|0)|0)+((8191&(i=(i=i+Math.imul(m,ve)|0)+Math.imul(b,pe)|0))<<13)|0;c=((o=o+Math.imul(b,ve)|0)+(i>>>13)|0)+(ke>>>26)|0,ke&=67108863,r=Math.imul(U,J),i=(i=Math.imul(U,Z))+Math.imul(B,J)|0,o=Math.imul(B,Z),r=r+Math.imul(L,Q)|0,i=(i=i+Math.imul(L,ee)|0)+Math.imul(j,Q)|0,o=o+Math.imul(j,ee)|0,r=r+Math.imul(P,ne)|0,i=(i=i+Math.imul(P,re)|0)+Math.imul(N,ne)|0,o=o+Math.imul(N,re)|0,r=r+Math.imul(x,oe)|0,i=(i=i+Math.imul(x,se)|0)+Math.imul(C,oe)|0,o=o+Math.imul(C,se)|0,r=r+Math.imul(I,ue)|0,i=(i=i+Math.imul(I,ce)|0)+Math.imul(k,ue)|0,o=o+Math.imul(k,ce)|0,r=r+Math.imul(E,le)|0,i=(i=i+Math.imul(E,de)|0)+Math.imul(M,le)|0,o=o+Math.imul(M,de)|0;var Oe=(c+(r=r+Math.imul(w,pe)|0)|0)+((8191&(i=(i=i+Math.imul(w,ve)|0)+Math.imul(_,pe)|0))<<13)|0;c=((o=o+Math.imul(_,ve)|0)+(i>>>13)|0)+(Oe>>>26)|0,Oe&=67108863,r=Math.imul(U,Q),i=(i=Math.imul(U,ee))+Math.imul(B,Q)|0,o=Math.imul(B,ee),r=r+Math.imul(L,ne)|0,i=(i=i+Math.imul(L,re)|0)+Math.imul(j,ne)|0,o=o+Math.imul(j,re)|0,r=r+Math.imul(P,oe)|0,i=(i=i+Math.imul(P,se)|0)+Math.imul(N,oe)|0,o=o+Math.imul(N,se)|0,r=r+Math.imul(x,ue)|0,i=(i=i+Math.imul(x,ce)|0)+Math.imul(C,ue)|0,o=o+Math.imul(C,ce)|0,r=r+Math.imul(I,le)|0,i=(i=i+Math.imul(I,de)|0)+Math.imul(k,le)|0,o=o+Math.imul(k,de)|0;var xe=(c+(r=r+Math.imul(E,pe)|0)|0)+((8191&(i=(i=i+Math.imul(E,ve)|0)+Math.imul(M,pe)|0))<<13)|0;c=((o=o+Math.imul(M,ve)|0)+(i>>>13)|0)+(xe>>>26)|0,xe&=67108863,r=Math.imul(U,ne),i=(i=Math.imul(U,re))+Math.imul(B,ne)|0,o=Math.imul(B,re),r=r+Math.imul(L,oe)|0,i=(i=i+Math.imul(L,se)|0)+Math.imul(j,oe)|0,o=o+Math.imul(j,se)|0,r=r+Math.imul(P,ue)|0,i=(i=i+Math.imul(P,ce)|0)+Math.imul(N,ue)|0,o=o+Math.imul(N,ce)|0,r=r+Math.imul(x,le)|0,i=(i=i+Math.imul(x,de)|0)+Math.imul(C,le)|0,o=o+Math.imul(C,de)|0;var Ce=(c+(r=r+Math.imul(I,pe)|0)|0)+((8191&(i=(i=i+Math.imul(I,ve)|0)+Math.imul(k,pe)|0))<<13)|0;c=((o=o+Math.imul(k,ve)|0)+(i>>>13)|0)+(Ce>>>26)|0,Ce&=67108863,r=Math.imul(U,oe),i=(i=Math.imul(U,se))+Math.imul(B,oe)|0,o=Math.imul(B,se),r=r+Math.imul(L,ue)|0,i=(i=i+Math.imul(L,ce)|0)+Math.imul(j,ue)|0,o=o+Math.imul(j,ce)|0,r=r+Math.imul(P,le)|0,i=(i=i+Math.imul(P,de)|0)+Math.imul(N,le)|0,o=o+Math.imul(N,de)|0;var Te=(c+(r=r+Math.imul(x,pe)|0)|0)+((8191&(i=(i=i+Math.imul(x,ve)|0)+Math.imul(C,pe)|0))<<13)|0;c=((o=o+Math.imul(C,ve)|0)+(i>>>13)|0)+(Te>>>26)|0,Te&=67108863,r=Math.imul(U,ue),i=(i=Math.imul(U,ce))+Math.imul(B,ue)|0,o=Math.imul(B,ce),r=r+Math.imul(L,le)|0,i=(i=i+Math.imul(L,de)|0)+Math.imul(j,le)|0,o=o+Math.imul(j,de)|0;var Pe=(c+(r=r+Math.imul(P,pe)|0)|0)+((8191&(i=(i=i+Math.imul(P,ve)|0)+Math.imul(N,pe)|0))<<13)|0;c=((o=o+Math.imul(N,ve)|0)+(i>>>13)|0)+(Pe>>>26)|0,Pe&=67108863,r=Math.imul(U,le),i=(i=Math.imul(U,de))+Math.imul(B,le)|0,o=Math.imul(B,de);var Ne=(c+(r=r+Math.imul(L,pe)|0)|0)+((8191&(i=(i=i+Math.imul(L,ve)|0)+Math.imul(j,pe)|0))<<13)|0;c=((o=o+Math.imul(j,ve)|0)+(i>>>13)|0)+(Ne>>>26)|0,Ne&=67108863;var Re=(c+(r=Math.imul(U,pe))|0)+((8191&(i=(i=Math.imul(U,ve))+Math.imul(B,pe)|0))<<13)|0;return c=((o=Math.imul(B,ve))+(i>>>13)|0)+(Re>>>26)|0,Re&=67108863,u[0]=ge,u[1]=me,u[2]=be,u[3]=ye,u[4]=we,u[5]=_e,u[6]=Se,u[7]=Ee,u[8]=Me,u[9]=Ae,u[10]=Ie,u[11]=ke,u[12]=Oe,u[13]=xe,u[14]=Ce,u[15]=Te,u[16]=Pe,u[17]=Ne,u[18]=Re,0!==c&&(u[19]=c,n.length++),n};function p(e,t,n){return(new v).mulp(e,t,n)}function v(e,t){this.x=e,this.y=t}Math.imul||(h=d),o.prototype.mulTo=function(e,t){var n=this.length+e.length;return 10===this.length&&10===e.length?h(this,e,t):n<63?d(this,e,t):n<1024?function(e,t,n){n.negative=t.negative^e.negative,n.length=e.length+t.length;for(var r=0,i=0,o=0;o<n.length-1;o++){var s=i;i=0;for(var a=67108863&r,u=Math.min(o,t.length-1),c=Math.max(0,o-e.length+1);c<=u;c++){var f=o-c,l=(0|e.words[f])*(0|t.words[c]),d=67108863&l;a=67108863&(d=d+a|0),i+=(s=(s=s+(l/67108864|0)|0)+(d>>>26)|0)>>>26,s&=67108863}n.words[o]=a,r=s,s=i}return 0!==r?n.words[o]=r:n.length--,n.strip()}(this,e,t):p(this,e,t)},v.prototype.makeRBT=function(e){for(var t=new Array(e),n=o.prototype._countBits(e)-1,r=0;r<e;r++)t[r]=this.revBin(r,n,e);return t},v.prototype.revBin=function(e,t,n){if(0===e||e===n-1)return e;for(var r=0,i=0;i<t;i++)r|=(1&e)<<t-i-1,e>>=1;return r},v.prototype.permute=function(e,t,n,r,i,o){for(var s=0;s<o;s++)r[s]=t[e[s]],i[s]=n[e[s]]},v.prototype.transform=function(e,t,n,r,i,o){this.permute(o,e,t,n,r,i);for(var s=1;s<i;s<<=1)for(var a=s<<1,u=Math.cos(2*Math.PI/a),c=Math.sin(2*Math.PI/a),f=0;f<i;f+=a)for(var l=u,d=c,h=0;h<s;h++){var p=n[f+h],v=r[f+h],g=n[f+h+s],m=r[f+h+s],b=l*g-d*m;m=l*m+d*g,g=b,n[f+h]=p+g,r[f+h]=v+m,n[f+h+s]=p-g,r[f+h+s]=v-m,h!==a&&(b=u*l-c*d,d=u*d+c*l,l=b)}},v.prototype.guessLen13b=function(e,t){var n=1|Math.max(t,e),r=1&n,i=0;for(n=n/2|0;n;n>>>=1)i++;return 1<<i+1+r},v.prototype.conjugate=function(e,t,n){if(!(n<=1))for(var r=0;r<n/2;r++){var i=e[r];e[r]=e[n-r-1],e[n-r-1]=i,i=t[r],t[r]=-t[n-r-1],t[n-r-1]=-i}},v.prototype.normalize13b=function(e,t){for(var n=0,r=0;r<t/2;r++){var i=8192*Math.round(e[2*r+1]/t)+Math.round(e[2*r]/t)+n;e[r]=67108863&i,n=i<67108864?0:i/67108864|0}return e},v.prototype.convert13b=function(e,t,n,i){for(var o=0,s=0;s<t;s++)o+=0|e[s],n[2*s]=8191&o,o>>>=13,n[2*s+1]=8191&o,o>>>=13;for(s=2*t;s<i;++s)n[s]=0;r(0===o),r(0==(-8192&o))},v.prototype.stub=function(e){for(var t=new Array(e),n=0;n<e;n++)t[n]=0;return t},v.prototype.mulp=function(e,t,n){var r=2*this.guessLen13b(e.length,t.length),i=this.makeRBT(r),o=this.stub(r),s=new Array(r),a=new Array(r),u=new Array(r),c=new Array(r),f=new Array(r),l=new Array(r),d=n.words;d.length=r,this.convert13b(e.words,e.length,s,r),this.convert13b(t.words,t.length,c,r),this.transform(s,o,a,u,r,i),this.transform(c,o,f,l,r,i);for(var h=0;h<r;h++){var p=a[h]*f[h]-u[h]*l[h];u[h]=a[h]*l[h]+u[h]*f[h],a[h]=p}return this.conjugate(a,u,r),this.transform(a,u,d,o,r,i),this.conjugate(d,o,r),this.normalize13b(d,r),n.negative=e.negative^t.negative,n.length=e.length+t.length,n.strip()},o.prototype.mul=function(e){var t=new o(null);return t.words=new Array(this.length+e.length),this.mulTo(e,t)},o.prototype.mulf=function(e){var t=new o(null);return t.words=new Array(this.length+e.length),p(this,e,t)},o.prototype.imul=function(e){return this.clone().mulTo(e,this)},o.prototype.imuln=function(e){r("number"==typeof e),r(e<67108864);for(var t=0,n=0;n<this.length;n++){var i=(0|this.words[n])*e,o=(67108863&i)+(67108863&t);t>>=26,t+=i/67108864|0,t+=o>>>26,this.words[n]=67108863&o}return 0!==t&&(this.words[n]=t,this.length++),this},o.prototype.muln=function(e){return this.clone().imuln(e)},o.prototype.sqr=function(){return this.mul(this)},o.prototype.isqr=function(){return this.imul(this.clone())},o.prototype.pow=function(e){var t=function(e){for(var t=new Array(e.bitLength()),n=0;n<t.length;n++){var r=n/26|0,i=n%26;t[n]=(e.words[r]&1<<i)>>>i}return t}(e);if(0===t.length)return new o(1);for(var n=this,r=0;r<t.length&&0===t[r];r++,n=n.sqr());if(++r<t.length)for(var i=n.sqr();r<t.length;r++,i=i.sqr())0!==t[r]&&(n=n.mul(i));return n},o.prototype.iushln=function(e){r("number"==typeof e&&e>=0);var t,n=e%26,i=(e-n)/26,o=67108863>>>26-n<<26-n;if(0!==n){var s=0;for(t=0;t<this.length;t++){var a=this.words[t]&o,u=(0|this.words[t])-a<<n;this.words[t]=u|s,s=a>>>26-n}s&&(this.words[t]=s,this.length++)}if(0!==i){for(t=this.length-1;t>=0;t--)this.words[t+i]=this.words[t];for(t=0;t<i;t++)this.words[t]=0;this.length+=i}return this.strip()},o.prototype.ishln=function(e){return r(0===this.negative),this.iushln(e)},o.prototype.iushrn=function(e,t,n){var i;r("number"==typeof e&&e>=0),i=t?(t-t%26)/26:0;var o=e%26,s=Math.min((e-o)/26,this.length),a=67108863^67108863>>>o<<o,u=n;if(i-=s,i=Math.max(0,i),u){for(var c=0;c<s;c++)u.words[c]=this.words[c];u.length=s}if(0===s);else if(this.length>s)for(this.length-=s,c=0;c<this.length;c++)this.words[c]=this.words[c+s];else this.words[0]=0,this.length=1;var f=0;for(c=this.length-1;c>=0&&(0!==f||c>=i);c--){var l=0|this.words[c];this.words[c]=f<<26-o|l>>>o,f=l&a}return u&&0!==f&&(u.words[u.length++]=f),0===this.length&&(this.words[0]=0,this.length=1),this.strip()},o.prototype.ishrn=function(e,t,n){return r(0===this.negative),this.iushrn(e,t,n)},o.prototype.shln=function(e){return this.clone().ishln(e)},o.prototype.ushln=function(e){return this.clone().iushln(e)},o.prototype.shrn=function(e){return this.clone().ishrn(e)},o.prototype.ushrn=function(e){return this.clone().iushrn(e)},o.prototype.testn=function(e){r("number"==typeof e&&e>=0);var t=e%26,n=(e-t)/26,i=1<<t;return!(this.length<=n)&&!!(this.words[n]&i)},o.prototype.imaskn=function(e){r("number"==typeof e&&e>=0);var t=e%26,n=(e-t)/26;if(r(0===this.negative,"imaskn works only with positive numbers"),this.length<=n)return this;if(0!==t&&n++,this.length=Math.min(n,this.length),0!==t){var i=67108863^67108863>>>t<<t;this.words[this.length-1]&=i}return this.strip()},o.prototype.maskn=function(e){return this.clone().imaskn(e)},o.prototype.iaddn=function(e){return r("number"==typeof e),r(e<67108864),e<0?this.isubn(-e):0!==this.negative?1===this.length&&(0|this.words[0])<e?(this.words[0]=e-(0|this.words[0]),this.negative=0,this):(this.negative=0,this.isubn(e),this.negative=1,this):this._iaddn(e)},o.prototype._iaddn=function(e){this.words[0]+=e;for(var t=0;t<this.length&&this.words[t]>=67108864;t++)this.words[t]-=67108864,t===this.length-1?this.words[t+1]=1:this.words[t+1]++;return this.length=Math.max(this.length,t+1),this},o.prototype.isubn=function(e){if(r("number"==typeof e),r(e<67108864),e<0)return this.iaddn(-e);if(0!==this.negative)return this.negative=0,this.iaddn(e),this.negative=1,this;if(this.words[0]-=e,1===this.length&&this.words[0]<0)this.words[0]=-this.words[0],this.negative=1;else for(var t=0;t<this.length&&this.words[t]<0;t++)this.words[t]+=67108864,this.words[t+1]-=1;return this.strip()},o.prototype.addn=function(e){return this.clone().iaddn(e)},o.prototype.subn=function(e){return this.clone().isubn(e)},o.prototype.iabs=function(){return this.negative=0,this},o.prototype.abs=function(){return this.clone().iabs()},o.prototype._ishlnsubmul=function(e,t,n){var i,o,s=e.length+n;this._expand(s);var a=0;for(i=0;i<e.length;i++){o=(0|this.words[i+n])+a;var u=(0|e.words[i])*t;a=((o-=67108863&u)>>26)-(u/67108864|0),this.words[i+n]=67108863&o}for(;i<this.length-n;i++)a=(o=(0|this.words[i+n])+a)>>26,this.words[i+n]=67108863&o;if(0===a)return this.strip();for(r(-1===a),a=0,i=0;i<this.length;i++)a=(o=-(0|this.words[i])+a)>>26,this.words[i]=67108863&o;return this.negative=1,this.strip()},o.prototype._wordDiv=function(e,t){var n=(this.length,e.length),r=this.clone(),i=e,s=0|i.words[i.length-1];0!==(n=26-this._countBits(s))&&(i=i.ushln(n),r.iushln(n),s=0|i.words[i.length-1]);var a,u=r.length-i.length;if("mod"!==t){(a=new o(null)).length=u+1,a.words=new Array(a.length);for(var c=0;c<a.length;c++)a.words[c]=0}var f=r.clone()._ishlnsubmul(i,1,u);0===f.negative&&(r=f,a&&(a.words[u]=1));for(var l=u-1;l>=0;l--){var d=67108864*(0|r.words[i.length+l])+(0|r.words[i.length+l-1]);for(d=Math.min(d/s|0,67108863),r._ishlnsubmul(i,d,l);0!==r.negative;)d--,r.negative=0,r._ishlnsubmul(i,1,l),r.isZero()||(r.negative^=1);a&&(a.words[l]=d)}return a&&a.strip(),r.strip(),"div"!==t&&0!==n&&r.iushrn(n),{div:a||null,mod:r}},o.prototype.divmod=function(e,t,n){return r(!e.isZero()),this.isZero()?{div:new o(0),mod:new o(0)}:0!==this.negative&&0===e.negative?(a=this.neg().divmod(e,t),"mod"!==t&&(i=a.div.neg()),"div"!==t&&(s=a.mod.neg(),n&&0!==s.negative&&s.iadd(e)),{div:i,mod:s}):0===this.negative&&0!==e.negative?(a=this.divmod(e.neg(),t),"mod"!==t&&(i=a.div.neg()),{div:i,mod:a.mod}):0!=(this.negative&e.negative)?(a=this.neg().divmod(e.neg(),t),"div"!==t&&(s=a.mod.neg(),n&&0!==s.negative&&s.isub(e)),{div:a.div,mod:s}):e.length>this.length||this.cmp(e)<0?{div:new o(0),mod:this}:1===e.length?"div"===t?{div:this.divn(e.words[0]),mod:null}:"mod"===t?{div:null,mod:new o(this.modn(e.words[0]))}:{div:this.divn(e.words[0]),mod:new o(this.modn(e.words[0]))}:this._wordDiv(e,t);var i,s,a},o.prototype.div=function(e){return this.divmod(e,"div",!1).div},o.prototype.mod=function(e){return this.divmod(e,"mod",!1).mod},o.prototype.umod=function(e){return this.divmod(e,"mod",!0).mod},o.prototype.divRound=function(e){var t=this.divmod(e);if(t.mod.isZero())return t.div;var n=0!==t.div.negative?t.mod.isub(e):t.mod,r=e.ushrn(1),i=e.andln(1),o=n.cmp(r);return o<0||1===i&&0===o?t.div:0!==t.div.negative?t.div.isubn(1):t.div.iaddn(1)},o.prototype.modn=function(e){r(e<=67108863);for(var t=(1<<26)%e,n=0,i=this.length-1;i>=0;i--)n=(t*n+(0|this.words[i]))%e;return n},o.prototype.idivn=function(e){r(e<=67108863);for(var t=0,n=this.length-1;n>=0;n--){var i=(0|this.words[n])+67108864*t;this.words[n]=i/e|0,t=i%e}return this.strip()},o.prototype.divn=function(e){return this.clone().idivn(e)},o.prototype.egcd=function(e){r(0===e.negative),r(!e.isZero());var t=this,n=e.clone();t=0!==t.negative?t.umod(e):t.clone();for(var i=new o(1),s=new o(0),a=new o(0),u=new o(1),c=0;t.isEven()&&n.isEven();)t.iushrn(1),n.iushrn(1),++c;for(var f=n.clone(),l=t.clone();!t.isZero();){for(var d=0,h=1;0==(t.words[0]&h)&&d<26;++d,h<<=1);if(d>0)for(t.iushrn(d);d-- >0;)(i.isOdd()||s.isOdd())&&(i.iadd(f),s.isub(l)),i.iushrn(1),s.iushrn(1);for(var p=0,v=1;0==(n.words[0]&v)&&p<26;++p,v<<=1);if(p>0)for(n.iushrn(p);p-- >0;)(a.isOdd()||u.isOdd())&&(a.iadd(f),u.isub(l)),a.iushrn(1),u.iushrn(1);t.cmp(n)>=0?(t.isub(n),i.isub(a),s.isub(u)):(n.isub(t),a.isub(i),u.isub(s))}return{a:a,b:u,gcd:n.iushln(c)}},o.prototype._invmp=function(e){r(0===e.negative),r(!e.isZero());var t=this,n=e.clone();t=0!==t.negative?t.umod(e):t.clone();for(var i,s=new o(1),a=new o(0),u=n.clone();t.cmpn(1)>0&&n.cmpn(1)>0;){for(var c=0,f=1;0==(t.words[0]&f)&&c<26;++c,f<<=1);if(c>0)for(t.iushrn(c);c-- >0;)s.isOdd()&&s.iadd(u),s.iushrn(1);for(var l=0,d=1;0==(n.words[0]&d)&&l<26;++l,d<<=1);if(l>0)for(n.iushrn(l);l-- >0;)a.isOdd()&&a.iadd(u),a.iushrn(1);t.cmp(n)>=0?(t.isub(n),s.isub(a)):(n.isub(t),a.isub(s))}return(i=0===t.cmpn(1)?s:a).cmpn(0)<0&&i.iadd(e),i},o.prototype.gcd=function(e){if(this.isZero())return e.abs();if(e.isZero())return this.abs();var t=this.clone(),n=e.clone();t.negative=0,n.negative=0;for(var r=0;t.isEven()&&n.isEven();r++)t.iushrn(1),n.iushrn(1);for(;;){for(;t.isEven();)t.iushrn(1);for(;n.isEven();)n.iushrn(1);var i=t.cmp(n);if(i<0){var o=t;t=n,n=o}else if(0===i||0===n.cmpn(1))break;t.isub(n)}return n.iushln(r)},o.prototype.invm=function(e){return this.egcd(e).a.umod(e)},o.prototype.isEven=function(){return 0==(1&this.words[0])},o.prototype.isOdd=function(){return 1==(1&this.words[0])},o.prototype.andln=function(e){return this.words[0]&e},o.prototype.bincn=function(e){r("number"==typeof e);var t=e%26,n=(e-t)/26,i=1<<t;if(this.length<=n)return this._expand(n+1),this.words[n]|=i,this;for(var o=i,s=n;0!==o&&s<this.length;s++){var a=0|this.words[s];o=(a+=o)>>>26,a&=67108863,this.words[s]=a}return 0!==o&&(this.words[s]=o,this.length++),this},o.prototype.isZero=function(){return 1===this.length&&0===this.words[0]},o.prototype.cmpn=function(e){var t,n=e<0;if(0!==this.negative&&!n)return-1;if(0===this.negative&&n)return 1;if(this.strip(),this.length>1)t=1;else{n&&(e=-e),r(e<=67108863,"Number is too big");var i=0|this.words[0];t=i===e?0:i<e?-1:1}return 0!==this.negative?0|-t:t},o.prototype.cmp=function(e){if(0!==this.negative&&0===e.negative)return-1;if(0===this.negative&&0!==e.negative)return 1;var t=this.ucmp(e);return 0!==this.negative?0|-t:t},o.prototype.ucmp=function(e){if(this.length>e.length)return 1;if(this.length<e.length)return-1;for(var t=0,n=this.length-1;n>=0;n--){var r=0|this.words[n],i=0|e.words[n];if(r!==i){r<i?t=-1:r>i&&(t=1);break}}return t},o.prototype.gtn=function(e){return 1===this.cmpn(e)},o.prototype.gt=function(e){return 1===this.cmp(e)},o.prototype.gten=function(e){return this.cmpn(e)>=0},o.prototype.gte=function(e){return this.cmp(e)>=0},o.prototype.ltn=function(e){return-1===this.cmpn(e)},o.prototype.lt=function(e){return-1===this.cmp(e)},o.prototype.lten=function(e){return this.cmpn(e)<=0},o.prototype.lte=function(e){return this.cmp(e)<=0},o.prototype.eqn=function(e){return 0===this.cmpn(e)},o.prototype.eq=function(e){return 0===this.cmp(e)},o.red=function(e){return new S(e)},o.prototype.toRed=function(e){return r(!this.red,"Already a number in reduction context"),r(0===this.negative,"red works only with positives"),e.convertTo(this)._forceRed(e)},o.prototype.fromRed=function(){return r(this.red,"fromRed works only with numbers in reduction context"),this.red.convertFrom(this)},o.prototype._forceRed=function(e){return this.red=e,this},o.prototype.forceRed=function(e){return r(!this.red,"Already a number in reduction context"),this._forceRed(e)},o.prototype.redAdd=function(e){return r(this.red,"redAdd works only with red numbers"),this.red.add(this,e)},o.prototype.redIAdd=function(e){return r(this.red,"redIAdd works only with red numbers"),this.red.iadd(this,e)},o.prototype.redSub=function(e){return r(this.red,"redSub works only with red numbers"),this.red.sub(this,e)},o.prototype.redISub=function(e){return r(this.red,"redISub works only with red numbers"),this.red.isub(this,e)},o.prototype.redShl=function(e){return r(this.red,"redShl works only with red numbers"),this.red.shl(this,e)},o.prototype.redMul=function(e){return r(this.red,"redMul works only with red numbers"),this.red._verify2(this,e),this.red.mul(this,e)},o.prototype.redIMul=function(e){return r(this.red,"redMul works only with red numbers"),this.red._verify2(this,e),this.red.imul(this,e)},o.prototype.redSqr=function(){return r(this.red,"redSqr works only with red numbers"),this.red._verify1(this),this.red.sqr(this)},o.prototype.redISqr=function(){return r(this.red,"redISqr works only with red numbers"),this.red._verify1(this),this.red.isqr(this)},o.prototype.redSqrt=function(){return r(this.red,"redSqrt works only with red numbers"),this.red._verify1(this),this.red.sqrt(this)},o.prototype.redInvm=function(){return r(this.red,"redInvm works only with red numbers"),this.red._verify1(this),this.red.invm(this)},o.prototype.redNeg=function(){return r(this.red,"redNeg works only with red numbers"),this.red._verify1(this),this.red.neg(this)},o.prototype.redPow=function(e){return r(this.red&&!e.red,"redPow(normalNum)"),this.red._verify1(this),this.red.pow(this,e)};var g={k256:null,p224:null,p192:null,p25519:null};function m(e,t){this.name=e,this.p=new o(t,16),this.n=this.p.bitLength(),this.k=new o(1).iushln(this.n).isub(this.p),this.tmp=this._tmp()}function b(){m.call(this,"k256","ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f")}function y(){m.call(this,"p224","ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001")}function w(){m.call(this,"p192","ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff")}function _(){m.call(this,"25519","7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed")}function S(e){if("string"==typeof e){var t=o._prime(e);this.m=t.p,this.prime=t}else r(e.gtn(1),"modulus must be greater than 1"),this.m=e,this.prime=null}function E(e){S.call(this,e),this.shift=this.m.bitLength(),this.shift%26!=0&&(this.shift+=26-this.shift%26),this.r=new o(1).iushln(this.shift),this.r2=this.imod(this.r.sqr()),this.rinv=this.r._invmp(this.m),this.minv=this.rinv.mul(this.r).isubn(1).div(this.m),this.minv=this.minv.umod(this.r),this.minv=this.r.sub(this.minv)}m.prototype._tmp=function(){var e=new o(null);return e.words=new Array(Math.ceil(this.n/13)),e},m.prototype.ireduce=function(e){var t,n=e;do{this.split(n,this.tmp),t=(n=(n=this.imulK(n)).iadd(this.tmp)).bitLength()}while(t>this.n);var r=t<this.n?-1:n.ucmp(this.p);return 0===r?(n.words[0]=0,n.length=1):r>0?n.isub(this.p):void 0!==n.strip?n.strip():n._strip(),n},m.prototype.split=function(e,t){e.iushrn(this.n,0,t)},m.prototype.imulK=function(e){return e.imul(this.k)},i(b,m),b.prototype.split=function(e,t){for(var n=Math.min(e.length,9),r=0;r<n;r++)t.words[r]=e.words[r];if(t.length=n,e.length<=9)return e.words[0]=0,void(e.length=1);var i=e.words[9];for(t.words[t.length++]=4194303&i,r=10;r<e.length;r++){var o=0|e.words[r];e.words[r-10]=(4194303&o)<<4|i>>>22,i=o}i>>>=22,e.words[r-10]=i,0===i&&e.length>10?e.length-=10:e.length-=9},b.prototype.imulK=function(e){e.words[e.length]=0,e.words[e.length+1]=0,e.length+=2;for(var t=0,n=0;n<e.length;n++){var r=0|e.words[n];t+=977*r,e.words[n]=67108863&t,t=64*r+(t/67108864|0)}return 0===e.words[e.length-1]&&(e.length--,0===e.words[e.length-1]&&e.length--),e},i(y,m),i(w,m),i(_,m),_.prototype.imulK=function(e){for(var t=0,n=0;n<e.length;n++){var r=19*(0|e.words[n])+t,i=67108863&r;r>>>=26,e.words[n]=i,t=r}return 0!==t&&(e.words[e.length++]=t),e},o._prime=function(e){if(g[e])return g[e];var t;if("k256"===e)t=new b;else if("p224"===e)t=new y;else if("p192"===e)t=new w;else{if("p25519"!==e)throw new Error("Unknown prime "+e);t=new _}return g[e]=t,t},S.prototype._verify1=function(e){r(0===e.negative,"red works only with positives"),r(e.red,"red works only with red numbers")},S.prototype._verify2=function(e,t){r(0==(e.negative|t.negative),"red works only with positives"),r(e.red&&e.red===t.red,"red works only with red numbers")},S.prototype.imod=function(e){return this.prime?this.prime.ireduce(e)._forceRed(this):e.umod(this.m)._forceRed(this)},S.prototype.neg=function(e){return e.isZero()?e.clone():this.m.sub(e)._forceRed(this)},S.prototype.add=function(e,t){this._verify2(e,t);var n=e.add(t);return n.cmp(this.m)>=0&&n.isub(this.m),n._forceRed(this)},S.prototype.iadd=function(e,t){this._verify2(e,t);var n=e.iadd(t);return n.cmp(this.m)>=0&&n.isub(this.m),n},S.prototype.sub=function(e,t){this._verify2(e,t);var n=e.sub(t);return n.cmpn(0)<0&&n.iadd(this.m),n._forceRed(this)},S.prototype.isub=function(e,t){this._verify2(e,t);var n=e.isub(t);return n.cmpn(0)<0&&n.iadd(this.m),n},S.prototype.shl=function(e,t){return this._verify1(e),this.imod(e.ushln(t))},S.prototype.imul=function(e,t){return this._verify2(e,t),this.imod(e.imul(t))},S.prototype.mul=function(e,t){return this._verify2(e,t),this.imod(e.mul(t))},S.prototype.isqr=function(e){return this.imul(e,e.clone())},S.prototype.sqr=function(e){return this.mul(e,e)},S.prototype.sqrt=function(e){if(e.isZero())return e.clone();var t=this.m.andln(3);if(r(t%2==1),3===t){var n=this.m.add(new o(1)).iushrn(2);return this.pow(e,n)}for(var i=this.m.subn(1),s=0;!i.isZero()&&0===i.andln(1);)s++,i.iushrn(1);r(!i.isZero());var a=new o(1).toRed(this),u=a.redNeg(),c=this.m.subn(1).iushrn(1),f=this.m.bitLength();for(f=new o(2*f*f).toRed(this);0!==this.pow(f,c).cmp(u);)f.redIAdd(u);for(var l=this.pow(f,i),d=this.pow(e,i.addn(1).iushrn(1)),h=this.pow(e,i),p=s;0!==h.cmp(a);){for(var v=h,g=0;0!==v.cmp(a);g++)v=v.redSqr();r(g<p);var m=this.pow(l,new o(1).iushln(p-g-1));d=d.redMul(m),l=m.redSqr(),h=h.redMul(l),p=g}return d},S.prototype.invm=function(e){var t=e._invmp(this.m);return 0!==t.negative?(t.negative=0,this.imod(t).redNeg()):this.imod(t)},S.prototype.pow=function(e,t){if(t.isZero())return new o(1).toRed(this);if(0===t.cmpn(1))return e.clone();var n=new Array(16);n[0]=new o(1).toRed(this),n[1]=e;for(var r=2;r<n.length;r++)n[r]=this.mul(n[r-1],e);var i=n[0],s=0,a=0,u=t.bitLength()%26;for(0===u&&(u=26),r=t.length-1;r>=0;r--){for(var c=t.words[r],f=u-1;f>=0;f--){var l=c>>f&1;i!==n[0]&&(i=this.sqr(i)),0!==l||0!==s?(s<<=1,s|=l,(4===++a||0===r&&0===f)&&(i=this.mul(i,n[s]),a=0,s=0)):a=0}u=26}return i},S.prototype.convertTo=function(e){var t=e.umod(this.m);return t===e?t.clone():t},S.prototype.convertFrom=function(e){var t=e.clone();return t.red=null,t},o.mont=function(e){return new E(e)},i(E,S),E.prototype.convertTo=function(e){return this.imod(e.ushln(this.shift))},E.prototype.convertFrom=function(e){var t=this.imod(e.mul(this.rinv));return t.red=null,t},E.prototype.imul=function(e,t){if(e.isZero()||t.isZero())return e.words[0]=0,e.length=1,e;var n=e.imul(t),r=n.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=n.isub(r).iushrn(this.shift),o=i;return i.cmp(this.m)>=0?o=i.isub(this.m):i.cmpn(0)<0&&(o=i.iadd(this.m)),o._forceRed(this)},E.prototype.mul=function(e,t){if(e.isZero()||t.isZero())return new o(0)._forceRed(this);var n=e.mul(t),r=n.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=n.isub(r).iushrn(this.shift),s=i;return i.cmp(this.m)>=0?s=i.isub(this.m):i.cmpn(0)<0&&(s=i.iadd(this.m)),s._forceRed(this)},E.prototype.invm=function(e){return this.imod(e._invmp(this.m).mul(this.r2))._forceRed(this)}}(e,this)}).call(this,n(57)(e))},function(e,t,n){"use strict";n.d(t,"a",(function(){return r})),n.d(t,"b",(function(){return C})),n.d(t,"c",(function(){return P})),n.d(t,"d",(function(){return N})),n.d(t,"e",(function(){return V})),n.d(t,"f",(function(){return F})),n.d(t,"g",(function(){return te})),n.d(t,"h",(function(){return j})),n.d(t,"i",(function(){return re})); -/*! - * Copyright 2016 Amazon.com, - * Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Amazon Software License (the "License"). - * You may not use this file except in compliance with the - * License. A copy of the License is located at - * - * http://aws.amazon.com/asl/ - * - * or in the "license" file accompanying this file. This file is - * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, express or implied. See the License - * for the specific language governing permissions and - * limitations under the License. - */ -var r=function(){function e(e){var t=e||{},n=t.ValidationData,r=t.Username,i=t.Password,o=t.AuthParameters,s=t.ClientMetadata;this.validationData=n||{},this.authParameters=o||{},this.clientMetadata=s||{},this.username=r,this.password=i}var t=e.prototype;return t.getUsername=function(){return this.username},t.getPassword=function(){return this.password},t.getValidationData=function(){return this.validationData},t.getAuthParameters=function(){return this.authParameters},t.getClientMetadata=function(){return this.clientMetadata},e}(),i=n(6),o=n(32),s=n.n(o),a=(n(161),n(87)),u=n.n(a),c=n(75),f=n.n(c),l=n(250);var d,h=function(){function e(e,t){e=this.words=e||[],this.sigBytes=null!=t?t:4*e.length}var t=e.prototype;return t.random=function(t){for(var n=[],r=0;r<t;r+=4)n.push(Object(l.a)());return new e(n,t)},t.toString=function(){return function(e){for(var t=e.words,n=e.sigBytes,r=[],i=0;i<n;i++){var o=t[i>>>2]>>>24-i%4*8&255;r.push((o>>>4).toString(16)),r.push((15&o).toString(16))}return r.join("")}(this)},e}(),p=v;function v(e,t){null!=e&&this.fromString(e,t)}function g(){return new v(null)}var m="undefined"!=typeof navigator;m&&"Microsoft Internet Explorer"==navigator.appName?(v.prototype.am=function(e,t,n,r,i,o){for(var s=32767&t,a=t>>15;--o>=0;){var u=32767&this[e],c=this[e++]>>15,f=a*u+c*s;i=((u=s*u+((32767&f)<<15)+n[r]+(1073741823&i))>>>30)+(f>>>15)+a*c+(i>>>30),n[r++]=1073741823&u}return i},d=30):m&&"Netscape"!=navigator.appName?(v.prototype.am=function(e,t,n,r,i,o){for(;--o>=0;){var s=t*this[e++]+n[r]+i;i=Math.floor(s/67108864),n[r++]=67108863&s}return i},d=26):(v.prototype.am=function(e,t,n,r,i,o){for(var s=16383&t,a=t>>14;--o>=0;){var u=16383&this[e],c=this[e++]>>14,f=a*u+c*s;i=((u=s*u+((16383&f)<<14)+n[r]+i)>>28)+(f>>14)+a*c,n[r++]=268435455&u}return i},d=28),v.prototype.DB=d,v.prototype.DM=(1<<d)-1,v.prototype.DV=1<<d;v.prototype.FV=Math.pow(2,52),v.prototype.F1=52-d,v.prototype.F2=2*d-52;var b,y,w=new Array;for(b="0".charCodeAt(0),y=0;y<=9;++y)w[b++]=y;for(b="a".charCodeAt(0),y=10;y<36;++y)w[b++]=y;for(b="A".charCodeAt(0),y=10;y<36;++y)w[b++]=y;function _(e){return"0123456789abcdefghijklmnopqrstuvwxyz".charAt(e)}function S(e,t){var n=w[e.charCodeAt(t)];return null==n?-1:n}function E(e){var t=g();return t.fromInt(e),t}function M(e){var t,n=1;return 0!=(t=e>>>16)&&(e=t,n+=16),0!=(t=e>>8)&&(e=t,n+=8),0!=(t=e>>4)&&(e=t,n+=4),0!=(t=e>>2)&&(e=t,n+=2),0!=(t=e>>1)&&(e=t,n+=1),n}function A(e){this.m=e,this.mp=e.invDigit(),this.mpl=32767&this.mp,this.mph=this.mp>>15,this.um=(1<<e.DB-15)-1,this.mt2=2*e.t}A.prototype.convert=function(e){var t=g();return e.abs().dlShiftTo(this.m.t,t),t.divRemTo(this.m,null,t),e.s<0&&t.compareTo(v.ZERO)>0&&this.m.subTo(t,t),t},A.prototype.revert=function(e){var t=g();return e.copyTo(t),this.reduce(t),t},A.prototype.reduce=function(e){for(;e.t<=this.mt2;)e[e.t++]=0;for(var t=0;t<this.m.t;++t){var n=32767&e[t],r=n*this.mpl+((n*this.mph+(e[t]>>15)*this.mpl&this.um)<<15)&e.DM;for(e[n=t+this.m.t]+=this.m.am(0,r,e,t,0,this.m.t);e[n]>=e.DV;)e[n]-=e.DV,e[++n]++}e.clamp(),e.drShiftTo(this.m.t,e),e.compareTo(this.m)>=0&&e.subTo(this.m,e)},A.prototype.mulTo=function(e,t,n){e.multiplyTo(t,n),this.reduce(n)},A.prototype.sqrTo=function(e,t){e.squareTo(t),this.reduce(t)},v.prototype.copyTo=function(e){for(var t=this.t-1;t>=0;--t)e[t]=this[t];e.t=this.t,e.s=this.s},v.prototype.fromInt=function(e){this.t=1,this.s=e<0?-1:0,e>0?this[0]=e:e<-1?this[0]=e+this.DV:this.t=0},v.prototype.fromString=function(e,t){var n;if(16==t)n=4;else if(8==t)n=3;else if(2==t)n=1;else if(32==t)n=5;else{if(4!=t)throw new Error("Only radix 2, 4, 8, 16, 32 are supported");n=2}this.t=0,this.s=0;for(var r=e.length,i=!1,o=0;--r>=0;){var s=S(e,r);s<0?"-"==e.charAt(r)&&(i=!0):(i=!1,0==o?this[this.t++]=s:o+n>this.DB?(this[this.t-1]|=(s&(1<<this.DB-o)-1)<<o,this[this.t++]=s>>this.DB-o):this[this.t-1]|=s<<o,(o+=n)>=this.DB&&(o-=this.DB))}this.clamp(),i&&v.ZERO.subTo(this,this)},v.prototype.clamp=function(){for(var e=this.s&this.DM;this.t>0&&this[this.t-1]==e;)--this.t},v.prototype.dlShiftTo=function(e,t){var n;for(n=this.t-1;n>=0;--n)t[n+e]=this[n];for(n=e-1;n>=0;--n)t[n]=0;t.t=this.t+e,t.s=this.s},v.prototype.drShiftTo=function(e,t){for(var n=e;n<this.t;++n)t[n-e]=this[n];t.t=Math.max(this.t-e,0),t.s=this.s},v.prototype.lShiftTo=function(e,t){var n,r=e%this.DB,i=this.DB-r,o=(1<<i)-1,s=Math.floor(e/this.DB),a=this.s<<r&this.DM;for(n=this.t-1;n>=0;--n)t[n+s+1]=this[n]>>i|a,a=(this[n]&o)<<r;for(n=s-1;n>=0;--n)t[n]=0;t[s]=a,t.t=this.t+s+1,t.s=this.s,t.clamp()},v.prototype.rShiftTo=function(e,t){t.s=this.s;var n=Math.floor(e/this.DB);if(n>=this.t)t.t=0;else{var r=e%this.DB,i=this.DB-r,o=(1<<r)-1;t[0]=this[n]>>r;for(var s=n+1;s<this.t;++s)t[s-n-1]|=(this[s]&o)<<i,t[s-n]=this[s]>>r;r>0&&(t[this.t-n-1]|=(this.s&o)<<i),t.t=this.t-n,t.clamp()}},v.prototype.subTo=function(e,t){for(var n=0,r=0,i=Math.min(e.t,this.t);n<i;)r+=this[n]-e[n],t[n++]=r&this.DM,r>>=this.DB;if(e.t<this.t){for(r-=e.s;n<this.t;)r+=this[n],t[n++]=r&this.DM,r>>=this.DB;r+=this.s}else{for(r+=this.s;n<e.t;)r-=e[n],t[n++]=r&this.DM,r>>=this.DB;r-=e.s}t.s=r<0?-1:0,r<-1?t[n++]=this.DV+r:r>0&&(t[n++]=r),t.t=n,t.clamp()},v.prototype.multiplyTo=function(e,t){var n=this.abs(),r=e.abs(),i=n.t;for(t.t=i+r.t;--i>=0;)t[i]=0;for(i=0;i<r.t;++i)t[i+n.t]=n.am(0,r[i],t,i,0,n.t);t.s=0,t.clamp(),this.s!=e.s&&v.ZERO.subTo(t,t)},v.prototype.squareTo=function(e){for(var t=this.abs(),n=e.t=2*t.t;--n>=0;)e[n]=0;for(n=0;n<t.t-1;++n){var r=t.am(n,t[n],e,2*n,0,1);(e[n+t.t]+=t.am(n+1,2*t[n],e,2*n+1,r,t.t-n-1))>=t.DV&&(e[n+t.t]-=t.DV,e[n+t.t+1]=1)}e.t>0&&(e[e.t-1]+=t.am(n,t[n],e,2*n,0,1)),e.s=0,e.clamp()},v.prototype.divRemTo=function(e,t,n){var r=e.abs();if(!(r.t<=0)){var i=this.abs();if(i.t<r.t)return null!=t&&t.fromInt(0),void(null!=n&&this.copyTo(n));null==n&&(n=g());var o=g(),s=this.s,a=e.s,u=this.DB-M(r[r.t-1]);u>0?(r.lShiftTo(u,o),i.lShiftTo(u,n)):(r.copyTo(o),i.copyTo(n));var c=o.t,f=o[c-1];if(0!=f){var l=f*(1<<this.F1)+(c>1?o[c-2]>>this.F2:0),d=this.FV/l,h=(1<<this.F1)/l,p=1<<this.F2,m=n.t,b=m-c,y=null==t?g():t;for(o.dlShiftTo(b,y),n.compareTo(y)>=0&&(n[n.t++]=1,n.subTo(y,n)),v.ONE.dlShiftTo(c,y),y.subTo(o,o);o.t<c;)o[o.t++]=0;for(;--b>=0;){var w=n[--m]==f?this.DM:Math.floor(n[m]*d+(n[m-1]+p)*h);if((n[m]+=o.am(0,w,n,b,0,c))<w)for(o.dlShiftTo(b,y),n.subTo(y,n);n[m]<--w;)n.subTo(y,n)}null!=t&&(n.drShiftTo(c,t),s!=a&&v.ZERO.subTo(t,t)),n.t=c,n.clamp(),u>0&&n.rShiftTo(u,n),s<0&&v.ZERO.subTo(n,n)}}},v.prototype.invDigit=function(){if(this.t<1)return 0;var e=this[0];if(0==(1&e))return 0;var t=3&e;return(t=(t=(t=(t=t*(2-(15&e)*t)&15)*(2-(255&e)*t)&255)*(2-((65535&e)*t&65535))&65535)*(2-e*t%this.DV)%this.DV)>0?this.DV-t:-t},v.prototype.addTo=function(e,t){for(var n=0,r=0,i=Math.min(e.t,this.t);n<i;)r+=this[n]+e[n],t[n++]=r&this.DM,r>>=this.DB;if(e.t<this.t){for(r+=e.s;n<this.t;)r+=this[n],t[n++]=r&this.DM,r>>=this.DB;r+=this.s}else{for(r+=this.s;n<e.t;)r+=e[n],t[n++]=r&this.DM,r>>=this.DB;r+=e.s}t.s=r<0?-1:0,r>0?t[n++]=r:r<-1&&(t[n++]=this.DV+r),t.t=n,t.clamp()},v.prototype.toString=function(e){if(this.s<0)return"-"+this.negate().toString(e);var t;if(16==e)t=4;else if(8==e)t=3;else if(2==e)t=1;else if(32==e)t=5;else{if(4!=e)throw new Error("Only radix 2, 4, 8, 16, 32 are supported");t=2}var n,r=(1<<t)-1,i=!1,o="",s=this.t,a=this.DB-s*this.DB%t;if(s-- >0)for(a<this.DB&&(n=this[s]>>a)>0&&(i=!0,o=_(n));s>=0;)a<t?(n=(this[s]&(1<<a)-1)<<t-a,n|=this[--s]>>(a+=this.DB-t)):(n=this[s]>>(a-=t)&r,a<=0&&(a+=this.DB,--s)),n>0&&(i=!0),i&&(o+=_(n));return i?o:"0"},v.prototype.negate=function(){var e=g();return v.ZERO.subTo(this,e),e},v.prototype.abs=function(){return this.s<0?this.negate():this},v.prototype.compareTo=function(e){var t=this.s-e.s;if(0!=t)return t;var n=this.t;if(0!=(t=n-e.t))return this.s<0?-t:t;for(;--n>=0;)if(0!=(t=this[n]-e[n]))return t;return 0},v.prototype.bitLength=function(){return this.t<=0?0:this.DB*(this.t-1)+M(this[this.t-1]^this.s&this.DM)},v.prototype.mod=function(e){var t=g();return this.abs().divRemTo(e,null,t),this.s<0&&t.compareTo(v.ZERO)>0&&e.subTo(t,t),t},v.prototype.equals=function(e){return 0==this.compareTo(e)},v.prototype.add=function(e){var t=g();return this.addTo(e,t),t},v.prototype.subtract=function(e){var t=g();return this.subTo(e,t),t},v.prototype.multiply=function(e){var t=g();return this.multiplyTo(e,t),t},v.prototype.divide=function(e){var t=g();return this.divRemTo(e,t,null),t},v.prototype.modPow=function(e,t,n){var r,i=e.bitLength(),o=E(1),s=new A(t);if(i<=0)return o;r=i<18?1:i<48?3:i<144?4:i<768?5:6;var a=new Array,u=3,c=r-1,f=(1<<r)-1;if(a[1]=s.convert(this),r>1){var l=g();for(s.sqrTo(a[1],l);u<=f;)a[u]=g(),s.mulTo(l,a[u-2],a[u]),u+=2}var d,h,p=e.t-1,v=!0,m=g();for(i=M(e[p])-1;p>=0;){for(i>=c?d=e[p]>>i-c&f:(d=(e[p]&(1<<i+1)-1)<<c-i,p>0&&(d|=e[p-1]>>this.DB+i-c)),u=r;0==(1&d);)d>>=1,--u;if((i-=u)<0&&(i+=this.DB,--p),v)a[d].copyTo(o),v=!1;else{for(;u>1;)s.sqrTo(o,m),s.sqrTo(m,o),u-=2;u>0?s.sqrTo(o,m):(h=o,o=m,m=h),s.mulTo(m,a[d],o)}for(;p>=0&&0==(e[p]&1<<i);)s.sqrTo(o,m),h=o,o=m,m=h,--i<0&&(i=this.DB-1,--p)}var b=s.revert(o);return n(null,b),b},v.ZERO=E(0),v.ONE=E(1); -/*! - * Copyright 2016 Amazon.com, - * Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Amazon Software License (the "License"). - * You may not use this file except in compliance with the - * License. A copy of the License is located at - * - * http://aws.amazon.com/asl/ - * - * or in the "license" file accompanying this file. This file is - * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, express or implied. See the License - * for the specific language governing permissions and - * limitations under the License. - */ -var I=function(e){return i.Buffer.from((new h).random(e).toString(),"hex")},k=function(){function e(e){this.N=new p("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF",16),this.g=new p("2",16),this.k=new p(this.hexHash("00"+this.N.toString(16)+"0"+this.g.toString(16)),16),this.smallAValue=this.generateRandomSmallA(),this.getLargeAValue((function(){})),this.infoBits=i.Buffer.from("Caldera Derived Key","utf8"),this.poolName=e}var t=e.prototype;return t.getSmallAValue=function(){return this.smallAValue},t.getLargeAValue=function(e){var t=this;this.largeAValue?e(null,this.largeAValue):this.calculateA(this.smallAValue,(function(n,r){n&&e(n,null),t.largeAValue=r,e(null,t.largeAValue)}))},t.generateRandomSmallA=function(){var e=I(128).toString("hex");return new p(e,16).mod(this.N)},t.generateRandomString=function(){return I(40).toString("base64")},t.getRandomPassword=function(){return this.randomPassword},t.getSaltDevices=function(){return this.SaltToHashDevices},t.getVerifierDevices=function(){return this.verifierDevices},t.generateHashDevice=function(e,t,n){var r=this;this.randomPassword=this.generateRandomString();var i=""+e+t+":"+this.randomPassword,o=this.hash(i),s=I(16).toString("hex");this.SaltToHashDevices=this.padHex(new p(s,16)),this.g.modPow(new p(this.hexHash(this.SaltToHashDevices+o),16),this.N,(function(e,t){e&&n(e,null),r.verifierDevices=r.padHex(t),n(null,null)}))},t.calculateA=function(e,t){var n=this;this.g.modPow(e,this.N,(function(e,r){e&&t(e,null),r.mod(n.N).equals(p.ZERO)&&t(new Error("Illegal paramater. A mod N cannot be 0."),null),t(null,r)}))},t.calculateU=function(e,t){return this.UHexHash=this.hexHash(this.padHex(e)+this.padHex(t)),new p(this.UHexHash,16)},t.hash=function(e){var t=e instanceof i.Buffer?s.a.lib.WordArray.create(e):e,n=u()(t).toString();return new Array(64-n.length).join("0")+n},t.hexHash=function(e){return this.hash(i.Buffer.from(e,"hex"))},t.computehkdf=function(e,t){var n=s.a.lib.WordArray.create(i.Buffer.concat([this.infoBits,i.Buffer.from(String.fromCharCode(1),"utf8")])),r=e instanceof i.Buffer?s.a.lib.WordArray.create(e):e,o=t instanceof i.Buffer?s.a.lib.WordArray.create(t):t,a=f()(r,o),u=f()(n,a);return i.Buffer.from(u.toString(),"hex").slice(0,16)},t.getPasswordAuthenticationKey=function(e,t,n,r,o){var s=this;if(n.mod(this.N).equals(p.ZERO))throw new Error("B cannot be zero.");if(this.UValue=this.calculateU(this.largeAValue,n),this.UValue.equals(p.ZERO))throw new Error("U cannot be zero.");var a=""+this.poolName+e+":"+t,u=this.hash(a),c=new p(this.hexHash(this.padHex(r)+u),16);this.calculateS(c,n,(function(e,t){e&&o(e,null);var n=s.computehkdf(i.Buffer.from(s.padHex(t),"hex"),i.Buffer.from(s.padHex(s.UValue.toString(16)),"hex"));o(null,n)}))},t.calculateS=function(e,t,n){var r=this;this.g.modPow(e,this.N,(function(i,o){i&&n(i,null),t.subtract(r.k.multiply(o)).modPow(r.smallAValue.add(r.UValue.multiply(e)),r.N,(function(e,t){e&&n(e,null),n(null,t.mod(r.N))}))}))},t.getNewPasswordRequiredChallengeUserAttributePrefix=function(){return"userAttributes."},t.padHex=function(e){var t=e.toString(16);return t.length%2==1?t="0"+t:-1!=="89ABCDEFabcdef".indexOf(t[0])&&(t="00"+t),t},e}(),O=function(){function e(e){this.jwtToken=e||"",this.payload=this.decodePayload()}var t=e.prototype;return t.getJwtToken=function(){return this.jwtToken},t.getExpiration=function(){return this.payload.exp},t.getIssuedAt=function(){return this.payload.iat},t.decodePayload=function(){var e=this.jwtToken.split(".")[1];try{return JSON.parse(i.Buffer.from(e,"base64").toString("utf8"))}catch(e){return{}}},e}();function x(e,t){return(x=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var C=function(e){var t,n;function r(t){var n=(void 0===t?{}:t).AccessToken;return e.call(this,n||"")||this}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,x(t,n),r}(O);function T(e,t){return(T=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)} -/*! - * Copyright 2016 Amazon.com, - * Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Amazon Software License (the "License"). - * You may not use this file except in compliance with the - * License. A copy of the License is located at - * - * http://aws.amazon.com/asl/ - * - * or in the "license" file accompanying this file. This file is - * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, express or implied. See the License - * for the specific language governing permissions and - * limitations under the License. - */var P=function(e){var t,n;function r(t){var n=(void 0===t?{}:t).IdToken;return e.call(this,n||"")||this}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,T(t,n),r}(O),N=function(){function e(e){var t=(void 0===e?{}:e).RefreshToken;this.token=t||""}return e.prototype.getToken=function(){return this.token},e}(),R=n(90),L=n.n(R),j=function(){function e(e){var t=void 0===e?{}:e,n=t.IdToken,r=t.RefreshToken,i=t.AccessToken,o=t.ClockDrift;if(null==i||null==n)throw new Error("Id token and Access Token must be present.");this.idToken=n,this.refreshToken=r,this.accessToken=i,this.clockDrift=void 0===o?this.calculateClockDrift():o}var t=e.prototype;return t.getIdToken=function(){return this.idToken},t.getRefreshToken=function(){return this.refreshToken},t.getAccessToken=function(){return this.accessToken},t.getClockDrift=function(){return this.clockDrift},t.calculateClockDrift=function(){return Math.floor(new Date/1e3)-Math.min(this.accessToken.getIssuedAt(),this.idToken.getIssuedAt())},t.isValid=function(){var e=Math.floor(new Date/1e3)-this.clockDrift;return e<this.accessToken.getExpiration()&&e<this.idToken.getExpiration()},e}(),D=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],U=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],B=function(){function e(){}return e.prototype.getNowString=function(){var e=new Date,t=U[e.getUTCDay()],n=D[e.getUTCMonth()],r=e.getUTCDate(),i=e.getUTCHours();i<10&&(i="0"+i);var o=e.getUTCMinutes();o<10&&(o="0"+o);var s=e.getUTCSeconds();return s<10&&(s="0"+s),t+" "+n+" "+r+" "+i+":"+o+":"+s+" UTC "+e.getUTCFullYear()},e}(),F=function(){function e(e){var t=void 0===e?{}:e,n=t.Name,r=t.Value;this.Name=n||"",this.Value=r||""}var t=e.prototype;return t.getValue=function(){return this.Value},t.setValue=function(e){return this.Value=e,this},t.getName=function(){return this.Name},t.setName=function(e){return this.Name=e,this},t.toString=function(){return JSON.stringify(this)},t.toJSON=function(){return{Name:this.Name,Value:this.Value}},e}(),z={},q=function(){function e(){}return e.setItem=function(e,t){return z[e]=t,z[e]},e.getItem=function(e){return Object.prototype.hasOwnProperty.call(z,e)?z[e]:void 0},e.removeItem=function(e){return delete z[e]},e.clear=function(){return z={}},e}(),K=function(){function e(){try{this.storageWindow=window.localStorage,this.storageWindow.setItem("aws.cognito.test-ls",1),this.storageWindow.removeItem("aws.cognito.test-ls")}catch(e){this.storageWindow=q}}return e.prototype.getStorage=function(){return this.storageWindow},e}(),H="undefined"!=typeof navigator?navigator.userAgent:"nodejs",V=function(){function e(e){if(null==e||null==e.Username||null==e.Pool)throw new Error("Username and Pool information are required.");this.username=e.Username||"",this.pool=e.Pool,this.Session=null,this.client=e.Pool.client,this.signInUserSession=null,this.authenticationFlowType="USER_SRP_AUTH",this.storage=e.Storage||(new K).getStorage(),this.keyPrefix="CognitoIdentityServiceProvider."+this.pool.getClientId(),this.userDataKey=this.keyPrefix+"."+this.username+".userData"}var t=e.prototype;return t.setSignInUserSession=function(e){this.clearCachedUserData(),this.signInUserSession=e,this.cacheTokens()},t.getSignInUserSession=function(){return this.signInUserSession},t.getUsername=function(){return this.username},t.getAuthenticationFlowType=function(){return this.authenticationFlowType},t.setAuthenticationFlowType=function(e){this.authenticationFlowType=e},t.initiateAuth=function(e,t){var n=this,r=e.getAuthParameters();r.USERNAME=this.username;var i=0!==Object.keys(e.getValidationData()).length?e.getValidationData():e.getClientMetadata(),o={AuthFlow:"CUSTOM_AUTH",ClientId:this.pool.getClientId(),AuthParameters:r,ClientMetadata:i};this.getUserContextData()&&(o.UserContextData=this.getUserContextData()),this.client.request("InitiateAuth",o,(function(e,r){if(e)return t.onFailure(e);var i=r.ChallengeName,o=r.ChallengeParameters;return"CUSTOM_CHALLENGE"===i?(n.Session=r.Session,t.customChallenge(o)):(n.signInUserSession=n.getCognitoUserSession(r.AuthenticationResult),n.cacheTokens(),t.onSuccess(n.signInUserSession))}))},t.authenticateUser=function(e,t){return"USER_PASSWORD_AUTH"===this.authenticationFlowType?this.authenticateUserPlainUsernamePassword(e,t):"USER_SRP_AUTH"===this.authenticationFlowType||"CUSTOM_AUTH"===this.authenticationFlowType?this.authenticateUserDefaultAuth(e,t):t.onFailure(new Error("Authentication flow type is invalid."))},t.authenticateUserDefaultAuth=function(e,t){var n,r,o=this,a=new k(this.pool.getUserPoolId().split("_")[1]),u=new B,c={};null!=this.deviceKey&&(c.DEVICE_KEY=this.deviceKey),c.USERNAME=this.username,a.getLargeAValue((function(l,d){l&&t.onFailure(l),c.SRP_A=d.toString(16),"CUSTOM_AUTH"===o.authenticationFlowType&&(c.CHALLENGE_NAME="SRP_A");var h=0!==Object.keys(e.getValidationData()).length?e.getValidationData():e.getClientMetadata(),v={AuthFlow:o.authenticationFlowType,ClientId:o.pool.getClientId(),AuthParameters:c,ClientMetadata:h};o.getUserContextData(o.username)&&(v.UserContextData=o.getUserContextData(o.username)),o.client.request("InitiateAuth",v,(function(c,l){if(c)return t.onFailure(c);var d=l.ChallengeParameters;o.username=d.USER_ID_FOR_SRP,n=new p(d.SRP_B,16),r=new p(d.SALT,16),o.getCachedDeviceKeyAndPassword(),a.getPasswordAuthenticationKey(o.username,e.getPassword(),n,r,(function(e,n){e&&t.onFailure(e);var r=u.getNowString(),c=s.a.lib.WordArray.create(i.Buffer.concat([i.Buffer.from(o.pool.getUserPoolId().split("_")[1],"utf8"),i.Buffer.from(o.username,"utf8"),i.Buffer.from(d.SECRET_BLOCK,"base64"),i.Buffer.from(r,"utf8")])),p=s.a.lib.WordArray.create(n),v=L.a.stringify(f()(c,p)),g={};g.USERNAME=o.username,g.PASSWORD_CLAIM_SECRET_BLOCK=d.SECRET_BLOCK,g.TIMESTAMP=r,g.PASSWORD_CLAIM_SIGNATURE=v,null!=o.deviceKey&&(g.DEVICE_KEY=o.deviceKey);var m={ChallengeName:"PASSWORD_VERIFIER",ClientId:o.pool.getClientId(),ChallengeResponses:g,Session:l.Session,ClientMetadata:h};o.getUserContextData()&&(m.UserContextData=o.getUserContextData()),function e(t,n){return o.client.request("RespondToAuthChallenge",t,(function(r,i){return r&&"ResourceNotFoundException"===r.code&&-1!==r.message.toLowerCase().indexOf("device")?(g.DEVICE_KEY=null,o.deviceKey=null,o.randomPassword=null,o.deviceGroupKey=null,o.clearCachedDeviceKeyAndPassword(),e(t,n)):n(r,i)}))}(m,(function(e,n){return e?t.onFailure(e):o.authenticateUserInternal(n,a,t)}))}))}))}))},t.authenticateUserPlainUsernamePassword=function(e,t){var n=this,r={};if(r.USERNAME=this.username,r.PASSWORD=e.getPassword(),r.PASSWORD){var i=new k(this.pool.getUserPoolId().split("_")[1]);this.getCachedDeviceKeyAndPassword(),null!=this.deviceKey&&(r.DEVICE_KEY=this.deviceKey);var o=0!==Object.keys(e.getValidationData()).length?e.getValidationData():e.getClientMetadata(),s={AuthFlow:"USER_PASSWORD_AUTH",ClientId:this.pool.getClientId(),AuthParameters:r,ClientMetadata:o};this.getUserContextData(this.username)&&(s.UserContextData=this.getUserContextData(this.username)),this.client.request("InitiateAuth",s,(function(e,r){return e?t.onFailure(e):n.authenticateUserInternal(r,i,t)}))}else t.onFailure(new Error("PASSWORD parameter is required"))},t.authenticateUserInternal=function(e,t,n){var r=this,o=e.ChallengeName,s=e.ChallengeParameters;if("SMS_MFA"===o)return this.Session=e.Session,n.mfaRequired(o,s);if("SELECT_MFA_TYPE"===o)return this.Session=e.Session,n.selectMFAType(o,s);if("MFA_SETUP"===o)return this.Session=e.Session,n.mfaSetup(o,s);if("SOFTWARE_TOKEN_MFA"===o)return this.Session=e.Session,n.totpRequired(o,s);if("CUSTOM_CHALLENGE"===o)return this.Session=e.Session,n.customChallenge(s);if("NEW_PASSWORD_REQUIRED"===o){this.Session=e.Session;var a=null,u=null,c=[],f=t.getNewPasswordRequiredChallengeUserAttributePrefix();if(s&&(a=JSON.parse(e.ChallengeParameters.userAttributes),u=JSON.parse(e.ChallengeParameters.requiredAttributes)),u)for(var l=0;l<u.length;l++)c[l]=u[l].substr(f.length);return n.newPasswordRequired(a,c)}if("DEVICE_SRP_AUTH"!==o){this.signInUserSession=this.getCognitoUserSession(e.AuthenticationResult),this.challengeName=o,this.cacheTokens();var d=e.AuthenticationResult.NewDeviceMetadata;if(null==d)return n.onSuccess(this.signInUserSession);t.generateHashDevice(e.AuthenticationResult.NewDeviceMetadata.DeviceGroupKey,e.AuthenticationResult.NewDeviceMetadata.DeviceKey,(function(o){if(o)return n.onFailure(o);var s={Salt:i.Buffer.from(t.getSaltDevices(),"hex").toString("base64"),PasswordVerifier:i.Buffer.from(t.getVerifierDevices(),"hex").toString("base64")};r.verifierDevices=s.PasswordVerifier,r.deviceGroupKey=d.DeviceGroupKey,r.randomPassword=t.getRandomPassword(),r.client.request("ConfirmDevice",{DeviceKey:d.DeviceKey,AccessToken:r.signInUserSession.getAccessToken().getJwtToken(),DeviceSecretVerifierConfig:s,DeviceName:H},(function(t,i){return t?n.onFailure(t):(r.deviceKey=e.AuthenticationResult.NewDeviceMetadata.DeviceKey,r.cacheDeviceKeyAndPassword(),!0===i.UserConfirmationNecessary?n.onSuccess(r.signInUserSession,i.UserConfirmationNecessary):n.onSuccess(r.signInUserSession))}))}))}else this.getDeviceResponse(n)},t.completeNewPasswordChallenge=function(e,t,n,r){var i=this;if(!e)return n.onFailure(new Error("New password is required."));var o=new k(this.pool.getUserPoolId().split("_")[1]),s=o.getNewPasswordRequiredChallengeUserAttributePrefix(),a={};t&&Object.keys(t).forEach((function(e){a[s+e]=t[e]})),a.NEW_PASSWORD=e,a.USERNAME=this.username;var u={ChallengeName:"NEW_PASSWORD_REQUIRED",ClientId:this.pool.getClientId(),ChallengeResponses:a,Session:this.Session,ClientMetadata:r};this.getUserContextData()&&(u.UserContextData=this.getUserContextData()),this.client.request("RespondToAuthChallenge",u,(function(e,t){return e?n.onFailure(e):i.authenticateUserInternal(t,o,n)}))},t.getDeviceResponse=function(e,t){var n=this,r=new k(this.deviceGroupKey),o=new B,a={};a.USERNAME=this.username,a.DEVICE_KEY=this.deviceKey,r.getLargeAValue((function(u,c){u&&e.onFailure(u),a.SRP_A=c.toString(16);var l={ChallengeName:"DEVICE_SRP_AUTH",ClientId:n.pool.getClientId(),ChallengeResponses:a,ClientMetadata:t};n.getUserContextData()&&(l.UserContextData=n.getUserContextData()),n.client.request("RespondToAuthChallenge",l,(function(t,a){if(t)return e.onFailure(t);var u=a.ChallengeParameters,c=new p(u.SRP_B,16),l=new p(u.SALT,16);r.getPasswordAuthenticationKey(n.deviceKey,n.randomPassword,c,l,(function(t,r){if(t)return e.onFailure(t);var c=o.getNowString(),l=s.a.lib.WordArray.create(i.Buffer.concat([i.Buffer.from(n.deviceGroupKey,"utf8"),i.Buffer.from(n.deviceKey,"utf8"),i.Buffer.from(u.SECRET_BLOCK,"base64"),i.Buffer.from(c,"utf8")])),d=s.a.lib.WordArray.create(r),h=L.a.stringify(f()(l,d)),p={};p.USERNAME=n.username,p.PASSWORD_CLAIM_SECRET_BLOCK=u.SECRET_BLOCK,p.TIMESTAMP=c,p.PASSWORD_CLAIM_SIGNATURE=h,p.DEVICE_KEY=n.deviceKey;var v={ChallengeName:"DEVICE_PASSWORD_VERIFIER",ClientId:n.pool.getClientId(),ChallengeResponses:p,Session:a.Session};n.getUserContextData()&&(v.UserContextData=n.getUserContextData()),n.client.request("RespondToAuthChallenge",v,(function(t,r){return t?e.onFailure(t):(n.signInUserSession=n.getCognitoUserSession(r.AuthenticationResult),n.cacheTokens(),e.onSuccess(n.signInUserSession))}))}))}))}))},t.confirmRegistration=function(e,t,n,r){var i={ClientId:this.pool.getClientId(),ConfirmationCode:e,Username:this.username,ForceAliasCreation:t,ClientMetadata:r};this.getUserContextData()&&(i.UserContextData=this.getUserContextData()),this.client.request("ConfirmSignUp",i,(function(e){return e?n(e,null):n(null,"SUCCESS")}))},t.sendCustomChallengeAnswer=function(e,t,n){var r=this,i={};i.USERNAME=this.username,i.ANSWER=e;var o=new k(this.pool.getUserPoolId().split("_")[1]);this.getCachedDeviceKeyAndPassword(),null!=this.deviceKey&&(i.DEVICE_KEY=this.deviceKey);var s={ChallengeName:"CUSTOM_CHALLENGE",ChallengeResponses:i,ClientId:this.pool.getClientId(),Session:this.Session,ClientMetadata:n};this.getUserContextData()&&(s.UserContextData=this.getUserContextData()),this.client.request("RespondToAuthChallenge",s,(function(e,n){return e?t.onFailure(e):r.authenticateUserInternal(n,o,t)}))},t.sendMFACode=function(e,t,n,r){var o=this,s={};s.USERNAME=this.username,s.SMS_MFA_CODE=e;var a=n||"SMS_MFA";"SOFTWARE_TOKEN_MFA"===a&&(s.SOFTWARE_TOKEN_MFA_CODE=e),null!=this.deviceKey&&(s.DEVICE_KEY=this.deviceKey);var u={ChallengeName:a,ChallengeResponses:s,ClientId:this.pool.getClientId(),Session:this.Session,ClientMetadata:r};this.getUserContextData()&&(u.UserContextData=this.getUserContextData()),this.client.request("RespondToAuthChallenge",u,(function(e,n){if(e)return t.onFailure(e);if("DEVICE_SRP_AUTH"!==n.ChallengeName){if(o.signInUserSession=o.getCognitoUserSession(n.AuthenticationResult),o.cacheTokens(),null==n.AuthenticationResult.NewDeviceMetadata)return t.onSuccess(o.signInUserSession);var r=new k(o.pool.getUserPoolId().split("_")[1]);r.generateHashDevice(n.AuthenticationResult.NewDeviceMetadata.DeviceGroupKey,n.AuthenticationResult.NewDeviceMetadata.DeviceKey,(function(e){if(e)return t.onFailure(e);var s={Salt:i.Buffer.from(r.getSaltDevices(),"hex").toString("base64"),PasswordVerifier:i.Buffer.from(r.getVerifierDevices(),"hex").toString("base64")};o.verifierDevices=s.PasswordVerifier,o.deviceGroupKey=n.AuthenticationResult.NewDeviceMetadata.DeviceGroupKey,o.randomPassword=r.getRandomPassword(),o.client.request("ConfirmDevice",{DeviceKey:n.AuthenticationResult.NewDeviceMetadata.DeviceKey,AccessToken:o.signInUserSession.getAccessToken().getJwtToken(),DeviceSecretVerifierConfig:s,DeviceName:H},(function(e,r){return e?t.onFailure(e):(o.deviceKey=n.AuthenticationResult.NewDeviceMetadata.DeviceKey,o.cacheDeviceKeyAndPassword(),!0===r.UserConfirmationNecessary?t.onSuccess(o.signInUserSession,r.UserConfirmationNecessary):t.onSuccess(o.signInUserSession))}))}))}else o.getDeviceResponse(t)}))},t.changePassword=function(e,t,n,r){if(null==this.signInUserSession||!this.signInUserSession.isValid())return n(new Error("User is not authenticated"),null);this.client.request("ChangePassword",{PreviousPassword:e,ProposedPassword:t,AccessToken:this.signInUserSession.getAccessToken().getJwtToken(),ClientMetadata:r},(function(e){return e?n(e,null):n(null,"SUCCESS")}))},t.enableMFA=function(e){if(null==this.signInUserSession||!this.signInUserSession.isValid())return e(new Error("User is not authenticated"),null);var t=[];t.push({DeliveryMedium:"SMS",AttributeName:"phone_number"}),this.client.request("SetUserSettings",{MFAOptions:t,AccessToken:this.signInUserSession.getAccessToken().getJwtToken()},(function(t){return t?e(t,null):e(null,"SUCCESS")}))},t.setUserMfaPreference=function(e,t,n){if(null==this.signInUserSession||!this.signInUserSession.isValid())return n(new Error("User is not authenticated"),null);this.client.request("SetUserMFAPreference",{SMSMfaSettings:e,SoftwareTokenMfaSettings:t,AccessToken:this.signInUserSession.getAccessToken().getJwtToken()},(function(e){return e?n(e,null):n(null,"SUCCESS")}))},t.disableMFA=function(e){if(null==this.signInUserSession||!this.signInUserSession.isValid())return e(new Error("User is not authenticated"),null);this.client.request("SetUserSettings",{MFAOptions:[],AccessToken:this.signInUserSession.getAccessToken().getJwtToken()},(function(t){return t?e(t,null):e(null,"SUCCESS")}))},t.deleteUser=function(e,t){var n=this;if(null==this.signInUserSession||!this.signInUserSession.isValid())return e(new Error("User is not authenticated"),null);this.client.request("DeleteUser",{AccessToken:this.signInUserSession.getAccessToken().getJwtToken(),ClientMetadata:t},(function(t){return t?e(t,null):(n.clearCachedUser(),e(null,"SUCCESS"))}))},t.updateAttributes=function(e,t,n){var r=this;if(null==this.signInUserSession||!this.signInUserSession.isValid())return t(new Error("User is not authenticated"),null);this.client.request("UpdateUserAttributes",{AccessToken:this.signInUserSession.getAccessToken().getJwtToken(),UserAttributes:e,ClientMetadata:n},(function(e){return e?t(e,null):r.getUserData((function(){return t(null,"SUCCESS")}),{bypassCache:!0})}))},t.getUserAttributes=function(e){if(null==this.signInUserSession||!this.signInUserSession.isValid())return e(new Error("User is not authenticated"),null);this.client.request("GetUser",{AccessToken:this.signInUserSession.getAccessToken().getJwtToken()},(function(t,n){if(t)return e(t,null);for(var r=[],i=0;i<n.UserAttributes.length;i++){var o={Name:n.UserAttributes[i].Name,Value:n.UserAttributes[i].Value},s=new F(o);r.push(s)}return e(null,r)}))},t.getMFAOptions=function(e){if(null==this.signInUserSession||!this.signInUserSession.isValid())return e(new Error("User is not authenticated"),null);this.client.request("GetUser",{AccessToken:this.signInUserSession.getAccessToken().getJwtToken()},(function(t,n){return t?e(t,null):e(null,n.MFAOptions)}))},t.createGetUserRequest=function(){return this.client.promisifyRequest("GetUser",{AccessToken:this.signInUserSession.getAccessToken().getJwtToken()})},t.refreshSessionIfPossible=function(e){var t=this;return void 0===e&&(e={}),new Promise((function(n){var r=t.signInUserSession.getRefreshToken();r&&r.getToken()?t.refreshSession(r,n,e.clientMetadata):n()}))},t.getUserData=function(e,t){var n=this;if(null==this.signInUserSession||!this.signInUserSession.isValid())return this.clearCachedUserData(),e(new Error("User is not authenticated"),null);var r=this.getUserDataFromCache();if(r)if(this.isFetchUserDataAndTokenRequired(t))this.fetchUserData().then((function(e){return n.refreshSessionIfPossible(t).then((function(){return e}))})).then((function(t){return e(null,t)})).catch(e);else try{return void e(null,JSON.parse(r))}catch(t){return this.clearCachedUserData(),void e(t,null)}else this.fetchUserData().then((function(t){e(null,t)})).catch(e)},t.getUserDataFromCache=function(){return this.storage.getItem(this.userDataKey)},t.isFetchUserDataAndTokenRequired=function(e){var t=(e||{}).bypassCache;return void 0!==t&&t},t.fetchUserData=function(){var e=this;return this.createGetUserRequest().then((function(t){return e.cacheUserData(t),t}))},t.deleteAttributes=function(e,t){if(null==this.signInUserSession||!this.signInUserSession.isValid())return t(new Error("User is not authenticated"),null);this.client.request("DeleteUserAttributes",{UserAttributeNames:e,AccessToken:this.signInUserSession.getAccessToken().getJwtToken()},(function(e){return e?t(e,null):t(null,"SUCCESS")}))},t.resendConfirmationCode=function(e,t){var n={ClientId:this.pool.getClientId(),Username:this.username,ClientMetadata:t};this.client.request("ResendConfirmationCode",n,(function(t,n){return t?e(t,null):e(null,n)}))},t.getSession=function(e,t){if(void 0===t&&(t={}),null==this.username)return e(new Error("Username is null. Cannot retrieve a new session"),null);if(null!=this.signInUserSession&&this.signInUserSession.isValid())return e(null,this.signInUserSession);var n="CognitoIdentityServiceProvider."+this.pool.getClientId()+"."+this.username,r=n+".idToken",i=n+".accessToken",o=n+".refreshToken",s=n+".clockDrift";if(this.storage.getItem(r)){var a=new P({IdToken:this.storage.getItem(r)}),u=new C({AccessToken:this.storage.getItem(i)}),c=new N({RefreshToken:this.storage.getItem(o)}),f=parseInt(this.storage.getItem(s),0)||0,l=new j({IdToken:a,AccessToken:u,RefreshToken:c,ClockDrift:f});if(l.isValid())return this.signInUserSession=l,e(null,this.signInUserSession);if(!c.getToken())return e(new Error("Cannot retrieve a new session. Please authenticate."),null);this.refreshSession(c,e,t.clientMetadata)}else e(new Error("Local storage is missing an ID Token, Please authenticate"),null)},t.refreshSession=function(e,t,n){var r=this,i=this.pool.wrapRefreshSessionCallback?this.pool.wrapRefreshSessionCallback(t):t,o={};o.REFRESH_TOKEN=e.getToken();var s="CognitoIdentityServiceProvider."+this.pool.getClientId(),a=s+".LastAuthUser";if(this.storage.getItem(a)){this.username=this.storage.getItem(a);var u=s+"."+this.username+".deviceKey";this.deviceKey=this.storage.getItem(u),o.DEVICE_KEY=this.deviceKey}var c={ClientId:this.pool.getClientId(),AuthFlow:"REFRESH_TOKEN_AUTH",AuthParameters:o,ClientMetadata:n};this.getUserContextData()&&(c.UserContextData=this.getUserContextData()),this.client.request("InitiateAuth",c,(function(t,n){if(t)return"NotAuthorizedException"===t.code&&r.clearCachedUser(),i(t,null);if(n){var o=n.AuthenticationResult;return Object.prototype.hasOwnProperty.call(o,"RefreshToken")||(o.RefreshToken=e.getToken()),r.signInUserSession=r.getCognitoUserSession(o),r.cacheTokens(),i(null,r.signInUserSession)}}))},t.cacheTokens=function(){var e="CognitoIdentityServiceProvider."+this.pool.getClientId(),t=e+"."+this.username+".idToken",n=e+"."+this.username+".accessToken",r=e+"."+this.username+".refreshToken",i=e+"."+this.username+".clockDrift",o=e+".LastAuthUser";this.storage.setItem(t,this.signInUserSession.getIdToken().getJwtToken()),this.storage.setItem(n,this.signInUserSession.getAccessToken().getJwtToken()),this.storage.setItem(r,this.signInUserSession.getRefreshToken().getToken()),this.storage.setItem(i,""+this.signInUserSession.getClockDrift()),this.storage.setItem(o,this.username)},t.cacheUserData=function(e){this.storage.setItem(this.userDataKey,JSON.stringify(e))},t.clearCachedUserData=function(){this.storage.removeItem(this.userDataKey)},t.clearCachedUser=function(){this.clearCachedTokens(),this.clearCachedUserData()},t.cacheDeviceKeyAndPassword=function(){var e="CognitoIdentityServiceProvider."+this.pool.getClientId()+"."+this.username,t=e+".deviceKey",n=e+".randomPasswordKey",r=e+".deviceGroupKey";this.storage.setItem(t,this.deviceKey),this.storage.setItem(n,this.randomPassword),this.storage.setItem(r,this.deviceGroupKey)},t.getCachedDeviceKeyAndPassword=function(){var e="CognitoIdentityServiceProvider."+this.pool.getClientId()+"."+this.username,t=e+".deviceKey",n=e+".randomPasswordKey",r=e+".deviceGroupKey";this.storage.getItem(t)&&(this.deviceKey=this.storage.getItem(t),this.randomPassword=this.storage.getItem(n),this.deviceGroupKey=this.storage.getItem(r))},t.clearCachedDeviceKeyAndPassword=function(){var e="CognitoIdentityServiceProvider."+this.pool.getClientId()+"."+this.username,t=e+".deviceKey",n=e+".randomPasswordKey",r=e+".deviceGroupKey";this.storage.removeItem(t),this.storage.removeItem(n),this.storage.removeItem(r)},t.clearCachedTokens=function(){var e="CognitoIdentityServiceProvider."+this.pool.getClientId(),t=e+"."+this.username+".idToken",n=e+"."+this.username+".accessToken",r=e+"."+this.username+".refreshToken",i=e+".LastAuthUser",o=e+"."+this.username+".clockDrift";this.storage.removeItem(t),this.storage.removeItem(n),this.storage.removeItem(r),this.storage.removeItem(i),this.storage.removeItem(o)},t.getCognitoUserSession=function(e){var t=new P(e),n=new C(e),r=new N(e);return new j({IdToken:t,AccessToken:n,RefreshToken:r})},t.forgotPassword=function(e,t){var n={ClientId:this.pool.getClientId(),Username:this.username,ClientMetadata:t};this.getUserContextData()&&(n.UserContextData=this.getUserContextData()),this.client.request("ForgotPassword",n,(function(t,n){return t?e.onFailure(t):"function"==typeof e.inputVerificationCode?e.inputVerificationCode(n):e.onSuccess(n)}))},t.confirmPassword=function(e,t,n,r){var i={ClientId:this.pool.getClientId(),Username:this.username,ConfirmationCode:e,Password:t,ClientMetadata:r};this.getUserContextData()&&(i.UserContextData=this.getUserContextData()),this.client.request("ConfirmForgotPassword",i,(function(e){return e?n.onFailure(e):n.onSuccess()}))},t.getAttributeVerificationCode=function(e,t,n){if(null==this.signInUserSession||!this.signInUserSession.isValid())return t.onFailure(new Error("User is not authenticated"));this.client.request("GetUserAttributeVerificationCode",{AttributeName:e,AccessToken:this.signInUserSession.getAccessToken().getJwtToken(),ClientMetadata:n},(function(e,n){return e?t.onFailure(e):"function"==typeof t.inputVerificationCode?t.inputVerificationCode(n):t.onSuccess()}))},t.verifyAttribute=function(e,t,n){if(null==this.signInUserSession||!this.signInUserSession.isValid())return n.onFailure(new Error("User is not authenticated"));this.client.request("VerifyUserAttribute",{AttributeName:e,Code:t,AccessToken:this.signInUserSession.getAccessToken().getJwtToken()},(function(e){return e?n.onFailure(e):n.onSuccess("SUCCESS")}))},t.getDevice=function(e){if(null==this.signInUserSession||!this.signInUserSession.isValid())return e.onFailure(new Error("User is not authenticated"));this.client.request("GetDevice",{AccessToken:this.signInUserSession.getAccessToken().getJwtToken(),DeviceKey:this.deviceKey},(function(t,n){return t?e.onFailure(t):e.onSuccess(n)}))},t.forgetSpecificDevice=function(e,t){if(null==this.signInUserSession||!this.signInUserSession.isValid())return t.onFailure(new Error("User is not authenticated"));this.client.request("ForgetDevice",{AccessToken:this.signInUserSession.getAccessToken().getJwtToken(),DeviceKey:e},(function(e){return e?t.onFailure(e):t.onSuccess("SUCCESS")}))},t.forgetDevice=function(e){var t=this;this.forgetSpecificDevice(this.deviceKey,{onFailure:e.onFailure,onSuccess:function(n){return t.deviceKey=null,t.deviceGroupKey=null,t.randomPassword=null,t.clearCachedDeviceKeyAndPassword(),e.onSuccess(n)}})},t.setDeviceStatusRemembered=function(e){if(null==this.signInUserSession||!this.signInUserSession.isValid())return e.onFailure(new Error("User is not authenticated"));this.client.request("UpdateDeviceStatus",{AccessToken:this.signInUserSession.getAccessToken().getJwtToken(),DeviceKey:this.deviceKey,DeviceRememberedStatus:"remembered"},(function(t){return t?e.onFailure(t):e.onSuccess("SUCCESS")}))},t.setDeviceStatusNotRemembered=function(e){if(null==this.signInUserSession||!this.signInUserSession.isValid())return e.onFailure(new Error("User is not authenticated"));this.client.request("UpdateDeviceStatus",{AccessToken:this.signInUserSession.getAccessToken().getJwtToken(),DeviceKey:this.deviceKey,DeviceRememberedStatus:"not_remembered"},(function(t){return t?e.onFailure(t):e.onSuccess("SUCCESS")}))},t.listDevices=function(e,t,n){if(null==this.signInUserSession||!this.signInUserSession.isValid())return n.onFailure(new Error("User is not authenticated"));var r={AccessToken:this.signInUserSession.getAccessToken().getJwtToken(),Limit:e};t&&(r.PaginationToken=t),this.client.request("ListDevices",r,(function(e,t){return e?n.onFailure(e):n.onSuccess(t)}))},t.globalSignOut=function(e){var t=this;if(null==this.signInUserSession||!this.signInUserSession.isValid())return e.onFailure(new Error("User is not authenticated"));this.client.request("GlobalSignOut",{AccessToken:this.signInUserSession.getAccessToken().getJwtToken()},(function(n){return n?e.onFailure(n):(t.clearCachedUser(),e.onSuccess("SUCCESS"))}))},t.signOut=function(){this.signInUserSession=null,this.clearCachedUser()},t.sendMFASelectionAnswer=function(e,t){var n=this,r={};r.USERNAME=this.username,r.ANSWER=e;var i={ChallengeName:"SELECT_MFA_TYPE",ChallengeResponses:r,ClientId:this.pool.getClientId(),Session:this.Session};this.getUserContextData()&&(i.UserContextData=this.getUserContextData()),this.client.request("RespondToAuthChallenge",i,(function(r,i){return r?t.onFailure(r):(n.Session=i.Session,"SMS_MFA"===e?t.mfaRequired(i.ChallengeName,i.ChallengeParameters):"SOFTWARE_TOKEN_MFA"===e?t.totpRequired(i.ChallengeName,i.ChallengeParameters):void 0)}))},t.getUserContextData=function(){return this.pool.getUserContextData(this.username)},t.associateSoftwareToken=function(e){var t=this;null!=this.signInUserSession&&this.signInUserSession.isValid()?this.client.request("AssociateSoftwareToken",{AccessToken:this.signInUserSession.getAccessToken().getJwtToken()},(function(t,n){return t?e.onFailure(t):e.associateSecretCode(n.SecretCode)})):this.client.request("AssociateSoftwareToken",{Session:this.Session},(function(n,r){return n?e.onFailure(n):(t.Session=r.Session,e.associateSecretCode(r.SecretCode))}))},t.verifySoftwareToken=function(e,t,n){var r=this;null!=this.signInUserSession&&this.signInUserSession.isValid()?this.client.request("VerifySoftwareToken",{AccessToken:this.signInUserSession.getAccessToken().getJwtToken(),UserCode:e,FriendlyDeviceName:t},(function(e,t){return e?n.onFailure(e):n.onSuccess(t)})):this.client.request("VerifySoftwareToken",{Session:this.Session,UserCode:e,FriendlyDeviceName:t},(function(e,t){if(e)return n.onFailure(e);r.Session=t.Session;var i={};i.USERNAME=r.username;var o={ChallengeName:"MFA_SETUP",ClientId:r.pool.getClientId(),ChallengeResponses:i,Session:r.Session};r.getUserContextData()&&(o.UserContextData=r.getUserContextData()),r.client.request("RespondToAuthChallenge",o,(function(e,t){return e?n.onFailure(e):(r.signInUserSession=r.getCognitoUserSession(t.AuthenticationResult),r.cacheTokens(),n.onSuccess(r.signInUserSession))}))}))},e}(); -/*! - * Copyright 2016 Amazon.com, - * Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Amazon Software License (the "License"). - * You may not use this file except in compliance with the - * License. A copy of the License is located at - * - * http://aws.amazon.com/asl/ - * - * or in the "license" file accompanying this file. This file is - * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, express or implied. See the License - * for the specific language governing permissions and - * limitations under the License. - */n(365);function G(){}G.prototype.userAgent="aws-amplify/0.1.x js";var W=G;function $(e){var t="function"==typeof Map?new Map:void 0;return($=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,r)}function r(){return Y(e,arguments,X(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),Z(r,e)})(e)}function Y(e,t,n){return(Y=J()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var i=new(Function.bind.apply(e,r));return n&&Z(i,n.prototype),i}).apply(null,arguments)}function J(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function Z(e,t){return(Z=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function X(e){return(X=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var Q=function(e){var t,n;function r(t,n,r,i){var o;return(o=e.call(this,t)||this).code=n,o.name=r,o.statusCode=i,o}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,Z(t,n),r}($(Error)),ee=function(){function e(e,t,n){this.endpoint=t||"https://cognito-idp."+e+".amazonaws.com/";var r=(n||{}).credentials;this.fetchOptions=r?{credentials:r}:{}}var t=e.prototype;return t.promisifyRequest=function(e,t){var n=this;return new Promise((function(r,i){n.request(e,t,(function(e,t){e?i(new Q(e.message,e.code,e.name,e.statusCode)):r(t)}))}))},t.request=function(e,t,n){var r,i={"Content-Type":"application/x-amz-json-1.1","X-Amz-Target":"AWSCognitoIdentityProviderService."+e,"X-Amz-User-Agent":W.prototype.userAgent},o=Object.assign({},this.fetchOptions,{headers:i,method:"POST",mode:"cors",cache:"no-cache",body:JSON.stringify(t)});fetch(this.endpoint,o).then((function(e){return r=e,e}),(function(e){if(e instanceof TypeError)throw new Error("Network error");throw e})).then((function(e){return e.json().catch((function(){return{}}))})).then((function(e){if(r.ok)return n(null,e);e;var t=(e.__type||e.code).split("#").pop(),i={code:t,name:t,message:e.message||e.Message||null};return n(i)})).catch((function(e){if(!(r&&r.headers&&r.headers.get("x-amzn-errortype"))){if(e instanceof Error&&"Network error"===e.message){var t={code:"NetworkError",name:e.name,message:e.message};return n(t)}return n(e)}try{var i=r.headers.get("x-amzn-errortype").split(":")[0],o={code:i,name:i,statusCode:r.status,message:r.status?r.status.toString():null};return n(o)}catch(t){return n(e)}}))},e}(),te=function(){function e(e,t){var n=e||{},r=n.UserPoolId,i=n.ClientId,o=n.endpoint,s=n.fetchOptions,a=n.AdvancedSecurityDataCollectionFlag;if(!r||!i)throw new Error("Both UserPoolId and ClientId are required.");if(!/^[\w-]+_.+$/.test(r))throw new Error("Invalid UserPoolId format.");var u=r.split("_")[0];this.userPoolId=r,this.clientId=i,this.client=new ee(u,o,s),this.advancedSecurityDataCollectionFlag=!1!==a,this.storage=e.Storage||(new K).getStorage(),t&&(this.wrapRefreshSessionCallback=t)}var t=e.prototype;return t.getUserPoolId=function(){return this.userPoolId},t.getClientId=function(){return this.clientId},t.signUp=function(e,t,n,r,i,o){var s=this,a={ClientId:this.clientId,Username:e,Password:t,UserAttributes:n,ValidationData:r,ClientMetadata:o};this.getUserContextData(e)&&(a.UserContextData=this.getUserContextData(e)),this.client.request("SignUp",a,(function(t,n){if(t)return i(t,null);var r={Username:e,Pool:s,Storage:s.storage},o={user:new V(r),userConfirmed:n.UserConfirmed,userSub:n.UserSub,codeDeliveryDetails:n.CodeDeliveryDetails};return i(null,o)}))},t.getCurrentUser=function(){var e="CognitoIdentityServiceProvider."+this.clientId+".LastAuthUser",t=this.storage.getItem(e);if(t){var n={Username:t,Pool:this,Storage:this.storage};return new V(n)}return null},t.getUserContextData=function(e){if("undefined"!=typeof AmazonCognitoAdvancedSecurityData){var t=AmazonCognitoAdvancedSecurityData;if(this.advancedSecurityDataCollectionFlag){var n=t.getData(e,this.userPoolId,this.clientId);if(n)return{EncodedData:n}}return{}}},e}(),ne=n(65),re=function(){function e(e){if(!e.domain)throw new Error("The domain of cookieStorage can not be undefined.");if(this.domain=e.domain,e.path?this.path=e.path:this.path="/",Object.prototype.hasOwnProperty.call(e,"expires")?this.expires=e.expires:this.expires=365,Object.prototype.hasOwnProperty.call(e,"secure")?this.secure=e.secure:this.secure=!0,Object.prototype.hasOwnProperty.call(e,"sameSite")){if(!["strict","lax","none"].includes(e.sameSite))throw new Error('The sameSite value of cookieStorage must be "lax", "strict" or "none".');if("none"===e.sameSite&&!this.secure)throw new Error("sameSite = None requires the Secure attribute in latest browser versions.");this.sameSite=e.sameSite}else this.sameSite=null}var t=e.prototype;return t.setItem=function(e,t){var n={path:this.path,expires:this.expires,domain:this.domain,secure:this.secure};return this.sameSite&&(n.sameSite=this.sameSite),ne.set(e,t,n),ne.get(e)},t.getItem=function(e){return ne.get(e)},t.removeItem=function(e){var t={path:this.path,expires:this.expires,domain:this.domain,secure:this.secure};return this.sameSite&&(t.sameSite=this.sameSite),ne.remove(e,t)},t.clear=function(){var e,t=ne.get();for(e=0;e<t.length;++e)ne.remove(t[e]);return{}},e}()},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){var r;e.exports=(r=r||function(e,t){var n=Object.create||function(){function e(){}return function(t){var n;return e.prototype=t,n=new e,e.prototype=null,n}}(),r={},i=r.lib={},o=i.Base={extend:function(e){var t=n(this);return e&&t.mixIn(e),t.hasOwnProperty("init")&&this.init!==t.init||(t.init=function(){t.$super.init.apply(this,arguments)}),t.init.prototype=t,t.$super=this,t},create:function(){var e=this.extend();return e.init.apply(e,arguments),e},init:function(){},mixIn:function(e){for(var t in e)e.hasOwnProperty(t)&&(this[t]=e[t]);e.hasOwnProperty("toString")&&(this.toString=e.toString)},clone:function(){return this.init.prototype.extend(this)}},s=i.WordArray=o.extend({init:function(e,t){e=this.words=e||[],this.sigBytes=null!=t?t:4*e.length},toString:function(e){return(e||u).stringify(this)},concat:function(e){var t=this.words,n=e.words,r=this.sigBytes,i=e.sigBytes;if(this.clamp(),r%4)for(var o=0;o<i;o++){var s=n[o>>>2]>>>24-o%4*8&255;t[r+o>>>2]|=s<<24-(r+o)%4*8}else for(o=0;o<i;o+=4)t[r+o>>>2]=n[o>>>2];return this.sigBytes+=i,this},clamp:function(){var t=this.words,n=this.sigBytes;t[n>>>2]&=4294967295<<32-n%4*8,t.length=e.ceil(n/4)},clone:function(){var e=o.clone.call(this);return e.words=this.words.slice(0),e},random:function(t){for(var n,r=[],i=function(t){t=t;var n=987654321,r=4294967295;return function(){var i=((n=36969*(65535&n)+(n>>16)&r)<<16)+(t=18e3*(65535&t)+(t>>16)&r)&r;return i/=4294967296,(i+=.5)*(e.random()>.5?1:-1)}},o=0;o<t;o+=4){var a=i(4294967296*(n||e.random()));n=987654071*a(),r.push(4294967296*a()|0)}return new s.init(r,t)}}),a=r.enc={},u=a.Hex={stringify:function(e){for(var t=e.words,n=e.sigBytes,r=[],i=0;i<n;i++){var o=t[i>>>2]>>>24-i%4*8&255;r.push((o>>>4).toString(16)),r.push((15&o).toString(16))}return r.join("")},parse:function(e){for(var t=e.length,n=[],r=0;r<t;r+=2)n[r>>>3]|=parseInt(e.substr(r,2),16)<<24-r%8*4;return new s.init(n,t/2)}},c=a.Latin1={stringify:function(e){for(var t=e.words,n=e.sigBytes,r=[],i=0;i<n;i++){var o=t[i>>>2]>>>24-i%4*8&255;r.push(String.fromCharCode(o))}return r.join("")},parse:function(e){for(var t=e.length,n=[],r=0;r<t;r++)n[r>>>2]|=(255&e.charCodeAt(r))<<24-r%4*8;return new s.init(n,t)}},f=a.Utf8={stringify:function(e){try{return decodeURIComponent(escape(c.stringify(e)))}catch(e){throw new Error("Malformed UTF-8 data")}},parse:function(e){return c.parse(unescape(encodeURIComponent(e)))}},l=i.BufferedBlockAlgorithm=o.extend({reset:function(){this._data=new s.init,this._nDataBytes=0},_append:function(e){"string"==typeof e&&(e=f.parse(e)),this._data.concat(e),this._nDataBytes+=e.sigBytes},_process:function(t){var n=this._data,r=n.words,i=n.sigBytes,o=this.blockSize,a=i/(4*o),u=(a=t?e.ceil(a):e.max((0|a)-this._minBufferSize,0))*o,c=e.min(4*u,i);if(u){for(var f=0;f<u;f+=o)this._doProcessBlock(r,f);var l=r.splice(0,u);n.sigBytes-=c}return new s.init(l,c)},clone:function(){var e=o.clone.call(this);return e._data=this._data.clone(),e},_minBufferSize:0}),d=(i.Hasher=l.extend({cfg:o.extend(),init:function(e){this.cfg=this.cfg.extend(e),this.reset()},reset:function(){l.reset.call(this),this._doReset()},update:function(e){return this._append(e),this._process(),this},finalize:function(e){return e&&this._append(e),this._doFinalize()},blockSize:16,_createHelper:function(e){return function(t,n){return new e.init(n).finalize(t)}},_createHmacHelper:function(e){return function(t,n){return new d.HMAC.init(e,n).finalize(t)}}}),r.algo={});return r}(Math),r)},function(e,t,n){"use strict";(function(e){n.d(t,"d",(function(){return f})),n.d(t,"c",(function(){return l})),n.d(t,"b",(function(){return d})),n.d(t,"a",(function(){return g}));var r=[{type:"text/plain",ext:"txt"},{type:"text/html",ext:"html"},{type:"text/javascript",ext:"js"},{type:"text/css",ext:"css"},{type:"text/csv",ext:"csv"},{type:"text/yaml",ext:"yml"},{type:"text/yaml",ext:"yaml"},{type:"text/calendar",ext:"ics"},{type:"text/calendar",ext:"ical"},{type:"image/apng",ext:"apng"},{type:"image/bmp",ext:"bmp"},{type:"image/gif",ext:"gif"},{type:"image/x-icon",ext:"ico"},{type:"image/x-icon",ext:"cur"},{type:"image/jpeg",ext:"jpg"},{type:"image/jpeg",ext:"jpeg"},{type:"image/jpeg",ext:"jfif"},{type:"image/jpeg",ext:"pjp"},{type:"image/jpeg",ext:"pjpeg"},{type:"image/png",ext:"png"},{type:"image/svg+xml",ext:"svg"},{type:"image/tiff",ext:"tif"},{type:"image/tiff",ext:"tiff"},{type:"image/webp",ext:"webp"},{type:"application/json",ext:"json"},{type:"application/xml",ext:"xml"},{type:"application/x-sh",ext:"sh"},{type:"application/zip",ext:"zip"},{type:"application/x-rar-compressed",ext:"rar"},{type:"application/x-tar",ext:"tar"},{type:"application/x-bzip",ext:"bz"},{type:"application/x-bzip2",ext:"bz2"},{type:"application/pdf",ext:"pdf"},{type:"application/java-archive",ext:"jar"},{type:"application/msword",ext:"doc"},{type:"application/vnd.ms-excel",ext:"xls"},{type:"application/vnd.ms-excel",ext:"xlsx"},{type:"message/rfc822",ext:"eml"}],i=function(e){return void 0===e&&(e={}),0===Object.keys(e).length},o=function(e,t,n){if(!e||!e.sort)return!1;var r=n&&"desc"===n?-1:1;return e.sort((function(e,n){var i=e[t],o=n[t];return void 0===o?void 0===i?0:1*r:void 0===i||i<o?-1*r:i>o?1*r:0})),!0},s=function(e,t){var n=Object.assign({},e);return t&&("string"==typeof t?delete n[t]:t.forEach((function(e){delete n[e]}))),n},a=function(e,t){void 0===t&&(t="application/octet-stream");var n=e.toLowerCase(),i=r.filter((function(e){return n.endsWith("."+e.ext)}));return i.length>0?i[0].type:t},u=function(e){var t=e.toLowerCase();return!!t.startsWith("text/")||("application/json"===t||"application/xml"===t||"application/sh"===t)},c=function(){for(var e="",t="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",n=32;n>0;n-=1)e+=t[Math.floor(Math.random()*t.length)];return e},f=function(e){if(e.isResolved)return e;var t=!0,n=!1,r=!1,i=e.then((function(e){return r=!0,t=!1,e}),(function(e){throw n=!0,t=!1,e}));return i.isFullfilled=function(){return r},i.isPending=function(){return t},i.isRejected=function(){return n},i},l=function(){if("undefined"==typeof self)return!1;var e=self;return void 0!==e.WorkerGlobalScope&&self instanceof e.WorkerGlobalScope},d=function(){return{isBrowser:"undefined"!=typeof window&&void 0!==window.document,isNode:void 0!==e&&null!=e.versions&&null!=e.versions.node}},h=function e(t,n,r){if(void 0===n&&(n=[]),void 0===r&&(r=[]),!v(t))return t;var i={};for(var o in t){if(t.hasOwnProperty(o))i[n.includes(o)?o:o[0].toLowerCase()+o.slice(1)]=r.includes(o)?t[o]:e(t[o],n,r)}return i},p=function e(t,n,r){if(void 0===n&&(n=[]),void 0===r&&(r=[]),!v(t))return t;var i={};for(var o in t){if(t.hasOwnProperty(o))i[n.includes(o)?o:o[0].toUpperCase()+o.slice(1)]=r.includes(o)?t[o]:e(t[o],n,r)}return i},v=function(e){return!(!(e instanceof Object)||e instanceof Array||e instanceof Function||e instanceof Number||e instanceof String||e instanceof Boolean)},g=function(){function e(){}return e.isEmpty=i,e.sortByField=o,e.objectLessAttributes=s,e.filenameToContentType=a,e.isTextFile=u,e.generateRandomString=c,e.makeQuerablePromise=f,e.isWebWorker=l,e.browserOrNode=d,e.transferKeyToLowerCase=h,e.transferKeyToUpperCase=p,e.isStrictObject=v,e}()}).call(this,n(20))},function(e,t,n){"use strict";n.d(t,"a",(function(){return r}));var r,i=n(105);!function(e){e.CONNECTION_CLOSED="Connection closed",e.TIMEOUT_DISCONNECT="Timeout disconnect",e.SUBSCRIPTION_ACK="Subscription ack"}(r||(r={})),t.b=i.a},function(e,t,n){"use strict";var r;n.d(t,"a",(function(){return r})),function(e){e.DEFAULT_MSG="Authentication Error",e.EMPTY_USERNAME="Username cannot be empty",e.INVALID_USERNAME="The username should either be a string or one of the sign in types",e.EMPTY_PASSWORD="Password cannot be empty",e.EMPTY_CODE="Confirmation code cannot be empty",e.SIGN_UP_ERROR="Error creating account",e.NO_MFA="No valid MFA method provided",e.INVALID_MFA="Invalid MFA type",e.EMPTY_CHALLENGE="Challenge response cannot be empty",e.NO_USER_SESSION="Failed to get the session because the user is empty"}(r||(r={}))},function(e,t,n){var r=n(228),i=n(230),o=n(231),s=n(61),a=n(232),u=n(138),c=n(229),f=n(139),l=Object.prototype.hasOwnProperty;e.exports=function(e){if(null==e)return!0;if(a(e)&&(s(e)||"string"==typeof e||"function"==typeof e.splice||u(e)||f(e)||o(e)))return!e.length;var t=i(e);if("[object Map]"==t||"[object Set]"==t)return!e.size;if(c(e))return!r(e).length;for(var n in e)if(l.call(e,n))return!1;return!0}},function(e,t,n){"use strict";n.d(t,"a",(function(){return s}));var r=n(1),i=n(2);var o={step:"build",tags:["SET_CONTENT_LENGTH","CONTENT_LENGTH"],name:"contentLengthMiddleware"},s=function(e){return{applyToStack:function(t){t.add(function(e){var t=this;return function(n){return function(o){return Object(r.__awaiter)(t,void 0,void 0,(function(){var t,s,a,u,c;return Object(r.__generator)(this,(function(f){return t=o.request,i.a.isInstance(t)&&(s=t.body,a=t.headers,s&&-1===Object.keys(a).map((function(e){return e.toLowerCase()})).indexOf("content-length")&&void 0!==(u=e(s))&&(t.headers=Object(r.__assign)(Object(r.__assign)({},t.headers),((c={})["content-length"]=String(u),c)))),[2,n(Object(r.__assign)(Object(r.__assign)({},o),{request:t}))]}))}))}}}(e.bodyLengthChecker),o)}}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.WebCryptoSha256=t.Ie11Sha256=void 0,n(1).__exportStar(n(373),t);var r=n(216);Object.defineProperty(t,"Ie11Sha256",{enumerable:!0,get:function(){return r.Sha256}});var i=n(219);Object.defineProperty(t,"WebCryptoSha256",{enumerable:!0,get:function(){return i.Sha256}})},function(e,t,n){"use strict";n.d(t,"a",(function(){return i}));var r=n(106),i=function(e){var t,n=new URL(e),i=n.hostname,o=n.pathname,s=n.port,a=n.protocol,u=n.search;return u&&(t=Object(r.a)(u)),{hostname:i,port:s?parseInt(s):void 0,protocol:a,path:o,query:t}}},function(e,t,n){"use strict";function r(e){if("string"==typeof e){for(var t=e.length,n=t-1;n>=0;n--){var r=e.charCodeAt(n);r>127&&r<=2047?t++:r>2047&&r<=65535&&(t+=2)}return t}return"number"==typeof e.byteLength?e.byteLength:"number"==typeof e.size?e.size:void 0}n.d(t,"a",(function(){return r}))},function(e,t,n){"use strict";function r(e,t){return"aws-sdk-js-v3-"+e+"/"+t+" "+("undefined"!=typeof navigator&&"string"==typeof navigator.userAgent?navigator.userAgent:"")}n.d(t,"a",(function(){return r}))},function(e,t,n){"use strict";var r=n(63);n(30);t.a=r.a},function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));var r=n(1),i={name:"loggerMiddleware",tags:["LOGGER"],step:"initialize"},o=function(e){return{applyToStack:function(e){e.add((function(e,t){return function(n){return Object(r.__awaiter)(void 0,void 0,void 0,(function(){var i,o,s,a;return Object(r.__generator)(this,(function(r){switch(r.label){case 0:return i=t.logger,[4,e(n)];case 1:return o=r.sent(),i?(s=o.response,"function"==typeof i.info&&i.info({metadata:{statusCode:s.statusCode,requestId:null!==(a=s.headers["x-amzn-requestid"])&&void 0!==a?a:s.headers["x-amzn-request-id"],extendedRequestId:s.headers["x-amz-id-2"],cfId:s.headers["x-amz-cf-id"]}}),[2,o]):[2,o]}}))}))}}),i)}}}},function(e,t,n){"use strict";n.d(t,"a",(function(){return s}));var r=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},i=function(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(r(arguments[t]));return e},o={VERBOSE:1,DEBUG:2,INFO:3,WARN:4,ERROR:5},s=function(){function e(e,t){void 0===t&&(t="WARN"),this.name=e,this.level=t}return e.prototype._padding=function(e){return e<10?"0"+e:""+e},e.prototype._ts=function(){var e=new Date;return[this._padding(e.getMinutes()),this._padding(e.getSeconds())].join(":")+"."+e.getMilliseconds()},e.prototype._log=function(t){for(var n=[],r=1;r<arguments.length;r++)n[r-1]=arguments[r];var i=this.level;e.LOG_LEVEL&&(i=e.LOG_LEVEL),"undefined"!=typeof window&&window.LOG_LEVEL&&(i=window.LOG_LEVEL);var s=o[i],a=o[t];if(a>=s){var u=console.log.bind(console);"ERROR"===t&&console.error&&(u=console.error.bind(console)),"WARN"===t&&console.warn&&(u=console.warn.bind(console));var c="["+t+"] "+this._ts()+" "+this.name;if(1===n.length&&"string"==typeof n[0])u(c+" - "+n[0]);else if(1===n.length)u(c,n[0]);else if("string"==typeof n[0]){var f=n.slice(1);1===f.length&&(f=f[0]),u(c+" - "+n[0],f)}else u(c,n)}},e.prototype.log=function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];this._log.apply(this,i(["INFO"],e))},e.prototype.info=function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];this._log.apply(this,i(["INFO"],e))},e.prototype.warn=function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];this._log.apply(this,i(["WARN"],e))},e.prototype.error=function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];this._log.apply(this,i(["ERROR"],e))},e.prototype.debug=function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];this._log.apply(this,i(["DEBUG"],e))},e.prototype.verbose=function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];this._log.apply(this,i(["VERBOSE"],e))},e.LOG_LEVEL=null,e}()},function(e,t,n){"use strict";var r=n(235),i=Object.prototype.toString;function o(e){return"[object Array]"===i.call(e)}function s(e){return void 0===e}function a(e){return null!==e&&"object"==typeof e}function u(e){if("[object Object]"!==i.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function c(e){return"[object Function]"===i.call(e)}function f(e,t){if(null!=e)if("object"!=typeof e&&(e=[e]),o(e))for(var n=0,r=e.length;n<r;n++)t.call(null,e[n],n,e);else for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&t.call(null,e[i],i,e)}e.exports={isArray:o,isArrayBuffer:function(e){return"[object ArrayBuffer]"===i.call(e)},isBuffer:function(e){return null!==e&&!s(e)&&null!==e.constructor&&!s(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)},isFormData:function(e){return"undefined"!=typeof FormData&&e instanceof FormData},isArrayBufferView:function(e){return"undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer},isString:function(e){return"string"==typeof e},isNumber:function(e){return"number"==typeof e},isObject:a,isPlainObject:u,isUndefined:s,isDate:function(e){return"[object Date]"===i.call(e)},isFile:function(e){return"[object File]"===i.call(e)},isBlob:function(e){return"[object Blob]"===i.call(e)},isFunction:c,isStream:function(e){return a(e)&&c(e.pipe)},isURLSearchParams:function(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams},isStandardBrowserEnv:function(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)},forEach:f,merge:function e(){var t={};function n(n,r){u(t[r])&&u(n)?t[r]=e(t[r],n):u(n)?t[r]=e({},n):o(n)?t[r]=n.slice():t[r]=n}for(var r=0,i=arguments.length;r<i;r++)f(arguments[r],n);return t},extend:function(e,t,n){return f(t,(function(t,i){e[i]=n&&"function"==typeof t?r(t,n):t})),e},trim:function(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")},stripBOM:function(e){return 65279===e.charCodeAt(0)&&(e=e.slice(1)),e}}},function(e,t){function n(e,t){if(!e)throw new Error(t||"Assertion failed")}e.exports=n,n.equal=function(e,t,n){if(e!=t)throw new Error(n||"Assertion failed: "+e+" != "+t)}},function(e,t,n){"use strict";var r=t,i=n(29),o=n(46),s=n(198);r.assert=o,r.toArray=s.toArray,r.zero2=s.zero2,r.toHex=s.toHex,r.encode=s.encode,r.getNAF=function(e,t,n){var r=new Array(Math.max(e.bitLength(),n)+1);r.fill(0);for(var i=1<<t+1,o=e.clone(),s=0;s<r.length;s++){var a,u=o.andln(i-1);o.isOdd()?(a=u>(i>>1)-1?(i>>1)-u:u,o.isubn(a)):a=0,r[s]=a,o.iushrn(1)}return r},r.getJSF=function(e,t){var n=[[],[]];e=e.clone(),t=t.clone();for(var r,i=0,o=0;e.cmpn(-i)>0||t.cmpn(-o)>0;){var s,a,u=e.andln(3)+i&3,c=t.andln(3)+o&3;3===u&&(u=-1),3===c&&(c=-1),s=0==(1&u)?0:3!==(r=e.andln(7)+i&7)&&5!==r||2!==c?u:-u,n[0].push(s),a=0==(1&c)?0:3!==(r=t.andln(7)+o&7)&&5!==r||2!==u?c:-c,n[1].push(a),2*i===s+1&&(i=1-i),2*o===a+1&&(o=1-o),e.iushrn(1),t.iushrn(1)}return n},r.cachedProperty=function(e,t,n){var r="_"+t;e.prototype[t]=function(){return void 0!==this[r]?this[r]:this[r]=n.call(this)}},r.parseBytes=function(e){return"string"==typeof e?r.toArray(e,"hex"):e},r.intFromLE=function(e){return new i(e,"hex","le")}},,function(e,t,n){"use strict";var r,i="object"==typeof Reflect?Reflect:null,o=i&&"function"==typeof i.apply?i.apply:function(e,t,n){return Function.prototype.apply.call(e,t,n)};r=i&&"function"==typeof i.ownKeys?i.ownKeys:Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:function(e){return Object.getOwnPropertyNames(e)};var s=Number.isNaN||function(e){return e!=e};function a(){a.init.call(this)}e.exports=a,e.exports.once=function(e,t){return new Promise((function(n,r){function i(){void 0!==o&&e.removeListener("error",o),n([].slice.call(arguments))}var o;"error"!==t&&(o=function(n){e.removeListener(t,i),r(n)},e.once("error",o)),e.once(t,i)}))},a.EventEmitter=a,a.prototype._events=void 0,a.prototype._eventsCount=0,a.prototype._maxListeners=void 0;var u=10;function c(e){if("function"!=typeof e)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof e)}function f(e){return void 0===e._maxListeners?a.defaultMaxListeners:e._maxListeners}function l(e,t,n,r){var i,o,s,a;if(c(n),void 0===(o=e._events)?(o=e._events=Object.create(null),e._eventsCount=0):(void 0!==o.newListener&&(e.emit("newListener",t,n.listener?n.listener:n),o=e._events),s=o[t]),void 0===s)s=o[t]=n,++e._eventsCount;else if("function"==typeof s?s=o[t]=r?[n,s]:[s,n]:r?s.unshift(n):s.push(n),(i=f(e))>0&&s.length>i&&!s.warned){s.warned=!0;var u=new Error("Possible EventEmitter memory leak detected. "+s.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");u.name="MaxListenersExceededWarning",u.emitter=e,u.type=t,u.count=s.length,a=u,console&&console.warn&&console.warn(a)}return e}function d(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function h(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},i=d.bind(r);return i.listener=n,r.wrapFn=i,i}function p(e,t,n){var r=e._events;if(void 0===r)return[];var i=r[t];return void 0===i?[]:"function"==typeof i?n?[i.listener||i]:[i]:n?function(e){for(var t=new Array(e.length),n=0;n<t.length;++n)t[n]=e[n].listener||e[n];return t}(i):g(i,i.length)}function v(e){var t=this._events;if(void 0!==t){var n=t[e];if("function"==typeof n)return 1;if(void 0!==n)return n.length}return 0}function g(e,t){for(var n=new Array(t),r=0;r<t;++r)n[r]=e[r];return n}Object.defineProperty(a,"defaultMaxListeners",{enumerable:!0,get:function(){return u},set:function(e){if("number"!=typeof e||e<0||s(e))throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received '+e+".");u=e}}),a.init=function(){void 0!==this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=Object.create(null),this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},a.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||s(e))throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received '+e+".");return this._maxListeners=e,this},a.prototype.getMaxListeners=function(){return f(this)},a.prototype.emit=function(e){for(var t=[],n=1;n<arguments.length;n++)t.push(arguments[n]);var r="error"===e,i=this._events;if(void 0!==i)r=r&&void 0===i.error;else if(!r)return!1;if(r){var s;if(t.length>0&&(s=t[0]),s instanceof Error)throw s;var a=new Error("Unhandled error."+(s?" ("+s.message+")":""));throw a.context=s,a}var u=i[e];if(void 0===u)return!1;if("function"==typeof u)o(u,this,t);else{var c=u.length,f=g(u,c);for(n=0;n<c;++n)o(f[n],this,t)}return!0},a.prototype.addListener=function(e,t){return l(this,e,t,!1)},a.prototype.on=a.prototype.addListener,a.prototype.prependListener=function(e,t){return l(this,e,t,!0)},a.prototype.once=function(e,t){return c(t),this.on(e,h(this,e,t)),this},a.prototype.prependOnceListener=function(e,t){return c(t),this.prependListener(e,h(this,e,t)),this},a.prototype.removeListener=function(e,t){var n,r,i,o,s;if(c(t),void 0===(r=this._events))return this;if(void 0===(n=r[e]))return this;if(n===t||n.listener===t)0==--this._eventsCount?this._events=Object.create(null):(delete r[e],r.removeListener&&this.emit("removeListener",e,n.listener||t));else if("function"!=typeof n){for(i=-1,o=n.length-1;o>=0;o--)if(n[o]===t||n[o].listener===t){s=n[o].listener,i=o;break}if(i<0)return this;0===i?n.shift():function(e,t){for(;t+1<e.length;t++)e[t]=e[t+1];e.pop()}(n,i),1===n.length&&(r[e]=n[0]),void 0!==r.removeListener&&this.emit("removeListener",e,s||t)}return this},a.prototype.off=a.prototype.removeListener,a.prototype.removeAllListeners=function(e){var t,n,r;if(void 0===(n=this._events))return this;if(void 0===n.removeListener)return 0===arguments.length?(this._events=Object.create(null),this._eventsCount=0):void 0!==n[e]&&(0==--this._eventsCount?this._events=Object.create(null):delete n[e]),this;if(0===arguments.length){var i,o=Object.keys(n);for(r=0;r<o.length;++r)"removeListener"!==(i=o[r])&&this.removeAllListeners(i);return this.removeAllListeners("removeListener"),this._events=Object.create(null),this._eventsCount=0,this}if("function"==typeof(t=n[e]))this.removeListener(e,t);else if(void 0!==t)for(r=t.length-1;r>=0;r--)this.removeListener(e,t[r]);return this},a.prototype.listeners=function(e){return p(this,e,!0)},a.prototype.rawListeners=function(e){return p(this,e,!1)},a.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):v.call(e,t)},a.prototype.listenerCount=v,a.prototype.eventNames=function(){return this._eventsCount>0?r(this._events):[]}},function(e,t,n){"use strict";n.d(t,"a",(function(){return r})),n.d(t,"b",(function(){return i}));var r={userAgent:"aws-amplify/3.8.12 js",product:"",navigator:null,isReactNative:!1};if("undefined"!=typeof navigator&&navigator.product)switch(r.product=navigator.product||"",r.navigator=navigator||null,navigator.product){case"ReactNative":r.userAgent="aws-amplify/3.8.12 react-native",r.isReactNative=!0;break;default:r.userAgent="aws-amplify/3.8.12 js",r.isReactNative=!1}var i=function(){return r.userAgent}},function(e,t,n){"use strict";var r=n(46),i=n(7);function o(e,t){return 55296==(64512&e.charCodeAt(t))&&(!(t<0||t+1>=e.length)&&56320==(64512&e.charCodeAt(t+1)))}function s(e){return(e>>>24|e>>>8&65280|e<<8&16711680|(255&e)<<24)>>>0}function a(e){return 1===e.length?"0"+e:e}function u(e){return 7===e.length?"0"+e:6===e.length?"00"+e:5===e.length?"000"+e:4===e.length?"0000"+e:3===e.length?"00000"+e:2===e.length?"000000"+e:1===e.length?"0000000"+e:e}t.inherits=i,t.toArray=function(e,t){if(Array.isArray(e))return e.slice();if(!e)return[];var n=[];if("string"==typeof e)if(t){if("hex"===t)for((e=e.replace(/[^a-z0-9]+/gi,"")).length%2!=0&&(e="0"+e),i=0;i<e.length;i+=2)n.push(parseInt(e[i]+e[i+1],16))}else for(var r=0,i=0;i<e.length;i++){var s=e.charCodeAt(i);s<128?n[r++]=s:s<2048?(n[r++]=s>>6|192,n[r++]=63&s|128):o(e,i)?(s=65536+((1023&s)<<10)+(1023&e.charCodeAt(++i)),n[r++]=s>>18|240,n[r++]=s>>12&63|128,n[r++]=s>>6&63|128,n[r++]=63&s|128):(n[r++]=s>>12|224,n[r++]=s>>6&63|128,n[r++]=63&s|128)}else for(i=0;i<e.length;i++)n[i]=0|e[i];return n},t.toHex=function(e){for(var t="",n=0;n<e.length;n++)t+=a(e[n].toString(16));return t},t.htonl=s,t.toHex32=function(e,t){for(var n="",r=0;r<e.length;r++){var i=e[r];"little"===t&&(i=s(i)),n+=u(i.toString(16))}return n},t.zero2=a,t.zero8=u,t.join32=function(e,t,n,i){var o=n-t;r(o%4==0);for(var s=new Array(o/4),a=0,u=t;a<s.length;a++,u+=4){var c;c="big"===i?e[u]<<24|e[u+1]<<16|e[u+2]<<8|e[u+3]:e[u+3]<<24|e[u+2]<<16|e[u+1]<<8|e[u],s[a]=c>>>0}return s},t.split32=function(e,t){for(var n=new Array(4*e.length),r=0,i=0;r<e.length;r++,i+=4){var o=e[r];"big"===t?(n[i]=o>>>24,n[i+1]=o>>>16&255,n[i+2]=o>>>8&255,n[i+3]=255&o):(n[i+3]=o>>>24,n[i+2]=o>>>16&255,n[i+1]=o>>>8&255,n[i]=255&o)}return n},t.rotr32=function(e,t){return e>>>t|e<<32-t},t.rotl32=function(e,t){return e<<t|e>>>32-t},t.sum32=function(e,t){return e+t>>>0},t.sum32_3=function(e,t,n){return e+t+n>>>0},t.sum32_4=function(e,t,n,r){return e+t+n+r>>>0},t.sum32_5=function(e,t,n,r,i){return e+t+n+r+i>>>0},t.sum64=function(e,t,n,r){var i=e[t],o=r+e[t+1]>>>0,s=(o<r?1:0)+n+i;e[t]=s>>>0,e[t+1]=o},t.sum64_hi=function(e,t,n,r){return(t+r>>>0<t?1:0)+e+n>>>0},t.sum64_lo=function(e,t,n,r){return t+r>>>0},t.sum64_4_hi=function(e,t,n,r,i,o,s,a){var u=0,c=t;return u+=(c=c+r>>>0)<t?1:0,u+=(c=c+o>>>0)<o?1:0,e+n+i+s+(u+=(c=c+a>>>0)<a?1:0)>>>0},t.sum64_4_lo=function(e,t,n,r,i,o,s,a){return t+r+o+a>>>0},t.sum64_5_hi=function(e,t,n,r,i,o,s,a,u,c){var f=0,l=t;return f+=(l=l+r>>>0)<t?1:0,f+=(l=l+o>>>0)<o?1:0,f+=(l=l+a>>>0)<a?1:0,e+n+i+s+u+(f+=(l=l+c>>>0)<c?1:0)>>>0},t.sum64_5_lo=function(e,t,n,r,i,o,s,a,u,c){return t+r+o+a+c>>>0},t.rotr64_hi=function(e,t,n){return(t<<32-n|e>>>n)>>>0},t.rotr64_lo=function(e,t,n){return(e<<32-n|t>>>n)>>>0},t.shr64_hi=function(e,t,n){return e>>>n},t.shr64_lo=function(e,t,n){return(e<<32-n|t>>>n)>>>0}},function(e,t,n){"use strict";var r=n(62);t.a=r.a},function(e,t,n){var r=n(223),i="object"==typeof self&&self&&self.Object===Object&&self,o=r||i||Function("return this")();e.exports=o},function(e,t,n){"use strict";const r=":A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD",i="["+r+"][:A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*",o=new RegExp("^"+i+"$");t.isExist=function(e){return void 0!==e},t.isEmptyObject=function(e){return 0===Object.keys(e).length},t.merge=function(e,t,n){if(t){const r=Object.keys(t),i=r.length;for(let o=0;o<i;o++)e[r[o]]="strict"===n?[t[r[o]]]:t[r[o]]}},t.getValue=function(e){return t.isExist(e)?e:""},t.buildOptions=function(e,t,n){var r={};if(!e)return t;for(let i=0;i<n.length;i++)void 0!==e[n[i]]?r[n[i]]=e[n[i]]:r[n[i]]=t[n[i]];return r},t.isTagNameInArrayMode=function(e,t,n){return!1!==t&&(t instanceof RegExp?t.test(e):"function"==typeof t?!!t(e,n):"strict"===t)},t.isName=function(e){const t=o.exec(e);return!(null==t)},t.getAllMatches=function(e,t){const n=[];let r=t.exec(e);for(;r;){const i=[],o=r.length;for(let e=0;e<o;e++)i.push(r[e]);n.push(i),r=t.exec(e)}return n},t.nameRegexp=i},function(e,t,n){"use strict";n.d(t,"a",(function(){return r}));var r=function(e){return encodeURIComponent(e).replace(/[!'()*]/g,i)},i=function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()}},function(e,t,n){var r=n(8).Buffer,i=n(285).Transform,o=n(59).StringDecoder;function s(e){i.call(this),this.hashMode="string"==typeof e,this.hashMode?this[e]=this._finalOrDigest:this.final=this._finalOrDigest,this._final&&(this.__final=this._final,this._final=null),this._decoder=null,this._encoding=null}n(7)(s,i),s.prototype.update=function(e,t,n){"string"==typeof e&&(e=r.from(e,t));var i=this._update(e);return this.hashMode?this:(n&&(i=this._toString(i,n)),i)},s.prototype.setAutoPadding=function(){},s.prototype.getAuthTag=function(){throw new Error("trying to get auth tag in unsupported state")},s.prototype.setAuthTag=function(){throw new Error("trying to set auth tag in unsupported state")},s.prototype.setAAD=function(){throw new Error("trying to set aad in unsupported state")},s.prototype._transform=function(e,t,n){var r;try{this.hashMode?this._update(e):this.push(this._update(e))}catch(e){r=e}finally{n(r)}},s.prototype._flush=function(e){var t;try{this.push(this.__final())}catch(e){t=e}e(t)},s.prototype._finalOrDigest=function(e){var t=this.__final()||r.alloc(0);return e&&(t=this._toString(t,e,!0)),t},s.prototype._toString=function(e,t,n){if(this._decoder||(this._decoder=new o(t),this._encoding=t),this._encoding!==t)throw new Error("can't switch encodings");var r=this._decoder.write(e);return n&&(r+=this._decoder.end()),r},e.exports=s},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},function(e,t,n){"use strict";var r;n.d(t,"a",(function(){return r})),function(e){e.API_KEY="API_KEY",e.AWS_IAM="AWS_IAM",e.OPENID_CONNECT="OPENID_CONNECT",e.AMAZON_COGNITO_USER_POOLS="AMAZON_COGNITO_USER_POOLS"}(r||(r={}))},function(e,t,n){"use strict";var r=n(8).Buffer,i=r.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function o(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(r.isEncoding===i||!i(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=u,this.end=c,t=4;break;case"utf8":this.fillLast=a,t=4;break;case"base64":this.text=f,this.end=l,t=3;break;default:return this.write=d,void(this.end=h)}this.lastNeed=0,this.lastTotal=0,this.lastChar=r.allocUnsafe(t)}function s(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function a(e){var t=this.lastTotal-this.lastNeed,n=function(e,t,n){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if(128!=(192&t[1]))return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&128!=(192&t[2]))return e.lastNeed=2,"�"}}(this,e);return void 0!==n?n:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(e.copy(this.lastChar,t,0,e.length),void(this.lastNeed-=e.length))}function u(e,t){if((e.length-t)%2==0){var n=e.toString("utf16le",t);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function c(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function f(e,t){var n=(e.length-t)%3;return 0===n?e.toString("base64",t):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-n))}function l(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function d(e){return e.toString(this.encoding)}function h(e){return e&&e.length?this.write(e):""}t.StringDecoder=o,o.prototype.write=function(e){if(0===e.length)return"";var t,n;if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n<e.length?t?t+this.text(e,n):this.text(e,n):t||""},o.prototype.end=function(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+"�":t},o.prototype.text=function(e,t){var n=function(e,t,n){var r=t.length-1;if(r<n)return 0;var i=s(t[r]);if(i>=0)return i>0&&(e.lastNeed=i-1),i;if(--r<n||-2===i)return 0;if((i=s(t[r]))>=0)return i>0&&(e.lastNeed=i-2),i;if(--r<n||-2===i)return 0;if((i=s(t[r]))>=0)return i>0&&(2===i?i=0:e.lastNeed=i-3),i;return 0}(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=n;var r=e.length-(n-this.lastNeed);return e.copy(this.lastChar,0,r),e.toString("utf8",t,r)},o.prototype.fillLast=function(e){if(this.lastNeed<=e.length)return e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,e.length),this.lastNeed-=e.length}},function(e,t,n){"use strict";var r=n(92),i=Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t};e.exports=l;var o=Object.create(n(80));o.inherits=n(7);var s=n(171),a=n(120);o.inherits(l,s);for(var u=i(a.prototype),c=0;c<u.length;c++){var f=u[c];l.prototype[f]||(l.prototype[f]=a.prototype[f])}function l(e){if(!(this instanceof l))return new l(e);s.call(this,e),a.call(this,e),e&&!1===e.readable&&(this.readable=!1),e&&!1===e.writable&&(this.writable=!1),this.allowHalfOpen=!0,e&&!1===e.allowHalfOpen&&(this.allowHalfOpen=!1),this.once("end",d)}function d(){this.allowHalfOpen||this._writableState.ended||r.nextTick(h,this)}function h(e){e.end()}Object.defineProperty(l.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),Object.defineProperty(l.prototype,"destroyed",{get:function(){return void 0!==this._readableState&&void 0!==this._writableState&&(this._readableState.destroyed&&this._writableState.destroyed)},set:function(e){void 0!==this._readableState&&void 0!==this._writableState&&(this._readableState.destroyed=e,this._writableState.destroyed=e)}}),l.prototype._destroy=function(e,t){this.push(null),this.end(),r.nextTick(t,e)}},function(e,t){var n=Array.isArray;e.exports=n},function(e,t,n){"use strict";n.d(t,"b",(function(){return _})),n.d(t,"a",(function(){return S}));var r=n(63),i=n(26),o=n(254),s=n(44),a=n(89),u=n(19),c=function(){return(c=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},f=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},l=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},d=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]])}return n},h=new s.a("RestAPI"),p=function(){function e(e){this._api=null,this.Credentials=a.a,this._options=e,h.debug("API Options",this._options)}return e.prototype.getModuleName=function(){return"RestAPI"},e.prototype.configure=function(e){var t=e||{},n=t.API,r=void 0===n?{}:n,i=d(t,["API"]),o=c(c({},i),r);if(h.debug("configure Rest API",{opt:o}),o.aws_project_region){if(o.aws_cloud_logic_custom){var s=o.aws_cloud_logic_custom;o.endpoints="string"==typeof s?JSON.parse(s):s}o=Object.assign({},o,{region:o.aws_project_region,header:{}})}return Array.isArray(o.endpoints)?o.endpoints.forEach((function(e){void 0!==e.custom_header&&"function"!=typeof e.custom_header&&(h.warn("Rest API "+e.name+", custom_header should be a function"),e.custom_header=void 0)})):this._options&&Array.isArray(this._options.endpoints)?o.endpoints=this._options.endpoints:o.endpoints=[],this._options=Object.assign({},this._options,o),this.createInstance(),this._options},e.prototype.createInstance=function(){return h.debug("create Rest API instance"),this._api=new o.a(this._options),this._api.Credentials=this.Credentials,!0},e.prototype.get=function(e,t,n){try{var r=this.getEndpointInfo(e,t),i=this._api.getCancellableToken(),o=Object.assign({},n);o.cancellableToken=i;var s=this._api.get(r,o);return this._api.updateRequestToBeCancellable(s,i),s}catch(e){return Promise.reject(e.message)}},e.prototype.post=function(e,t,n){try{var r=this.getEndpointInfo(e,t),i=this._api.getCancellableToken(),o=Object.assign({},n);o.cancellableToken=i;var s=this._api.post(r,o);return this._api.updateRequestToBeCancellable(s,i),s}catch(e){return Promise.reject(e.message)}},e.prototype.put=function(e,t,n){try{var r=this.getEndpointInfo(e,t),i=this._api.getCancellableToken(),o=Object.assign({},n);o.cancellableToken=i;var s=this._api.put(r,o);return this._api.updateRequestToBeCancellable(s,i),s}catch(e){return Promise.reject(e.message)}},e.prototype.patch=function(e,t,n){try{var r=this.getEndpointInfo(e,t),i=this._api.getCancellableToken(),o=Object.assign({},n);o.cancellableToken=i;var s=this._api.patch(r,o);return this._api.updateRequestToBeCancellable(s,i),s}catch(e){return Promise.reject(e.message)}},e.prototype.del=function(e,t,n){try{var r=this.getEndpointInfo(e,t),i=this._api.getCancellableToken(),o=Object.assign({},n);o.cancellableToken=i;var s=this._api.del(r,o);return this._api.updateRequestToBeCancellable(s,i),s}catch(e){return Promise.reject(e.message)}},e.prototype.head=function(e,t,n){try{var r=this.getEndpointInfo(e,t),i=this._api.getCancellableToken(),o=Object.assign({},n);o.cancellableToken=i;var s=this._api.head(r,o);return this._api.updateRequestToBeCancellable(s,i),s}catch(e){return Promise.reject(e.message)}},e.prototype.isCancel=function(e){return this._api.isCancel(e)},e.prototype.cancel=function(e,t){return this._api.cancel(e,t)},e.prototype.endpoint=function(e){return f(this,void 0,void 0,(function(){return l(this,(function(t){return[2,this._api.endpoint(e)]}))}))},e.prototype.getEndpointInfo=function(e,t){var n=this._options.endpoints;if(!Array.isArray(n))throw new Error("API category not configured");var r=n.find((function(t){return t.name===e}));if(!r)throw new Error("API "+e+" does not exist");var i={endpoint:r.endpoint+t};return"string"==typeof r.region?i.region=r.region:"string"==typeof this._options.region&&(i.region=this._options.region),"string"==typeof r.service?i.service=r.service||"execute-api":i.service="execute-api","function"==typeof r.custom_header?i.custom_header=r.custom_header:i.custom_header=void 0,i},e}(),v=new p(null);u.a.register(v);var g=n(248),m=function(){return(m=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},b=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},y=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},w=new s.a("API"),_=function(){function e(e){this.Auth=r.a,this.Cache=i.a,this.Credentials=a.a,this._options=e,this._restApi=new p(e),this._graphqlApi=new g.a(e),w.debug("API Options",this._options)}return e.prototype.getModuleName=function(){return"API"},e.prototype.configure=function(e){this._options=Object.assign({},this._options,e),this._restApi.Credentials=this.Credentials,this._graphqlApi.Auth=this.Auth,this._graphqlApi.Cache=this.Cache,this._graphqlApi.Credentials=this.Credentials;var t=this._restApi.configure(this._options),n=this._graphqlApi.configure(this._options);return m(m({},t),n)},e.prototype.get=function(e,t,n){return this._restApi.get(e,t,n)},e.prototype.post=function(e,t,n){return this._restApi.post(e,t,n)},e.prototype.put=function(e,t,n){return this._restApi.put(e,t,n)},e.prototype.patch=function(e,t,n){return this._restApi.patch(e,t,n)},e.prototype.del=function(e,t,n){return this._restApi.del(e,t,n)},e.prototype.head=function(e,t,n){return this._restApi.head(e,t,n)},e.prototype.isCancel=function(e){return this._restApi.isCancel(e)},e.prototype.cancel=function(e,t){return this._restApi.cancel(e,t)},e.prototype.endpoint=function(e){return b(this,void 0,void 0,(function(){return y(this,(function(t){return[2,this._restApi.endpoint(e)]}))}))},e.prototype.getGraphqlOperationType=function(e){return this._graphqlApi.getGraphqlOperationType(e)},e.prototype.graphql=function(e,t){return this._graphqlApi.graphql(e,t)},e}(),S=new _(null);u.a.register(S)},function(e,t,n){"use strict";n.d(t,"a",(function(){return q}));var r=n(12),i=n(44),o=n(88),s=n(89),a=n(221),u=n(146),c=n(86),f=n(33);var l,d=n(19),h=n(30),p=n(16),v=function(e){var t=window.open(e,"_self");return t?Promise.resolve(t):Promise.reject()},g=n(87),m=n.n(g),b=n(90),y=n.n(b),w=function(){return(w=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},_=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},S=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},E=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},M="undefined"!=typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("amplify_default"):"@@amplify_default",A=function(e,t,n){o.a.dispatch("auth",{event:e,data:t,message:n},"Auth",M)},I=new i.a("OAuth"),k=function(){function e(e){var t=e.config,n=e.cognitoClientId,r=e.scopes,i=void 0===r?[]:r;if(this._urlOpener=t.urlOpener||v,this._config=t,this._cognitoClientId=n,!this.isValidScopes(i))throw Error("scopes must be a String Array");this._scopes=i}return e.prototype.isValidScopes=function(e){return Array.isArray(e)&&e.every((function(e){return"string"==typeof e}))},e.prototype.oauthSignIn=function(e,t,n,i,o,s){void 0===e&&(e="code"),void 0===o&&(o=r.b.Cognito);var a=this._generateState(32),u=s?a+"-"+s.split("").map((function(e){return e.charCodeAt(0).toString(16).padStart(2,"0")})).join(""):a;!function(e){window.sessionStorage.setItem("oauth_state",e)}(u);var c,f=this._generateRandom(128);c=f,window.sessionStorage.setItem("ouath_pkce_key",c);var l=this._generateChallenge(f),d=this._scopes.join(" "),h="https://"+t+"/oauth2/authorize?"+Object.entries(w(w({redirect_uri:n,response_type:e,client_id:i,identity_provider:o,scope:d,state:u},"code"===e?{code_challenge:l}:{}),"code"===e?{code_challenge_method:"S256"}:{})).map((function(e){var t=E(e,2),n=t[0],r=t[1];return encodeURIComponent(n)+"="+encodeURIComponent(r)})).join("&");I.debug("Redirecting to "+h),this._urlOpener(h,n)},e.prototype._handleCodeFlow=function(e){return _(this,void 0,void 0,(function(){var t,n,i,o,s,a,u,c,f,l,d,h;return S(this,(function(v){switch(v.label){case 0:return(t=(Object(p.parse)(e).query||"").split("&").map((function(e){return e.split("=")})).reduce((function(e,t){var n,r=E(t,2),i=r[0],o=r[1];return w(w({},e),((n={})[i]=o,n))}),{code:void 0}).code)&&Object(p.parse)(e).pathname===Object(p.parse)(this._config.redirectSignIn).pathname?(n="https://"+this._config.domain+"/oauth2/token",A("codeFlow",{},"Retrieving tokens from "+n),i=Object(r.d)(this._config)?this._cognitoClientId:this._config.clientID,o=Object(r.d)(this._config)?this._config.redirectSignIn:this._config.redirectUri,g=window.sessionStorage.getItem("ouath_pkce_key"),window.sessionStorage.removeItem("ouath_pkce_key"),a=w({grant_type:"authorization_code",code:t,client_id:i,redirect_uri:o},(s=g)?{code_verifier:s}:{}),I.debug("Calling token endpoint: "+n+" with",a),u=Object.entries(a).map((function(e){var t=E(e,2),n=t[0],r=t[1];return encodeURIComponent(n)+"="+encodeURIComponent(r)})).join("&"),[4,fetch(n,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:u})]):[2];case 1:return[4,v.sent().json()];case 2:if(c=v.sent(),f=c.access_token,l=c.refresh_token,d=c.id_token,h=c.error)throw new Error(h);return[2,{accessToken:f,refreshToken:l,idToken:d}]}var g}))}))},e.prototype._handleImplicitFlow=function(e){return _(this,void 0,void 0,(function(){var t,n,r;return S(this,(function(i){return t=(Object(p.parse)(e).hash||"#").substr(1).split("&").map((function(e){return e.split("=")})).reduce((function(e,t){var n,r=E(t,2),i=r[0],o=r[1];return w(w({},e),((n={})[i]=o,n))}),{id_token:void 0,access_token:void 0}),n=t.id_token,r=t.access_token,A("implicitFlow",{},"Got tokens from "+e),I.debug("Retrieving implicit tokens from "+e+" with"),[2,{accessToken:r,idToken:n,refreshToken:null}]}))}))},e.prototype.handleAuthResponse=function(e){return _(this,void 0,void 0,(function(){var t,n,r,i,o,s,a;return S(this,(function(u){switch(u.label){case 0:if(u.trys.push([0,5,,6]),t=e?w(w({},(Object(p.parse)(e).hash||"#").substr(1).split("&").map((function(e){return e.split("=")})).reduce((function(e,t){var n=E(t,2),r=n[0],i=n[1];return e[r]=i,e}),{})),(Object(p.parse)(e).query||"").split("&").map((function(e){return e.split("=")})).reduce((function(e,t){var n=E(t,2),r=n[0],i=n[1];return e[r]=i,e}),{})):{},n=t.error,r=t.error_description,n)throw new Error(r);return i=this._validateState(t),I.debug("Starting "+this._config.responseType+" flow with "+e),"code"!==this._config.responseType?[3,2]:(o=[{}],[4,this._handleCodeFlow(e)]);case 1:return[2,w.apply(void 0,[w.apply(void 0,o.concat([u.sent()])),{state:i}])];case 2:return s=[{}],[4,this._handleImplicitFlow(e)];case 3:return[2,w.apply(void 0,[w.apply(void 0,s.concat([u.sent()])),{state:i}])];case 4:return[3,6];case 5:throw a=u.sent(),I.error("Error handling auth response.",a),a;case 6:return[2]}}))}))},e.prototype._validateState=function(e){if(e){var t,n=(t=window.sessionStorage.getItem("oauth_state"),window.sessionStorage.removeItem("oauth_state"),t),r=e.state;if(n&&n!==r)throw new Error("Invalid state in OAuth flow");return r}},e.prototype.signOut=function(){return _(this,void 0,void 0,(function(){var e,t,n;return S(this,(function(i){return e="https://"+this._config.domain+"/logout?",t=Object(r.d)(this._config)?this._cognitoClientId:this._config.oauth.clientID,n=Object(r.d)(this._config)?this._config.redirectSignOut:this._config.returnTo,e+=Object.entries({client_id:t,logout_uri:encodeURIComponent(n)}).map((function(e){var t=E(e,2);return t[0]+"="+t[1]})).join("&"),A("oAuthSignOut",{oAuth:"signOut"},"Signing out from "+e),I.debug("Signing out from "+e),[2,this._urlOpener(e)]}))}))},e.prototype._generateState=function(e){for(var t="",n=e,r="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";n>0;--n)t+=r[Math.round(Math.random()*(r.length-1))];return t},e.prototype._generateChallenge=function(e){return this._base64URL(m()(e))},e.prototype._base64URL=function(e){return e.toString(y.a).replace(/=/g,"").replace(/\+/g,"-").replace(/\//g,"_")},e.prototype._generateRandom=function(e){var t=new Uint8Array(e);if("undefined"!=typeof window&&window.crypto)window.crypto.getRandomValues(t);else for(var n=0;n<e;n+=1)t[n]=Math.random()*"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~".length|0;return this._bufferToString(t)},e.prototype._bufferToString=function(e){for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",n=[],r=0;r<e.byteLength;r+=1){var i=e[r]%t.length;n.push(t[i])}return n.join("")},e}(),O=n(35),x=(l=function(e,t){return(l=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)},function(e,t){function n(){this.constructor=e}l(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),C=new i.a("AuthError"),T=function(e){function t(n){var r=this,i=N[n],o=i.message,s=i.log;return(r=e.call(this,o)||this).constructor=t,Object.setPrototypeOf(r,t.prototype),r.name="AuthError",r.log=s||o,C.error(r.log),r}return x(t,e),t}(Error),P=function(e){function t(n){var r=e.call(this,n)||this;return r.constructor=t,Object.setPrototypeOf(r,t.prototype),r.name="NoUserPoolError",r}return x(t,e),t}(T),N={noConfig:{message:O.a.DEFAULT_MSG,log:"\n Error: Amplify has not been configured correctly.\n This error is typically caused by one of the following scenarios:\n\n 1. Make sure you're passing the awsconfig object to Amplify.configure() in your app's entry point\n See https://aws-amplify.github.io/docs/js/authentication#configure-your-app for more information\n \n 2. There might be multiple conflicting versions of aws-amplify or amplify packages in your node_modules.\n Try deleting your node_modules folder and reinstalling the dependencies with `yarn install`\n "},missingAuthConfig:{message:O.a.DEFAULT_MSG,log:"\n Error: Amplify has not been configured correctly. \n The configuration object is missing required auth properties. \n Did you run `amplify push` after adding auth via `amplify add auth`?\n See https://aws-amplify.github.io/docs/js/authentication#amplify-project-setup for more information\n "},emptyUsername:{message:O.a.EMPTY_USERNAME},invalidUsername:{message:O.a.INVALID_USERNAME},emptyPassword:{message:O.a.EMPTY_PASSWORD},emptyCode:{message:O.a.EMPTY_CODE},signUpError:{message:O.a.SIGN_UP_ERROR,log:"The first parameter should either be non-null string or object"},noMFA:{message:O.a.NO_MFA},invalidMFA:{message:O.a.INVALID_MFA},emptyChallengeResponse:{message:O.a.EMPTY_CHALLENGE},noUserSession:{message:O.a.NO_USER_SESSION},default:{message:O.a.DEFAULT_MSG}};function R(e){return(R="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var L=function(){return(L=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},j=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},D=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},U=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},B=new i.a("AuthClass"),F="undefined"!=typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("amplify_default"):"@@amplify_default",z=function(e,t,n){o.a.dispatch("auth",{event:e,data:t,message:n},"Auth",F)},q=new(function(){function e(e){var t=this;this.userPool=null,this.user=null,this.oAuthFlowInProgress=!1,this.Credentials=s.a,this.wrapRefreshSessionCallback=function(e){return function(t,n){return n?z("tokenRefresh",void 0,"New token retrieved"):z("tokenRefresh_failure",t,"Failed to retrieve new token"),e(t,n)}},this.configure(e),this.currentCredentials=this.currentCredentials.bind(this),this.currentUserCredentials=this.currentUserCredentials.bind(this),o.a.listen("auth",(function(e){switch(e.payload.event){case"signIn":t._storage.setItem("amplify-signin-with-hostedUI","false");break;case"signOut":t._storage.removeItem("amplify-signin-with-hostedUI");break;case"cognitoHostedUI":t._storage.setItem("amplify-signin-with-hostedUI","true")}}))}return e.prototype.getModuleName=function(){return"Auth"},e.prototype.configure=function(e){var t=this;if(!e)return this._config||{};B.debug("configure Auth");var n=Object.assign({},this._config,a.a.parseMobilehubConfig(e).Auth,e);this._config=n;var i=this._config,o=i.userPoolId,s=i.userPoolWebClientId,l=i.cookieStorage,d=i.oauth,p=i.region,v=i.identityPoolId,g=i.mandatorySignIn,m=i.refreshHandlers,b=i.identityPoolRegion,y=i.clientMetadata,w=i.endpoint;if(this._config.storage){if(!this._isValidAuthStorage(this._config.storage))throw B.error("The storage in the Auth config is not valid!"),new Error("Empty storage object");this._storage=this._config.storage}else this._storage=l?new h.i(l):e.ssr?new u.a:(new c.a).getStorage();if(this._storageSync=Promise.resolve(),"function"==typeof this._storage.sync&&(this._storageSync=this._storage.sync()),o){var _={UserPoolId:o,ClientId:s,endpoint:w};_.Storage=this._storage,this.userPool=new h.g(_,this.wrapRefreshSessionCallback)}this.Credentials.configure({mandatorySignIn:g,region:b||p,userPoolId:o,identityPoolId:v,refreshHandlers:m,storage:this._storage});var S=d?Object(r.d)(this._config.oauth)?d:d.awsCognito:void 0;if(S){var E=Object.assign({cognitoClientId:s,UserPoolId:o,domain:S.domain,scopes:S.scope,redirectSignIn:S.redirectSignIn,redirectSignOut:S.redirectSignOut,responseType:S.responseType,Storage:this._storage,urlOpener:S.urlOpener,clientMetadata:y},S.options);this._oAuthHandler=new k({scopes:E.scopes,config:E,cognitoClientId:E.cognitoClientId});var M={};!function(e){if(f.a.browserOrNode().isBrowser&&window.location)e({url:window.location.href});else if(!f.a.browserOrNode().isNode)throw new Error("Not supported")}((function(e){var n=e.url;M[n]||(M[n]=!0,t._handleAuthResponse(n))}))}return z("configured",null,"The Auth category has been configured successfully"),this._config},e.prototype.signUp=function(e){for(var t=this,n=[],i=1;i<arguments.length;i++)n[i-1]=arguments[i];if(!this.userPool)return this.rejectNoUserPool();var o,s=null,a=null,u=[],c=null;if(e&&"string"==typeof e){s=e,a=n?n[0]:null;var f=n?n[1]:null,l=n?n[2]:null;f&&u.push(new h.f({Name:"email",Value:f})),l&&u.push(new h.f({Name:"phone_number",Value:l}))}else{if(!e||"object"!==R(e))return this.rejectAuthError(r.a.SignUpError);s=e.username,a=e.password,e&&e.clientMetadata?o=e.clientMetadata:this._config.clientMetadata&&(o=this._config.clientMetadata);var d=e.attributes;d&&Object.keys(d).map((function(e){u.push(new h.f({Name:e,Value:d[e]}))}));var p=e.validationData;p&&(c=[],Object.keys(p).map((function(e){c.push(new h.f({Name:e,Value:d[e]}))})))}return s?a?(B.debug("signUp attrs:",u),B.debug("signUp validation data:",c),new Promise((function(e,n){t.userPool.signUp(s,a,u,c,(function(t,r){t?(z("signUp_failure",t,s+" failed to signup"),n(t)):(z("signUp",r,s+" has signed up successfully"),e(r))}),o)}))):this.rejectAuthError(r.a.EmptyPassword):this.rejectAuthError(r.a.EmptyUsername)},e.prototype.confirmSignUp=function(e,t,n){if(!this.userPool)return this.rejectNoUserPool();if(!e)return this.rejectAuthError(r.a.EmptyUsername);if(!t)return this.rejectAuthError(r.a.EmptyCode);var i,o=this.createCognitoUser(e),s=!n||"boolean"!=typeof n.forceAliasCreation||n.forceAliasCreation;return n&&n.clientMetadata?i=n.clientMetadata:this._config.clientMetadata&&(i=this._config.clientMetadata),new Promise((function(e,n){o.confirmRegistration(t,s,(function(t,r){t?n(t):e(r)}),i)}))},e.prototype.resendSignUp=function(e,t){if(void 0===t&&(t=this._config.clientMetadata),!this.userPool)return this.rejectNoUserPool();if(!e)return this.rejectAuthError(r.a.EmptyUsername);var n=this.createCognitoUser(e);return new Promise((function(e,r){n.resendConfirmationCode((function(t,n){t?r(t):e(n)}),t)}))},e.prototype.signIn=function(e,t,n){if(void 0===n&&(n=this._config.clientMetadata),!this.userPool)return this.rejectNoUserPool();var i=null,o=null,s={};if("string"==typeof e)i=e,o=t;else{if(!Object(r.g)(e))return this.rejectAuthError(r.a.InvalidUsername);void 0!==t&&B.warn("The password should be defined under the first parameter object!"),i=e.username,o=e.password,s=e.validationData}if(!i)return this.rejectAuthError(r.a.EmptyUsername);var a=new h.a({Username:i,Password:o,ValidationData:s,ClientMetadata:n});return o?this.signInWithPassword(a):this.signInWithoutPassword(a)},e.prototype.authCallbacks=function(e,t,n){var r=this,i=this;return{onSuccess:function(o){return j(r,void 0,void 0,(function(){var r,s,a,u;return D(this,(function(c){switch(c.label){case 0:B.debug(o),delete e.challengeName,delete e.challengeParam,c.label=1;case 1:return c.trys.push([1,4,5,9]),[4,this.Credentials.clear()];case 2:return c.sent(),[4,this.Credentials.set(o,"session")];case 3:return r=c.sent(),B.debug("succeed to get cognito credentials",r),[3,9];case 4:return s=c.sent(),B.debug("cannot get cognito credentials",s),[3,9];case 5:return c.trys.push([5,7,,8]),[4,this.currentUserPoolUser()];case 6:return a=c.sent(),i.user=a,z("signIn",a,"A user "+e.getUsername()+" has been signed in"),t(a),[3,8];case 7:return u=c.sent(),B.error("Failed to get the signed in user",u),n(u),[3,8];case 8:return[7];case 9:return[2]}}))}))},onFailure:function(t){B.debug("signIn failure",t),z("signIn_failure",t,e.getUsername()+" failed to signin"),n(t)},customChallenge:function(n){B.debug("signIn custom challenge answer required"),e.challengeName="CUSTOM_CHALLENGE",e.challengeParam=n,t(e)},mfaRequired:function(n,r){B.debug("signIn MFA required"),e.challengeName=n,e.challengeParam=r,t(e)},mfaSetup:function(n,r){B.debug("signIn mfa setup",n),e.challengeName=n,e.challengeParam=r,t(e)},newPasswordRequired:function(n,r){B.debug("signIn new password"),e.challengeName="NEW_PASSWORD_REQUIRED",e.challengeParam={userAttributes:n,requiredAttributes:r},t(e)},totpRequired:function(n,r){B.debug("signIn totpRequired"),e.challengeName=n,e.challengeParam=r,t(e)},selectMFAType:function(n,r){B.debug("signIn selectMFAType",n),e.challengeName=n,e.challengeParam=r,t(e)}}},e.prototype.signInWithPassword=function(e){var t=this;if(this.pendingSignIn)throw new Error("Pending sign-in attempt already in progress");var n=this.createCognitoUser(e.getUsername());return this.pendingSignIn=new Promise((function(r,i){n.authenticateUser(e,t.authCallbacks(n,(function(e){t.pendingSignIn=null,r(e)}),(function(e){t.pendingSignIn=null,i(e)})))})),this.pendingSignIn},e.prototype.signInWithoutPassword=function(e){var t=this,n=this.createCognitoUser(e.getUsername());return n.setAuthenticationFlowType("CUSTOM_AUTH"),new Promise((function(r,i){n.initiateAuth(e,t.authCallbacks(n,r,i))}))},e.prototype.getMFAOptions=function(e){return new Promise((function(t,n){e.getMFAOptions((function(e,r){if(e)return B.debug("get MFA Options failed",e),void n(e);B.debug("get MFA options success",r),t(r)}))}))},e.prototype.getPreferredMFA=function(e,t){var n=this,r=this;return new Promise((function(i,o){var s=n._config.clientMetadata,a=!!t&&t.bypassCache;e.getUserData((function(e,t){if(e)return B.debug("getting preferred mfa failed",e),void o(e);var n=r._getMfaTypeFromUserData(t);return n?void i(n):void o("invalid MFA Type")}),{bypassCache:a,clientMetadata:s})}))},e.prototype._getMfaTypeFromUserData=function(e){var t=null,n=e.PreferredMfaSetting;if(n)t=n;else{var r=e.UserMFASettingList;if(r)0===r.length?t="NOMFA":B.debug("invalid case for getPreferredMFA",e);else t=e.MFAOptions?"SMS_MFA":"NOMFA"}return t},e.prototype._getUserData=function(e,t){return new Promise((function(n,r){e.getUserData((function(e,t){return e?(B.debug("getting user data failed",e),void r(e)):void n(t)}),t)}))},e.prototype.setPreferredMFA=function(e,t){return j(this,void 0,void 0,(function(){var n,i,o,s,a,u;return D(this,(function(c){switch(c.label){case 0:return n=this._config.clientMetadata,[4,this._getUserData(e,{bypassCache:!0,clientMetadata:n})];case 1:switch(i=c.sent(),o=null,s=null,t){case"TOTP":return[3,2];case"SMS":return[3,3];case"NOMFA":return[3,4]}return[3,6];case 2:return s={PreferredMfa:!0,Enabled:!0},[3,7];case 3:return o={PreferredMfa:!0,Enabled:!0},[3,7];case 4:return a=i.UserMFASettingList,[4,this._getMfaTypeFromUserData(i)];case 5:if("NOMFA"===(u=c.sent()))return[2,Promise.resolve("No change for mfa type")];if("SMS_MFA"===u)o={PreferredMfa:!1,Enabled:!1};else{if("SOFTWARE_TOKEN_MFA"!==u)return[2,this.rejectAuthError(r.a.InvalidMFA)];s={PreferredMfa:!1,Enabled:!1}}return a&&0!==a.length&&a.forEach((function(e){"SMS_MFA"===e?o={PreferredMfa:!1,Enabled:!1}:"SOFTWARE_TOKEN_MFA"===e&&(s={PreferredMfa:!1,Enabled:!1})})),[3,7];case 6:return B.debug("no validmfa method provided"),[2,this.rejectAuthError(r.a.NoMFA)];case 7:return this,[2,new Promise((function(t,r){e.setUserMfaPreference(o,s,(function(i,o){if(i)return B.debug("Set user mfa preference error",i),r(i);B.debug("Set user mfa success",o),B.debug("Caching the latest user data into local"),e.getUserData((function(e,n){return e?(B.debug("getting user data failed",e),r(e)):t(o)}),{bypassCache:!0,clientMetadata:n})}))}))]}}))}))},e.prototype.disableSMS=function(e){return new Promise((function(t,n){e.disableMFA((function(e,r){if(e)return B.debug("disable mfa failed",e),void n(e);B.debug("disable mfa succeed",r),t(r)}))}))},e.prototype.enableSMS=function(e){return new Promise((function(t,n){e.enableMFA((function(e,r){if(e)return B.debug("enable mfa failed",e),void n(e);B.debug("enable mfa succeed",r),t(r)}))}))},e.prototype.setupTOTP=function(e){return new Promise((function(t,n){e.associateSoftwareToken({onFailure:function(e){B.debug("associateSoftwareToken failed",e),n(e)},associateSecretCode:function(e){B.debug("associateSoftwareToken sucess",e),t(e)}})}))},e.prototype.verifyTotpToken=function(e,t){return B.debug("verification totp token",e,t),new Promise((function(n,r){e.verifySoftwareToken(t,"My TOTP device",{onFailure:function(e){B.debug("verifyTotpToken failed",e),r(e)},onSuccess:function(t){z("signIn",e,"A user "+e.getUsername()+" has been signed in"),B.debug("verifyTotpToken success",t),n(t)}})}))},e.prototype.confirmSignIn=function(e,t,n,i){var o=this;if(void 0===i&&(i=this._config.clientMetadata),!t)return this.rejectAuthError(r.a.EmptyCode);var s=this;return new Promise((function(r,a){e.sendMFACode(t,{onSuccess:function(t){return j(o,void 0,void 0,(function(){var n,i;return D(this,(function(o){switch(o.label){case 0:B.debug(t),o.label=1;case 1:return o.trys.push([1,4,5,6]),[4,this.Credentials.clear()];case 2:return o.sent(),[4,this.Credentials.set(t,"session")];case 3:return n=o.sent(),B.debug("succeed to get cognito credentials",n),[3,6];case 4:return i=o.sent(),B.debug("cannot get cognito credentials",i),[3,6];case 5:return s.user=e,z("signIn",e,"A user "+e.getUsername()+" has been signed in"),r(e),[7];case 6:return[2]}}))}))},onFailure:function(e){B.debug("confirm signIn failure",e),a(e)}},n,i)}))},e.prototype.completeNewPassword=function(e,t,n,i){var o=this;if(void 0===n&&(n={}),void 0===i&&(i=this._config.clientMetadata),!t)return this.rejectAuthError(r.a.EmptyPassword);var s=this;return new Promise((function(r,a){e.completeNewPasswordChallenge(t,n,{onSuccess:function(t){return j(o,void 0,void 0,(function(){var n,i;return D(this,(function(o){switch(o.label){case 0:B.debug(t),o.label=1;case 1:return o.trys.push([1,4,5,6]),[4,this.Credentials.clear()];case 2:return o.sent(),[4,this.Credentials.set(t,"session")];case 3:return n=o.sent(),B.debug("succeed to get cognito credentials",n),[3,6];case 4:return i=o.sent(),B.debug("cannot get cognito credentials",i),[3,6];case 5:return s.user=e,z("signIn",e,"A user "+e.getUsername()+" has been signed in"),r(e),[7];case 6:return[2]}}))}))},onFailure:function(e){B.debug("completeNewPassword failure",e),z("completeNewPassword_failure",e,o.user+" failed to complete the new password flow"),a(e)},mfaRequired:function(t,n){B.debug("signIn MFA required"),e.challengeName=t,e.challengeParam=n,r(e)},mfaSetup:function(t,n){B.debug("signIn mfa setup",t),e.challengeName=t,e.challengeParam=n,r(e)},totpRequired:function(t,n){B.debug("signIn mfa setup",t),e.challengeName=t,e.challengeParam=n,r(e)}},i)}))},e.prototype.sendCustomChallengeAnswer=function(e,t,n){var i=this;if(void 0===n&&(n=this._config.clientMetadata),!this.userPool)return this.rejectNoUserPool();if(!t)return this.rejectAuthError(r.a.EmptyChallengeResponse);return new Promise((function(r,o){e.sendCustomChallengeAnswer(t,i.authCallbacks(e,r,o),n)}))},e.prototype.updateUserAttributes=function(e,t,n){void 0===n&&(n=this._config.clientMetadata);var r=[],i=this;return new Promise((function(o,s){i.userSession(e).then((function(i){for(var a in t)if("sub"!==a&&a.indexOf("_verified")<0){var u={Name:a,Value:t[a]};r.push(u)}e.updateAttributes(r,(function(e,t){return e?s(e):o(t)}),n)}))}))},e.prototype.userAttributes=function(e){var t=this;return new Promise((function(n,r){t.userSession(e).then((function(t){e.getUserAttributes((function(e,t){e?r(e):n(t)}))}))}))},e.prototype.verifiedContact=function(e){var t=this;return this.userAttributes(e).then((function(e){var n=t.attributesToObject(e),r={},i={};return n.email&&(n.email_verified?i.email=n.email:r.email=n.email),n.phone_number&&(n.phone_number_verified?i.phone_number=n.phone_number:r.phone_number=n.phone_number),{verified:i,unverified:r}}))},e.prototype.currentUserPoolUser=function(e){var t=this;return this.userPool?new Promise((function(n,r){t._storageSync.then((function(){return j(t,void 0,void 0,(function(){var t,i,s=this;return D(this,(function(a){switch(a.label){case 0:return this.isOAuthInProgress()?(B.debug("OAuth signIn in progress, waiting for resolution..."),[4,new Promise((function(e){var t=setTimeout((function(){B.debug("OAuth signIn in progress timeout"),o.a.remove("auth",n),e()}),1e4);function n(r){var i=r.payload.event;"cognitoHostedUI"!==i&&"cognitoHostedUI_failure"!==i||(B.debug("OAuth signIn resolved: "+i),clearTimeout(t),o.a.remove("auth",n),e())}o.a.listen("auth",n)}))]):[3,2];case 1:a.sent(),a.label=2;case 2:return(t=this.userPool.getCurrentUser())?(i=this._config.clientMetadata,t.getSession((function(i,o){return j(s,void 0,void 0,(function(){var s,a,u,c=this;return D(this,(function(f){switch(f.label){case 0:return i?(B.debug("Failed to get the user session",i),r(i),[2]):(s=!!e&&e.bypassCache)?[4,this.Credentials.clear()]:[3,2];case 1:f.sent(),f.label=2;case 2:return a=this._config.clientMetadata,u=o.getAccessToken().decodePayload().scope,(void 0===u?"":u).split(" ").includes("aws.cognito.signin.user.admin")?(t.getUserData((function(e,i){if(e)return B.debug("getting user data failed",e),void("User is disabled."===e.message||"User does not exist."===e.message||"Access Token has been revoked"===e.message?r(e):n(t));for(var o=i.PreferredMfaSetting||"NOMFA",s=[],a=0;a<i.UserAttributes.length;a++){var u={Name:i.UserAttributes[a].Name,Value:i.UserAttributes[a].Value},f=new h.f(u);s.push(f)}var l=c.attributesToObject(s);return Object.assign(t,{attributes:l,preferredMFA:o}),n(t)}),{bypassCache:s,clientMetadata:a}),[2]):(B.debug("Unable to get the user data because the aws.cognito.signin.user.admin is not in the scopes of the access token"),[2,n(t)])}}))}))}),{clientMetadata:i}),[2]):(B.debug("Failed to get user from user pool"),r("No current user"),[2])}}))}))})).catch((function(e){return B.debug("Failed to sync cache info into memory",e),r(e)}))})):this.rejectNoUserPool()},e.prototype.isOAuthInProgress=function(){return this.oAuthFlowInProgress},e.prototype.currentAuthenticatedUser=function(e){return j(this,void 0,void 0,(function(){var t,n,r,i,o;return D(this,(function(s){switch(s.label){case 0:B.debug("getting current authenticated user"),t=null,s.label=1;case 1:return s.trys.push([1,3,,4]),[4,this._storageSync];case 2:return s.sent(),[3,4];case 3:throw n=s.sent(),B.debug("Failed to sync cache info into memory",n),n;case 4:try{(r=JSON.parse(this._storage.getItem("aws-amplify-federatedInfo")))&&(t=L(L({},r.user),{token:r.token}))}catch(e){B.debug("cannot load federated user from auth storage")}return t?(this.user=t,B.debug("get current authenticated federated user",this.user),[2,this.user]):[3,5];case 5:B.debug("get current authenticated userpool user"),i=null,s.label=6;case 6:return s.trys.push([6,8,,9]),[4,this.currentUserPoolUser(e)];case 7:return i=s.sent(),[3,9];case 8:return"No userPool"===(o=s.sent())&&B.error("Cannot get the current user because the user pool is missing. Please make sure the Auth module is configured with a valid Cognito User Pool ID"),B.debug("The user is not authenticated by the error",o),[2,Promise.reject("The user is not authenticated")];case 9:return this.user=i,[2,this.user]}}))}))},e.prototype.currentSession=function(){var e=this;return B.debug("Getting current session"),this.userPool?new Promise((function(t,n){e.currentUserPoolUser().then((function(r){e.userSession(r).then((function(e){t(e)})).catch((function(e){B.debug("Failed to get the current session",e),n(e)}))})).catch((function(e){B.debug("Failed to get the current user",e),n(e)}))})):Promise.reject()},e.prototype.userSession=function(e){if(!e)return B.debug("the user is null"),this.rejectAuthError(r.a.NoUserSession);var t=this._config.clientMetadata;return new Promise((function(n,r){B.debug("Getting the session from this user:",e),e.getSession((function(t,i){return t?(B.debug("Failed to get the session from user",e),void r(t)):(B.debug("Succeed to get the user session",i),void n(i))}),{clientMetadata:t})}))},e.prototype.currentUserCredentials=function(){return j(this,void 0,void 0,(function(){var e,t,n=this;return D(this,(function(r){switch(r.label){case 0:B.debug("Getting current user credentials"),r.label=1;case 1:return r.trys.push([1,3,,4]),[4,this._storageSync];case 2:return r.sent(),[3,4];case 3:throw e=r.sent(),B.debug("Failed to sync cache info into memory",e),e;case 4:t=null;try{t=JSON.parse(this._storage.getItem("aws-amplify-federatedInfo"))}catch(e){B.debug("failed to get or parse item aws-amplify-federatedInfo",e)}return t?[2,this.Credentials.refreshFederatedToken(t)]:[2,this.currentSession().then((function(e){return B.debug("getting session success",e),n.Credentials.set(e,"session")})).catch((function(e){return B.debug("getting session failed",e),n.Credentials.set(null,"guest")}))]}}))}))},e.prototype.currentCredentials=function(){return B.debug("getting current credentials"),this.Credentials.get()},e.prototype.verifyUserAttribute=function(e,t,n){return void 0===n&&(n=this._config.clientMetadata),new Promise((function(r,i){e.getAttributeVerificationCode(t,{onSuccess:function(){return r()},onFailure:function(e){return i(e)}},n)}))},e.prototype.verifyUserAttributeSubmit=function(e,t,n){return n?new Promise((function(r,i){e.verifyAttribute(t,n,{onSuccess:function(e){r(e)},onFailure:function(e){i(e)}})})):this.rejectAuthError(r.a.EmptyCode)},e.prototype.verifyCurrentUserAttribute=function(e){var t=this;return t.currentUserPoolUser().then((function(n){return t.verifyUserAttribute(n,e)}))},e.prototype.verifyCurrentUserAttributeSubmit=function(e,t){var n=this;return n.currentUserPoolUser().then((function(r){return n.verifyUserAttributeSubmit(r,e,t)}))},e.prototype.cognitoIdentitySignOut=function(e,t){return j(this,void 0,void 0,(function(){var n,r,i=this;return D(this,(function(o){switch(o.label){case 0:return o.trys.push([0,2,,3]),[4,this._storageSync];case 1:return o.sent(),[3,3];case 2:throw n=o.sent(),B.debug("Failed to sync cache info into memory",n),n;case 3:return r=this._oAuthHandler&&"true"===this._storage.getItem("amplify-signin-with-hostedUI"),[2,new Promise((function(n,o){if(e&&e.global){B.debug("user global sign out",t);var s=i._config.clientMetadata;t.getSession((function(e,s){if(e)return B.debug("failed to get the user session",e),o(e);t.globalSignOut({onSuccess:function(e){if(B.debug("global sign out success"),!r)return n();i.oAuthSignOutRedirect(n,o)},onFailure:function(e){return B.debug("global sign out failed",e),o(e)}})}),{clientMetadata:s})}else{if(B.debug("user sign out",t),t.signOut(),!r)return n();i.oAuthSignOutRedirect(n,o)}}))]}}))}))},e.prototype.oAuthSignOutRedirect=function(e,t){f.a.browserOrNode().isBrowser?this.oAuthSignOutRedirectOrReject(t):this.oAuthSignOutAndResolve(e)},e.prototype.oAuthSignOutAndResolve=function(e){this._oAuthHandler.signOut(),e()},e.prototype.oAuthSignOutRedirectOrReject=function(e){this._oAuthHandler.signOut(),setTimeout((function(){return e("Signout timeout fail")}),3e3)},e.prototype.signOut=function(e){return j(this,void 0,void 0,(function(){var t;return D(this,(function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,this.cleanCachedItems()];case 1:return n.sent(),[3,3];case 2:return n.sent(),B.debug("failed to clear cached items"),[3,3];case 3:return this.userPool?(t=this.userPool.getCurrentUser())?[4,this.cognitoIdentitySignOut(e,t)]:[3,5]:[3,7];case 4:return n.sent(),[3,6];case 5:B.debug("no current Cognito user"),n.label=6;case 6:return[3,8];case 7:B.debug("no Congito User pool"),n.label=8;case 8:return z("signOut",this.user,"A user has been signed out"),this.user=null,[2]}}))}))},e.prototype.cleanCachedItems=function(){return j(this,void 0,void 0,(function(){return D(this,(function(e){switch(e.label){case 0:return[4,this.Credentials.clear()];case 1:return e.sent(),[2]}}))}))},e.prototype.changePassword=function(e,t,n,r){var i=this;return void 0===r&&(r=this._config.clientMetadata),new Promise((function(o,s){i.userSession(e).then((function(i){e.changePassword(t,n,(function(e,t){return e?(B.debug("change password failure",e),s(e)):o(t)}),r)}))}))},e.prototype.forgotPassword=function(e,t){if(void 0===t&&(t=this._config.clientMetadata),!this.userPool)return this.rejectNoUserPool();if(!e)return this.rejectAuthError(r.a.EmptyUsername);var n=this.createCognitoUser(e);return new Promise((function(r,i){n.forgotPassword({onSuccess:function(){r()},onFailure:function(t){B.debug("forgot password failure",t),z("forgotPassword_failure",t,e+" forgotPassword failed"),i(t)},inputVerificationCode:function(t){z("forgotPassword",n,e+" has initiated forgot password flow"),r(t)}},t)}))},e.prototype.forgotPasswordSubmit=function(e,t,n,i){if(void 0===i&&(i=this._config.clientMetadata),!this.userPool)return this.rejectNoUserPool();if(!e)return this.rejectAuthError(r.a.EmptyUsername);if(!t)return this.rejectAuthError(r.a.EmptyCode);if(!n)return this.rejectAuthError(r.a.EmptyPassword);var o=this.createCognitoUser(e);return new Promise((function(r,s){o.confirmPassword(t,n,{onSuccess:function(){z("forgotPasswordSubmit",o,e+" forgotPasswordSubmit successful"),r()},onFailure:function(t){z("forgotPasswordSubmit_failure",t,e+" forgotPasswordSubmit failed"),s(t)}},i)}))},e.prototype.currentUserInfo=function(){return j(this,void 0,void 0,(function(){var e,t,n,r,i,o,s;return D(this,(function(a){switch(a.label){case 0:return(e=this.Credentials.getCredSource())&&"aws"!==e&&"userPool"!==e?[3,9]:[4,this.currentUserPoolUser().catch((function(e){return B.debug(e)}))];case 1:if(!(s=a.sent()))return[2,null];a.label=2;case 2:return a.trys.push([2,8,,9]),[4,this.userAttributes(s)];case 3:t=a.sent(),n=this.attributesToObject(t),r=null,a.label=4;case 4:return a.trys.push([4,6,,7]),[4,this.currentCredentials()];case 5:return r=a.sent(),[3,7];case 6:return i=a.sent(),B.debug("Failed to retrieve credentials while getting current user info",i),[3,7];case 7:return[2,{id:r?r.identityId:void 0,username:s.getUsername(),attributes:n}];case 8:return o=a.sent(),B.debug("currentUserInfo error",o),[2,{}];case 9:return"federated"===e?[2,(s=this.user)||{}]:[2]}}))}))},e.prototype.federatedSignIn=function(e,t,n){return j(this,void 0,void 0,(function(){var i,o,s,a,u,c,f,l,d,h,p;return D(this,(function(v){switch(v.label){case 0:if(!this._config.identityPoolId&&!this._config.userPoolId)throw new Error("Federation requires either a User Pool or Identity Pool in config");if(void 0===e&&this._config.identityPoolId&&!this._config.userPoolId)throw new Error("Federation with Identity Pools requires tokens passed as arguments");return Object(r.e)(e)||Object(r.f)(e)||Object(r.c)(e)||void 0===e?(i=e||{provider:r.b.Cognito},u=Object(r.e)(i)?i.provider:i.customProvider,Object(r.e)(i),o=i.customState,this._config.userPoolId&&(s=Object(r.d)(this._config.oauth)?this._config.userPoolWebClientId:this._config.oauth.clientID,a=Object(r.d)(this._config.oauth)?this._config.oauth.redirectSignIn:this._config.oauth.redirectUri,this._oAuthHandler.oauthSignIn(this._config.oauth.responseType,this._config.oauth.domain,a,s,u,o)),[3,4]):[3,1];case 1:u=e;try{(c=JSON.stringify(JSON.parse(this._storage.getItem("aws-amplify-federatedInfo")).user))&&B.warn("There is already a signed in user: "+c+" in your app.\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tYou should not call Auth.federatedSignIn method again as it may cause unexpected behavior.")}catch(e){}return f=t.token,l=t.identity_id,d=t.expires_at,[4,this.Credentials.set({provider:u,token:f,identity_id:l,user:n,expires_at:d},"federation")];case 2:return h=v.sent(),[4,this.currentAuthenticatedUser()];case 3:return p=v.sent(),z("signIn",p,"A user "+p.username+" has been signed in"),B.debug("federated sign in credentials",h),[2,h];case 4:return[2]}}))}))},e.prototype._handleAuthResponse=function(e){return j(this,void 0,void 0,(function(){var t,n,r,i,o,s,a,u,c,l,d,v,g,m;return D(this,(function(b){switch(b.label){case 0:if(this.oAuthFlowInProgress)return B.debug("Skipping URL "+e+" current flow in progress"),[2];b.label=1;case 1:if(b.trys.push([1,,8,9]),this.oAuthFlowInProgress=!0,!this._config.userPoolId)throw new Error("OAuth responses require a User Pool defined in config");if(z("parsingCallbackUrl",{url:e},"The callback url is being parsed"),t=e||(f.a.browserOrNode().isBrowser?window.location.href:""),n=!!(Object(p.parse)(t).query||"").split("&").map((function(e){return e.split("=")})).find((function(e){var t=U(e,1)[0];return"code"===t||"error"===t})),r=!!(Object(p.parse)(t).hash||"#").substr(1).split("&").map((function(e){return e.split("=")})).find((function(e){var t=U(e,1)[0];return"access_token"===t||"error"===t})),!n&&!r)return[3,7];this._storage.setItem("amplify-redirected-from-hosted-ui","true"),b.label=2;case 2:return b.trys.push([2,6,,7]),[4,this._oAuthHandler.handleAuthResponse(t)];case 3:return i=b.sent(),o=i.accessToken,s=i.idToken,a=i.refreshToken,u=i.state,c=new h.h({IdToken:new h.c({IdToken:s}),RefreshToken:new h.d({RefreshToken:a}),AccessToken:new h.b({AccessToken:o})}),l=void 0,this._config.identityPoolId?[4,this.Credentials.set(c,"session")]:[3,5];case 4:l=b.sent(),B.debug("AWS credentials",l),b.label=5;case 5:return d=/-/.test(u),(v=this.createCognitoUser(c.getIdToken().decodePayload()["cognito:username"])).setSignInUserSession(c),window&&void 0!==window.history&&window.history.replaceState({},null,this._config.oauth.redirectSignIn),z("signIn",v,"A user "+v.getUsername()+" has been signed in"),z("cognitoHostedUI",v,"A user "+v.getUsername()+" has been signed in via Cognito Hosted UI"),d&&(g=u.split("-").splice(1).join("-"),z("customOAuthState",g.match(/.{2}/g).map((function(e){return String.fromCharCode(parseInt(e,16))})).join(""),"State for user "+v.getUsername())),[2,l];case 6:return m=b.sent(),B.debug("Error in cognito hosted auth response",m),z("signIn_failure",m,"The OAuth response flow failed"),z("cognitoHostedUI_failure",m,"A failure occurred when returning to the Cognito Hosted UI"),z("customState_failure",m,"A failure occurred when returning state"),[3,7];case 7:return[3,9];case 8:return this.oAuthFlowInProgress=!1,[7];case 9:return[2]}}))}))},e.prototype.essentialCredentials=function(e){return{accessKeyId:e.accessKeyId,sessionToken:e.sessionToken,secretAccessKey:e.secretAccessKey,identityId:e.identityId,authenticated:e.authenticated}},e.prototype.attributesToObject=function(e){var t={};return e&&e.map((function(e){"email_verified"===e.Name||"phone_number_verified"===e.Name?t[e.Name]="true"===e.Value||!0===e.Value:t[e.Name]=e.Value})),t},e.prototype.createCognitoUser=function(e){var t={Username:e,Pool:this.userPool};t.Storage=this._storage;var n=this._config.authenticationFlowType,r=new h.e(t);return n&&r.setAuthenticationFlowType(n),r},e.prototype._isValidAuthStorage=function(e){return!!e&&"function"==typeof e.getItem&&"function"==typeof e.setItem&&"function"==typeof e.removeItem&&"function"==typeof e.clear},e.prototype.noUserPoolErrorHandler=function(e){return!e||e.userPoolId&&e.identityPoolId?r.a.NoConfig:r.a.MissingAuthConfig},e.prototype.rejectAuthError=function(e){return Promise.reject(new T(e))},e.prototype.rejectNoUserPool=function(){var e=this.noUserPoolErrorHandler(this._config);return Promise.reject(new P(e))},e}())(null);d.a.register(q)},function(e,t,n){e.exports=n(465)},function(e,t,n){var r,i; -/*! - * JavaScript Cookie v2.2.1 - * https://github.com/js-cookie/js-cookie - * - * Copyright 2006, 2015 Klaus Hartl & Fagner Brack - * Released under the MIT license - */!function(o){if(void 0===(i="function"==typeof(r=o)?r.call(t,n,t,e):r)||(e.exports=i),!0,e.exports=o(),!!0){var s=window.Cookies,a=window.Cookies=o();a.noConflict=function(){return window.Cookies=s,a}}}((function(){function e(){for(var e=0,t={};e<arguments.length;e++){var n=arguments[e];for(var r in n)t[r]=n[r]}return t}function t(e){return e.replace(/(%[0-9A-Z]{2})+/g,decodeURIComponent)}return function n(r){function i(){}function o(t,n,o){if("undefined"!=typeof document){"number"==typeof(o=e({path:"/"},i.defaults,o)).expires&&(o.expires=new Date(1*new Date+864e5*o.expires)),o.expires=o.expires?o.expires.toUTCString():"";try{var s=JSON.stringify(n);/^[\{\[]/.test(s)&&(n=s)}catch(e){}n=r.write?r.write(n,t):encodeURIComponent(String(n)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),t=encodeURIComponent(String(t)).replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent).replace(/[\(\)]/g,escape);var a="";for(var u in o)o[u]&&(a+="; "+u,!0!==o[u]&&(a+="="+o[u].split(";")[0]));return document.cookie=t+"="+n+a}}function s(e,n){if("undefined"!=typeof document){for(var i={},o=document.cookie?document.cookie.split("; "):[],s=0;s<o.length;s++){var a=o[s].split("="),u=a.slice(1).join("=");n||'"'!==u.charAt(0)||(u=u.slice(1,-1));try{var c=t(a[0]);if(u=(r.read||r)(u,c)||t(u),n)try{u=JSON.parse(u)}catch(e){}if(i[c]=u,e===c)break}catch(e){}}return e?i[e]:i}}return i.set=o,i.get=function(e){return s(e,!1)},i.getJSON=function(e){return s(e,!0)},i.remove=function(t,n){o(t,"",e(n,{expires:-1}))},i.defaults={},i.withConverter=n,i}((function(){}))}))},function(e,t,n){"use strict";(function(t,r){var i=n(8).Buffer,o=t.crypto||t.msCrypto;o&&o.getRandomValues?e.exports=function(e,t){if(e>4294967295)throw new RangeError("requested too many random bytes");var n=i.allocUnsafe(e);if(e>0)if(e>65536)for(var s=0;s<e;s+=65536)o.getRandomValues(n.slice(s,s+65536));else o.getRandomValues(n);if("function"==typeof t)return r.nextTick((function(){t(null,n)}));return n}:e.exports=function(){throw new Error("Secure random number generation is not supported by this browser.\nUse Chrome, Firefox or Internet Explorer 11")}}).call(this,n(31),n(20))},function(e,t,n){"use strict";var r={};function i(e,t,n){n||(n=Error);var i=function(e){var n,r;function i(n,r,i){return e.call(this,function(e,n,r){return"string"==typeof t?t:t(e,n,r)}(n,r,i))||this}return r=e,(n=i).prototype=Object.create(r.prototype),n.prototype.constructor=n,n.__proto__=r,i}(n);i.prototype.name=n.name,i.prototype.code=e,r[e]=i}function o(e,t){if(Array.isArray(e)){var n=e.length;return e=e.map((function(e){return String(e)})),n>2?"one of ".concat(t," ").concat(e.slice(0,n-1).join(", "),", or ")+e[n-1]:2===n?"one of ".concat(t," ").concat(e[0]," or ").concat(e[1]):"of ".concat(t," ").concat(e[0])}return"of ".concat(t," ").concat(String(e))}i("ERR_INVALID_OPT_VALUE",(function(e,t){return'The value "'+t+'" is invalid for option "'+e+'"'}),TypeError),i("ERR_INVALID_ARG_TYPE",(function(e,t,n){var r,i,s,a;if("string"==typeof t&&(i="not ",t.substr(!s||s<0?0:+s,i.length)===i)?(r="must not be",t=t.replace(/^not /,"")):r="must be",function(e,t,n){return(void 0===n||n>e.length)&&(n=e.length),e.substring(n-t.length,n)===t}(e," argument"))a="The ".concat(e," ").concat(r," ").concat(o(t,"type"));else{var u=function(e,t,n){return"number"!=typeof n&&(n=0),!(n+t.length>e.length)&&-1!==e.indexOf(t,n)}(e,".")?"property":"argument";a='The "'.concat(e,'" ').concat(u," ").concat(r," ").concat(o(t,"type"))}return a+=". Received type ".concat(typeof n)}),TypeError),i("ERR_STREAM_PUSH_AFTER_EOF","stream.push() after EOF"),i("ERR_METHOD_NOT_IMPLEMENTED",(function(e){return"The "+e+" method is not implemented"})),i("ERR_STREAM_PREMATURE_CLOSE","Premature close"),i("ERR_STREAM_DESTROYED",(function(e){return"Cannot call "+e+" after a stream was destroyed"})),i("ERR_MULTIPLE_CALLBACK","Callback called multiple times"),i("ERR_STREAM_CANNOT_PIPE","Cannot pipe, not readable"),i("ERR_STREAM_WRITE_AFTER_END","write after end"),i("ERR_STREAM_NULL_VALUES","May not write null values to stream",TypeError),i("ERR_UNKNOWN_ENCODING",(function(e){return"Unknown encoding: "+e}),TypeError),i("ERR_STREAM_UNSHIFT_AFTER_END_EVENT","stream.unshift() after end event"),e.exports.codes=r},function(e,t,n){"use strict";(function(t){var r=Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t};e.exports=c;var i=n(163),o=n(167);n(7)(c,i);for(var s=r(o.prototype),a=0;a<s.length;a++){var u=s[a];c.prototype[u]||(c.prototype[u]=o.prototype[u])}function c(e){if(!(this instanceof c))return new c(e);i.call(this,e),o.call(this,e),this.allowHalfOpen=!0,e&&(!1===e.readable&&(this.readable=!1),!1===e.writable&&(this.writable=!1),!1===e.allowHalfOpen&&(this.allowHalfOpen=!1,this.once("end",f)))}function f(){this._writableState.ended||t.nextTick(l,this)}function l(e){e.end()}Object.defineProperty(c.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),Object.defineProperty(c.prototype,"writableBuffer",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}}),Object.defineProperty(c.prototype,"writableLength",{enumerable:!1,get:function(){return this._writableState.length}}),Object.defineProperty(c.prototype,"destroyed",{enumerable:!1,get:function(){return void 0!==this._readableState&&void 0!==this._writableState&&(this._readableState.destroyed&&this._writableState.destroyed)},set:function(e){void 0!==this._readableState&&void 0!==this._writableState&&(this._readableState.destroyed=e,this._writableState.destroyed=e)}})}).call(this,n(20))},function(e,t,n){var r=n(8).Buffer;function i(e,t){this._block=r.alloc(e),this._finalSize=t,this._blockSize=e,this._len=0}i.prototype.update=function(e,t){"string"==typeof e&&(t=t||"utf8",e=r.from(e,t));for(var n=this._block,i=this._blockSize,o=e.length,s=this._len,a=0;a<o;){for(var u=s%i,c=Math.min(o-a,i-u),f=0;f<c;f++)n[u+f]=e[a+f];a+=c,(s+=c)%i==0&&this._update(n)}return this._len+=o,this},i.prototype.digest=function(e){var t=this._len%this._blockSize;this._block[t]=128,this._block.fill(0,t+1),t>=this._finalSize&&(this._update(this._block),this._block.fill(0));var n=8*this._len;if(n<=4294967295)this._block.writeUInt32BE(n,this._blockSize-4);else{var r=(4294967295&n)>>>0,i=(n-r)/4294967296;this._block.writeUInt32BE(i,this._blockSize-8),this._block.writeUInt32BE(r,this._blockSize-4)}this._update(this._block);var o=this._hash();return e?o.toString(e):o},i.prototype._update=function(){throw new Error("_update must be implemented by subclass")},e.exports=i},function(e,t,n){"use strict";var r={};function i(e,t,n){n||(n=Error);var i=function(e){var n,r;function i(n,r,i){return e.call(this,function(e,n,r){return"string"==typeof t?t:t(e,n,r)}(n,r,i))||this}return r=e,(n=i).prototype=Object.create(r.prototype),n.prototype.constructor=n,n.__proto__=r,i}(n);i.prototype.name=n.name,i.prototype.code=e,r[e]=i}function o(e,t){if(Array.isArray(e)){var n=e.length;return e=e.map((function(e){return String(e)})),n>2?"one of ".concat(t," ").concat(e.slice(0,n-1).join(", "),", or ")+e[n-1]:2===n?"one of ".concat(t," ").concat(e[0]," or ").concat(e[1]):"of ".concat(t," ").concat(e[0])}return"of ".concat(t," ").concat(String(e))}i("ERR_INVALID_OPT_VALUE",(function(e,t){return'The value "'+t+'" is invalid for option "'+e+'"'}),TypeError),i("ERR_INVALID_ARG_TYPE",(function(e,t,n){var r,i,s,a;if("string"==typeof t&&(i="not ",t.substr(!s||s<0?0:+s,i.length)===i)?(r="must not be",t=t.replace(/^not /,"")):r="must be",function(e,t,n){return(void 0===n||n>e.length)&&(n=e.length),e.substring(n-t.length,n)===t}(e," argument"))a="The ".concat(e," ").concat(r," ").concat(o(t,"type"));else{var u=function(e,t,n){return"number"!=typeof n&&(n=0),!(n+t.length>e.length)&&-1!==e.indexOf(t,n)}(e,".")?"property":"argument";a='The "'.concat(e,'" ').concat(u," ").concat(r," ").concat(o(t,"type"))}return a+=". Received type ".concat(typeof n)}),TypeError),i("ERR_STREAM_PUSH_AFTER_EOF","stream.push() after EOF"),i("ERR_METHOD_NOT_IMPLEMENTED",(function(e){return"The "+e+" method is not implemented"})),i("ERR_STREAM_PREMATURE_CLOSE","Premature close"),i("ERR_STREAM_DESTROYED",(function(e){return"Cannot call "+e+" after a stream was destroyed"})),i("ERR_MULTIPLE_CALLBACK","Callback called multiple times"),i("ERR_STREAM_CANNOT_PIPE","Cannot pipe, not readable"),i("ERR_STREAM_WRITE_AFTER_END","write after end"),i("ERR_STREAM_NULL_VALUES","May not write null values to stream",TypeError),i("ERR_UNKNOWN_ENCODING",(function(e){return"Unknown encoding: "+e}),TypeError),i("ERR_STREAM_UNSHIFT_AFTER_END_EVENT","stream.unshift() after end event"),e.exports.codes=r},function(e,t,n){"use strict";(function(t){var r=Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t};e.exports=c;var i=n(192),o=n(196);n(7)(c,i);for(var s=r(o.prototype),a=0;a<s.length;a++){var u=s[a];c.prototype[u]||(c.prototype[u]=o.prototype[u])}function c(e){if(!(this instanceof c))return new c(e);i.call(this,e),o.call(this,e),this.allowHalfOpen=!0,e&&(!1===e.readable&&(this.readable=!1),!1===e.writable&&(this.writable=!1),!1===e.allowHalfOpen&&(this.allowHalfOpen=!1,this.once("end",f)))}function f(){this._writableState.ended||t.nextTick(l,this)}function l(e){e.end()}Object.defineProperty(c.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),Object.defineProperty(c.prototype,"writableBuffer",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}}),Object.defineProperty(c.prototype,"writableLength",{enumerable:!1,get:function(){return this._writableState.length}}),Object.defineProperty(c.prototype,"destroyed",{enumerable:!1,get:function(){return void 0!==this._readableState&&void 0!==this._writableState&&(this._readableState.destroyed&&this._writableState.destroyed)},set:function(e){void 0!==this._readableState&&void 0!==this._writableState&&(this._readableState.destroyed=e,this._writableState.destroyed=e)}})}).call(this,n(20))},function(e,t,n){var r=n(398),i=n(401);e.exports=function(e,t){var n=i(e,t);return r(n)?n:void 0}},function(e,t,n){"use strict";n.d(t,"b",(function(){return g})),n.d(t,"a",(function(){return m}));var r=n(44),i=n(33),o=n(514),s=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},a=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},u=new r.a("CognitoCredentials"),c=new Promise((function(e,t){return Object(i.b)().isBrowser?(window.gapi&&window.gapi.auth2?window.gapi.auth2:null)?(u.debug("google api already loaded"),e()):void setTimeout((function(){return e()}),2e3):(u.debug("not in the browser, directly resolved"),e())})),f=function(){function e(){this.initialized=!1,this.refreshGoogleToken=this.refreshGoogleToken.bind(this),this._refreshGoogleTokenImpl=this._refreshGoogleTokenImpl.bind(this)}return e.prototype.refreshGoogleToken=function(){return s(this,void 0,void 0,(function(){return a(this,(function(e){switch(e.label){case 0:return this.initialized?[3,2]:(u.debug("need to wait for the Google SDK loaded"),[4,c]);case 1:e.sent(),this.initialized=!0,u.debug("finish waiting"),e.label=2;case 2:return[2,this._refreshGoogleTokenImpl()]}}))}))},e.prototype._refreshGoogleTokenImpl=function(){var e=null;return Object(i.b)().isBrowser&&(e=window.gapi&&window.gapi.auth2?window.gapi.auth2:null),e?new Promise((function(t,n){e.getAuthInstance().then((function(e){e||(u.debug("google Auth undefined"),n(new o.a("google Auth undefined")));var r=e.currentUser.get();r.isSignedIn()?(u.debug("refreshing the google access token"),r.reloadAuthResponse().then((function(e){var n=e.id_token,r=e.expires_at;t({token:n,expires_at:r})})).catch((function(e){e&&"network_error"===e.error?n("Network error reloading google auth response"):n(new o.a("Failed to reload google auth response"))}))):n(new o.a("User is not signed in with Google"))})).catch((function(e){u.debug("Failed to refresh google token",e),n(new o.a("Failed to refresh google token"))}))})):(u.debug("no gapi auth2 available"),Promise.reject("no gapi auth2 available"))},e}(),l=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},d=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},h=new r.a("CognitoCredentials"),p=new Promise((function(e,t){return Object(i.b)().isBrowser?window.FB?(h.debug("FB SDK already loaded"),e()):void setTimeout((function(){return e()}),2e3):(h.debug("not in the browser, directly resolved"),e())})),v=function(){function e(){this.initialized=!1,this.refreshFacebookToken=this.refreshFacebookToken.bind(this),this._refreshFacebookTokenImpl=this._refreshFacebookTokenImpl.bind(this)}return e.prototype.refreshFacebookToken=function(){return l(this,void 0,void 0,(function(){return d(this,(function(e){switch(e.label){case 0:return this.initialized?[3,2]:(h.debug("need to wait for the Facebook SDK loaded"),[4,p]);case 1:e.sent(),this.initialized=!0,h.debug("finish waiting"),e.label=2;case 2:return[2,this._refreshFacebookTokenImpl()]}}))}))},e.prototype._refreshFacebookTokenImpl=function(){var e=null;if(Object(i.b)().isBrowser&&(e=window.FB),!e){return h.debug("no fb sdk available"),Promise.reject(new o.a("no fb sdk available"))}return new Promise((function(t,n){e.getLoginStatus((function(e){if(e&&e.authResponse){var r=e.authResponse,i=r.accessToken,s=1e3*r.expiresIn+(new Date).getTime();if(!i){a="the jwtToken is undefined";h.debug(a),n(new o.a(a))}t({token:i,expires_at:s})}else{var a="no response from facebook when refreshing the jwt token";h.debug(a),n(new o.a(a))}}),{scope:"public_profile,email"})}))},e}(),g=new f,m=new v},function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));var r=n(1),i=n(55);function o(e){var t,n,o=[];try{for(var s=Object(r.__values)(Object.keys(e).sort()),a=s.next();!a.done;a=s.next()){var u=a.value,c=e[u];if(u=Object(i.a)(u),Array.isArray(c))for(var f=0,l=c.length;f<l;f++)o.push(u+"="+Object(i.a)(c[f]));else{var d=u;(c||"string"==typeof c)&&(d+="="+Object(i.a)(c)),o.push(d)}}}catch(e){t={error:e}}finally{try{a&&!a.done&&(n=s.return)&&n.call(s)}finally{if(t)throw t.error}}return o.join("&")}},function(e,t,n){var r;e.exports=(r=n(32),n(87),n(271),r.HmacSHA256)},function(e,t,n){"use strict"; -/*! - * cookie - * Copyright(c) 2012-2014 Roman Shtylman - * Copyright(c) 2015 Douglas Christopher Wilson - * MIT Licensed - */t.parse=function(e,t){if("string"!=typeof e)throw new TypeError("argument str must be a string");for(var n={},i=t||{},s=e.split(o),u=i.decode||r,c=0;c<s.length;c++){var f=s[c],l=f.indexOf("=");if(!(l<0)){var d=f.substr(0,l).trim(),h=f.substr(++l,f.length).trim();'"'==h[0]&&(h=h.slice(1,-1)),null==n[d]&&(n[d]=a(h,u))}}return n},t.serialize=function(e,t,n){var r=n||{},o=r.encode||i;if("function"!=typeof o)throw new TypeError("option encode is invalid");if(!s.test(e))throw new TypeError("argument name is invalid");var a=o(t);if(a&&!s.test(a))throw new TypeError("argument val is invalid");var u=e+"="+a;if(null!=r.maxAge){var c=r.maxAge-0;if(isNaN(c)||!isFinite(c))throw new TypeError("option maxAge is invalid");u+="; Max-Age="+Math.floor(c)}if(r.domain){if(!s.test(r.domain))throw new TypeError("option domain is invalid");u+="; Domain="+r.domain}if(r.path){if(!s.test(r.path))throw new TypeError("option path is invalid");u+="; Path="+r.path}if(r.expires){if("function"!=typeof r.expires.toUTCString)throw new TypeError("option expires is invalid");u+="; Expires="+r.expires.toUTCString()}r.httpOnly&&(u+="; HttpOnly");r.secure&&(u+="; Secure");if(r.sameSite){switch("string"==typeof r.sameSite?r.sameSite.toLowerCase():r.sameSite){case!0:u+="; SameSite=Strict";break;case"lax":u+="; SameSite=Lax";break;case"strict":u+="; SameSite=Strict";break;case"none":u+="; SameSite=None";break;default:throw new TypeError("option sameSite is invalid")}}return u};var r=decodeURIComponent,i=encodeURIComponent,o=/; */,s=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function a(e,t){try{return t(e)}catch(t){return e}}},function(e,t,n){"use strict";n.d(t,"a",(function(){return i}));var r=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},i={clockOffset:0,getDateWithClockOffset:function(){return i.clockOffset?new Date((new Date).getTime()+i.clockOffset):new Date},getClockOffset:function(){return i.clockOffset},getHeaderStringFromDate:function(e){return void 0===e&&(e=i.getDateWithClockOffset()),e.toISOString().replace(/[:\-]|\.\d{3}/g,"")},getDateFromHeaderString:function(e){var t=r(e.match(/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2}).+/),7),n=t[1],i=t[2],o=t[3],s=t[4],a=t[5],u=t[6];return new Date(Date.UTC(Number(n),Number(i)-1,Number(o),Number(s),Number(a),Number(u)))},isClockSkewed:function(e){return Math.abs(e.getTime()-i.getDateWithClockOffset().getTime())>=3e5},isClockSkewError:function(e){if(!e.response||!e.response.headers)return!1;var t=e.response.headers;return Boolean("BadRequestException"===t["x-amzn-errortype"]&&(t.date||t.Date))},setClockOffset:function(e){i.clockOffset=e}}},,function(e,t,n){"use strict";var r=n(7),i=n(113),o=n(116),s=n(117),a=n(56);function u(e){a.call(this,"digest"),this._hash=e}r(u,a),u.prototype._update=function(e){this._hash.update(e)},u.prototype._final=function(){return this._hash.digest()},e.exports=function(e){return"md5"===(e=e.toLowerCase())?new i:"rmd160"===e||"ripemd160"===e?new o:new u(s(e))}},function(e,t,n){(function(e){function n(e){return Object.prototype.toString.call(e)}t.isArray=function(e){return Array.isArray?Array.isArray(e):"[object Array]"===n(e)},t.isBoolean=function(e){return"boolean"==typeof e},t.isNull=function(e){return null===e},t.isNullOrUndefined=function(e){return null==e},t.isNumber=function(e){return"number"==typeof e},t.isString=function(e){return"string"==typeof e},t.isSymbol=function(e){return"symbol"==typeof e},t.isUndefined=function(e){return void 0===e},t.isRegExp=function(e){return"[object RegExp]"===n(e)},t.isObject=function(e){return"object"==typeof e&&null!==e},t.isDate=function(e){return"[object Date]"===n(e)},t.isError=function(e){return"[object Error]"===n(e)||e instanceof Error},t.isFunction=function(e){return"function"==typeof e},t.isPrimitive=function(e){return null===e||"boolean"==typeof e||"number"==typeof e||"string"==typeof e||"symbol"==typeof e||void 0===e},t.isBuffer=e.isBuffer}).call(this,n(6).Buffer)},function(e,t,n){(function(t){e.exports=function(e,n){for(var r=Math.min(e.length,n.length),i=new t(r),o=0;o<r;++o)i[o]=e[o]^n[o];return i}}).call(this,n(6).Buffer)},function(e,t,n){"use strict";var r=n(51),i=n(46);function o(){this.pending=null,this.pendingTotal=0,this.blockSize=this.constructor.blockSize,this.outSize=this.constructor.outSize,this.hmacStrength=this.constructor.hmacStrength,this.padLength=this.constructor.padLength/8,this.endian="big",this._delta8=this.blockSize/8,this._delta32=this.blockSize/32}t.BlockHash=o,o.prototype.update=function(e,t){if(e=r.toArray(e,t),this.pending?this.pending=this.pending.concat(e):this.pending=e,this.pendingTotal+=e.length,this.pending.length>=this._delta8){var n=(e=this.pending).length%this._delta8;this.pending=e.slice(e.length-n,e.length),0===this.pending.length&&(this.pending=null),e=r.join32(e,0,e.length-n,this.endian);for(var i=0;i<e.length;i+=this._delta32)this._update(e,i,i+this._delta32)}return this},o.prototype.digest=function(e){return this.update(this._pad()),i(null===this.pending),this._digest(e)},o.prototype._pad=function(){var e=this.pendingTotal,t=this._delta8,n=t-(e+this.padLength)%t,r=new Array(n+this.padLength);r[0]=128;for(var i=1;i<n;i++)r[i]=0;if(e<<=3,"big"===this.endian){for(var o=8;o<this.padLength;o++)r[i++]=0;r[i++]=0,r[i++]=0,r[i++]=0,r[i++]=0,r[i++]=e>>>24&255,r[i++]=e>>>16&255,r[i++]=e>>>8&255,r[i++]=255&e}else for(r[i++]=255&e,r[i++]=e>>>8&255,r[i++]=e>>>16&255,r[i++]=e>>>24&255,r[i++]=0,r[i++]=0,r[i++]=0,r[i++]=0,o=8;o<this.padLength;o++)r[i++]=0;return r}},function(e,t,n){"use strict";const r=n(7),i=n(132).Reporter,o=n(130).Buffer;function s(e,t){i.call(this,t),o.isBuffer(e)?(this.base=e,this.offset=0,this.length=e.length):this.error("Input not Buffer")}function a(e,t){if(Array.isArray(e))this.length=0,this.value=e.map((function(e){return a.isEncoderBuffer(e)||(e=new a(e,t)),this.length+=e.length,e}),this);else if("number"==typeof e){if(!(0<=e&&e<=255))return t.error("non-byte EncoderBuffer value");this.value=e,this.length=1}else if("string"==typeof e)this.value=e,this.length=o.byteLength(e);else{if(!o.isBuffer(e))return t.error("Unsupported type: "+typeof e);this.value=e,this.length=e.length}}r(s,i),t.DecoderBuffer=s,s.isDecoderBuffer=function(e){if(e instanceof s)return!0;return"object"==typeof e&&o.isBuffer(e.base)&&"DecoderBuffer"===e.constructor.name&&"number"==typeof e.offset&&"number"==typeof e.length&&"function"==typeof e.save&&"function"==typeof e.restore&&"function"==typeof e.isEmpty&&"function"==typeof e.readUInt8&&"function"==typeof e.skip&&"function"==typeof e.raw},s.prototype.save=function(){return{offset:this.offset,reporter:i.prototype.save.call(this)}},s.prototype.restore=function(e){const t=new s(this.base);return t.offset=e.offset,t.length=this.offset,this.offset=e.offset,i.prototype.restore.call(this,e.reporter),t},s.prototype.isEmpty=function(){return this.offset===this.length},s.prototype.readUInt8=function(e){return this.offset+1<=this.length?this.base.readUInt8(this.offset++,!0):this.error(e||"DecoderBuffer overrun")},s.prototype.skip=function(e,t){if(!(this.offset+e<=this.length))return this.error(t||"DecoderBuffer overrun");const n=new s(this.base);return n._reporterState=this._reporterState,n.offset=this.offset,n.length=this.offset+e,this.offset+=e,n},s.prototype.raw=function(e){return this.base.slice(e?e.offset:this.offset,this.length)},t.EncoderBuffer=a,a.isEncoderBuffer=function(e){if(e instanceof a)return!0;return"object"==typeof e&&"EncoderBuffer"===e.constructor.name&&"number"==typeof e.length&&"function"==typeof e.join},a.prototype.join=function(e,t){return e||(e=o.alloc(this.length)),t||(t=0),0===this.length||(Array.isArray(this.value)?this.value.forEach((function(n){n.join(e,t),t+=n.length})):("number"==typeof this.value?e[t]=this.value:"string"==typeof this.value?e.write(this.value,t):o.isBuffer(this.value)&&this.value.copy(e,t),t+=this.length)),e}},function(e,t,n){var r=n(97),i=n(390),o=n(391),s=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":s&&s in Object(e)?i(e):o(e)}},function(e,t){e.exports=function(e){return null!=e&&"object"==typeof e}},function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));var r={},i=function(){function e(){}return e.setItem=function(e,t){return r[e]=t,r[e]},e.getItem=function(e){return Object.prototype.hasOwnProperty.call(r,e)?r[e]:void 0},e.removeItem=function(e){return delete r[e]},e.clear=function(){return r={}},e}(),o=function(){function e(){try{this.storageWindow=window.localStorage,this.storageWindow.setItem("aws.amplify.test-ls",1),this.storageWindow.removeItem("aws.amplify.test-ls")}catch(e){this.storageWindow=i}}return e.prototype.getStorage=function(){return this.storageWindow},e}()},function(e,t,n){var r;e.exports=(r=n(32),function(e){var t=r,n=t.lib,i=n.WordArray,o=n.Hasher,s=t.algo,a=[],u=[];!function(){function t(t){for(var n=e.sqrt(t),r=2;r<=n;r++)if(!(t%r))return!1;return!0}function n(e){return 4294967296*(e-(0|e))|0}for(var r=2,i=0;i<64;)t(r)&&(i<8&&(a[i]=n(e.pow(r,.5))),u[i]=n(e.pow(r,1/3)),i++),r++}();var c=[],f=s.SHA256=o.extend({_doReset:function(){this._hash=new i.init(a.slice(0))},_doProcessBlock:function(e,t){for(var n=this._hash.words,r=n[0],i=n[1],o=n[2],s=n[3],a=n[4],f=n[5],l=n[6],d=n[7],h=0;h<64;h++){if(h<16)c[h]=0|e[t+h];else{var p=c[h-15],v=(p<<25|p>>>7)^(p<<14|p>>>18)^p>>>3,g=c[h-2],m=(g<<15|g>>>17)^(g<<13|g>>>19)^g>>>10;c[h]=v+c[h-7]+m+c[h-16]}var b=r&i^r&o^i&o,y=(r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22),w=d+((a<<26|a>>>6)^(a<<21|a>>>11)^(a<<7|a>>>25))+(a&f^~a&l)+u[h]+c[h];d=l,l=f,f=a,a=s+w|0,s=o,o=i,i=r,r=w+(y+b)|0}n[0]=n[0]+r|0,n[1]=n[1]+i|0,n[2]=n[2]+o|0,n[3]=n[3]+s|0,n[4]=n[4]+a|0,n[5]=n[5]+f|0,n[6]=n[6]+l|0,n[7]=n[7]+d|0},_doFinalize:function(){var t=this._data,n=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return n[i>>>5]|=128<<24-i%32,n[14+(i+64>>>9<<4)]=e.floor(r/4294967296),n[15+(i+64>>>9<<4)]=r,t.sigBytes=4*n.length,this._process(),this._hash},clone:function(){var e=o.clone.call(this);return e._hash=this._hash.clone(),e}});t.SHA256=o._createHelper(f),t.HmacSHA256=o._createHmacHelper(f)}(Math),r.SHA256)},function(e,t,n){"use strict";n.d(t,"a",(function(){return c}));var r=n(44),i=function(){return(i=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},o=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},s=function(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(o(arguments[t]));return e},a=new r.a("Hub"),u="undefined"!=typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("amplify_default"):"@@amplify_default";var c=new(function(){function e(e){this.listeners=[],this.patterns=[],this.protectedChannels=["core","auth","api","analytics","interactions","pubsub","storage","xr"],this.name=e}return e.prototype.remove=function(e,t){if(e instanceof RegExp){var n=this.patterns.find((function(t){return t.pattern.source===e.source}));if(!n)return void a.warn("No listeners for "+e);this.patterns=s(this.patterns.filter((function(e){return e!==n})))}else{var r=this.listeners[e];if(!r)return void a.warn("No listeners for "+e);this.listeners[e]=s(r.filter((function(e){return e.callback!==t})))}},e.prototype.dispatch=function(e,t,n,r){(void 0===n&&(n=""),this.protectedChannels.indexOf(e)>-1)&&(r===u||a.warn("WARNING: "+e+" is protected and dispatching on it can have unintended consequences"));var o={channel:e,payload:i({},t),source:n,patternInfo:[]};try{this._toListeners(o)}catch(e){a.error(e)}},e.prototype.listen=function(e,t,n){var r,i=this;if(void 0===n&&(n="noname"),function(e){return void 0!==e.onHubCapsule}(t))a.warn("WARNING onHubCapsule is Deprecated. Please pass in a callback."),r=t.onHubCapsule.bind(t);else{if("function"!=typeof t)throw new Error("No callback supplied to Hub");r=t}if(e instanceof RegExp)this.patterns.push({pattern:e,callback:r});else{var o=this.listeners[e];o||(o=[],this.listeners[e]=o),o.push({name:n,callback:r})}return function(){i.remove(e,r)}},e.prototype._toListeners=function(e){var t=e.channel,n=e.payload,r=this.listeners[t];if(r&&r.forEach((function(r){a.debug("Dispatching to "+t+" with ",n);try{r.callback(e)}catch(e){a.error(e)}})),this.patterns.length>0){if(!n.message)return void a.warn("Cannot perform pattern matching without a message key");var s=n.message;this.patterns.forEach((function(t){var n=s.match(t.pattern);if(n){var r=o(n).slice(1),u=i(i({},e),{patternInfo:r});try{t.callback(u)}catch(e){a.error(e)}}}))}},e}())("__default__")},function(e,t,n){"use strict";n.d(t,"a",(function(){return Lt}));var r=n(44),i=n(86),o=n(33),s=n(73),a=n(514),u=n(50),c=n(19),f=n(1),l=function(e,t){return(l=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};function d(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}l(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var h=function(){return(h=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};function p(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))}function v(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}}Object.create;function g(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s}var m,b,y,w,_,S,E,M,A,I,k,O,x,C,T,P,N,R,L,j,D,U,B,F,z,q,K,H,V,G,W,$,Y,J,Z,X,Q,ee,te,ne,re,ie,oe,se,ae,ue,ce,fe,le,de,he,pe,ve,ge,me,be,ye;Object.create;!function(e){e.AUTHENTICATED_ROLE="AuthenticatedRole",e.DENY="Deny"}(m||(m={})),(b||(b={})).filterSensitiveLog=function(e){return h({},e)},(y||(y={})).filterSensitiveLog=function(e){return h({},e)},(w||(w={})).filterSensitiveLog=function(e){return h({},e)},(_||(_={})).filterSensitiveLog=function(e){return h({},e)},(S||(S={})).filterSensitiveLog=function(e){return h({},e)},(E||(E={})).filterSensitiveLog=function(e){return h({},e)},(M||(M={})).filterSensitiveLog=function(e){return h({},e)},(A||(A={})).filterSensitiveLog=function(e){return h({},e)},(I||(I={})).filterSensitiveLog=function(e){return h({},e)},(k||(k={})).filterSensitiveLog=function(e){return h({},e)},function(e){e.ACCESS_DENIED="AccessDenied",e.INTERNAL_SERVER_ERROR="InternalServerError"}(O||(O={})),(x||(x={})).filterSensitiveLog=function(e){return h({},e)},(C||(C={})).filterSensitiveLog=function(e){return h({},e)},(T||(T={})).filterSensitiveLog=function(e){return h({},e)},(P||(P={})).filterSensitiveLog=function(e){return h({},e)},(N||(N={})).filterSensitiveLog=function(e){return h({},e)},(R||(R={})).filterSensitiveLog=function(e){return h({},e)},(L||(L={})).filterSensitiveLog=function(e){return h({},e)},(j||(j={})).filterSensitiveLog=function(e){return h({},e)},(D||(D={})).filterSensitiveLog=function(e){return h({},e)},(U||(U={})).filterSensitiveLog=function(e){return h({},e)},(B||(B={})).filterSensitiveLog=function(e){return h({},e)},(F||(F={})).filterSensitiveLog=function(e){return h({},e)},(z||(z={})).filterSensitiveLog=function(e){return h({},e)},(q||(q={})).filterSensitiveLog=function(e){return h({},e)},(K||(K={})).filterSensitiveLog=function(e){return h({},e)},function(e){e.CONTAINS="Contains",e.EQUALS="Equals",e.NOT_EQUAL="NotEqual",e.STARTS_WITH="StartsWith"}(H||(H={})),(V||(V={})).filterSensitiveLog=function(e){return h({},e)},(G||(G={})).filterSensitiveLog=function(e){return h({},e)},function(e){e.RULES="Rules",e.TOKEN="Token"}(W||(W={})),($||($={})).filterSensitiveLog=function(e){return h({},e)},(Y||(Y={})).filterSensitiveLog=function(e){return h({},e)},(J||(J={})).filterSensitiveLog=function(e){return h({},e)},(Z||(Z={})).filterSensitiveLog=function(e){return h({},e)},(X||(X={})).filterSensitiveLog=function(e){return h({},e)},(Q||(Q={})).filterSensitiveLog=function(e){return h({},e)},(ee||(ee={})).filterSensitiveLog=function(e){return h({},e)},(te||(te={})).filterSensitiveLog=function(e){return h({},e)},(ne||(ne={})).filterSensitiveLog=function(e){return h({},e)},(re||(re={})).filterSensitiveLog=function(e){return h({},e)},(ie||(ie={})).filterSensitiveLog=function(e){return h({},e)},(oe||(oe={})).filterSensitiveLog=function(e){return h({},e)},(se||(se={})).filterSensitiveLog=function(e){return h({},e)},(ae||(ae={})).filterSensitiveLog=function(e){return h({},e)},(ue||(ue={})).filterSensitiveLog=function(e){return h({},e)},(ce||(ce={})).filterSensitiveLog=function(e){return h({},e)},(fe||(fe={})).filterSensitiveLog=function(e){return h({},e)},(le||(le={})).filterSensitiveLog=function(e){return h({},e)},(de||(de={})).filterSensitiveLog=function(e){return h({},e)},(he||(he={})).filterSensitiveLog=function(e){return h({},e)},(pe||(pe={})).filterSensitiveLog=function(e){return h({},e)},(ve||(ve={})).filterSensitiveLog=function(e){return h({},e)},(ge||(ge={})).filterSensitiveLog=function(e){return h({},e)},(me||(me={})).filterSensitiveLog=function(e){return h({},e)},(be||(be={})).filterSensitiveLog=function(e){return h({},e)},(ye||(ye={})).filterSensitiveLog=function(e){return h({},e)};var we=n(2),_e=function(e,t){return p(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,l,d,p,g,m,b,y;return v(this,(function(v){switch(v.label){case 0:return r=[h({},e)],y={},[4,Je(e.body,t)];case 1:switch(n=h.apply(void 0,r.concat([(y.body=v.sent(),y)])),o="UnknownError",s=n.body.__type.split("#"),o=void 0===s[1]?s[0]:s[1],o){case"ExternalServiceException":case"com.amazonaws.cognitoidentity#ExternalServiceException":return[3,2];case"InternalErrorException":case"com.amazonaws.cognitoidentity#InternalErrorException":return[3,4];case"InvalidIdentityPoolConfigurationException":case"com.amazonaws.cognitoidentity#InvalidIdentityPoolConfigurationException":return[3,6];case"InvalidParameterException":case"com.amazonaws.cognitoidentity#InvalidParameterException":return[3,8];case"NotAuthorizedException":case"com.amazonaws.cognitoidentity#NotAuthorizedException":return[3,10];case"ResourceConflictException":case"com.amazonaws.cognitoidentity#ResourceConflictException":return[3,12];case"ResourceNotFoundException":case"com.amazonaws.cognitoidentity#ResourceNotFoundException":return[3,14];case"TooManyRequestsException":case"com.amazonaws.cognitoidentity#TooManyRequestsException":return[3,16]}return[3,18];case 2:return a=[{}],[4,Ee(n,t)];case 3:return i=h.apply(void 0,[h.apply(void 0,a.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 4:return u=[{}],[4,Me(n,t)];case 5:return i=h.apply(void 0,[h.apply(void 0,u.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 6:return c=[{}],[4,Ae(n,t)];case 7:return i=h.apply(void 0,[h.apply(void 0,c.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 8:return f=[{}],[4,Ie(n,t)];case 9:return i=h.apply(void 0,[h.apply(void 0,f.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 10:return l=[{}],[4,Oe(n,t)];case 11:return i=h.apply(void 0,[h.apply(void 0,l.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 12:return d=[{}],[4,xe(n,t)];case 13:return i=h.apply(void 0,[h.apply(void 0,d.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 14:return p=[{}],[4,Ce(n,t)];case 15:return i=h.apply(void 0,[h.apply(void 0,p.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 16:return g=[{}],[4,Te(n,t)];case 17:return i=h.apply(void 0,[h.apply(void 0,g.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 18:m=n.body,o=m.code||m.Code||o,i=h(h({},m),{name:""+o,message:m.message||m.Message||o,$fault:"client",$metadata:We(e)}),v.label=19;case 19:return b=i.message||i.Message||o,i.message=b,delete i.Message,[2,Promise.reject(Object.assign(new Error(b),i))]}}))}))},Se=function(e,t){return p(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,l,d,p,g,m,b,y;return v(this,(function(v){switch(v.label){case 0:return r=[h({},e)],y={},[4,Je(e.body,t)];case 1:switch(n=h.apply(void 0,r.concat([(y.body=v.sent(),y)])),o="UnknownError",s=n.body.__type.split("#"),o=void 0===s[1]?s[0]:s[1],o){case"ExternalServiceException":case"com.amazonaws.cognitoidentity#ExternalServiceException":return[3,2];case"InternalErrorException":case"com.amazonaws.cognitoidentity#InternalErrorException":return[3,4];case"InvalidParameterException":case"com.amazonaws.cognitoidentity#InvalidParameterException":return[3,6];case"LimitExceededException":case"com.amazonaws.cognitoidentity#LimitExceededException":return[3,8];case"NotAuthorizedException":case"com.amazonaws.cognitoidentity#NotAuthorizedException":return[3,10];case"ResourceConflictException":case"com.amazonaws.cognitoidentity#ResourceConflictException":return[3,12];case"ResourceNotFoundException":case"com.amazonaws.cognitoidentity#ResourceNotFoundException":return[3,14];case"TooManyRequestsException":case"com.amazonaws.cognitoidentity#TooManyRequestsException":return[3,16]}return[3,18];case 2:return a=[{}],[4,Ee(n,t)];case 3:return i=h.apply(void 0,[h.apply(void 0,a.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 4:return u=[{}],[4,Me(n,t)];case 5:return i=h.apply(void 0,[h.apply(void 0,u.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 6:return c=[{}],[4,Ie(n,t)];case 7:return i=h.apply(void 0,[h.apply(void 0,c.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 8:return f=[{}],[4,ke(n,t)];case 9:return i=h.apply(void 0,[h.apply(void 0,f.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 10:return l=[{}],[4,Oe(n,t)];case 11:return i=h.apply(void 0,[h.apply(void 0,l.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 12:return d=[{}],[4,xe(n,t)];case 13:return i=h.apply(void 0,[h.apply(void 0,d.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 14:return p=[{}],[4,Ce(n,t)];case 15:return i=h.apply(void 0,[h.apply(void 0,p.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 16:return g=[{}],[4,Te(n,t)];case 17:return i=h.apply(void 0,[h.apply(void 0,g.concat([v.sent()])),{name:o,$metadata:We(e)}]),[3,19];case 18:m=n.body,o=m.code||m.Code||o,i=h(h({},m),{name:""+o,message:m.message||m.Message||o,$fault:"client",$metadata:We(e)}),v.label=19;case 19:return b=i.message||i.Message||o,i.message=b,delete i.Message,[2,Promise.reject(Object.assign(new Error(b),i))]}}))}))},Ee=function(e,t){return p(void 0,void 0,void 0,(function(){var n,r;return v(this,(function(i){return n=e.body,r=je(n,t),[2,h({name:"ExternalServiceException",$fault:"client",$metadata:We(e)},r)]}))}))},Me=function(e,t){return p(void 0,void 0,void 0,(function(){var n,r;return v(this,(function(i){return n=e.body,r=Be(n,t),[2,h({name:"InternalErrorException",$fault:"server",$metadata:We(e)},r)]}))}))},Ae=function(e,t){return p(void 0,void 0,void 0,(function(){var n,r;return v(this,(function(i){return n=e.body,r=Fe(n,t),[2,h({name:"InvalidIdentityPoolConfigurationException",$fault:"client",$metadata:We(e)},r)]}))}))},Ie=function(e,t){return p(void 0,void 0,void 0,(function(){var n,r;return v(this,(function(i){return n=e.body,r=ze(n,t),[2,h({name:"InvalidParameterException",$fault:"client",$metadata:We(e)},r)]}))}))},ke=function(e,t){return p(void 0,void 0,void 0,(function(){var n,r;return v(this,(function(i){return n=e.body,r=qe(n,t),[2,h({name:"LimitExceededException",$fault:"client",$metadata:We(e)},r)]}))}))},Oe=function(e,t){return p(void 0,void 0,void 0,(function(){var n,r;return v(this,(function(i){return n=e.body,r=Ke(n,t),[2,h({name:"NotAuthorizedException",$fault:"client",$metadata:We(e)},r)]}))}))},xe=function(e,t){return p(void 0,void 0,void 0,(function(){var n,r;return v(this,(function(i){return n=e.body,r=He(n,t),[2,h({name:"ResourceConflictException",$fault:"client",$metadata:We(e)},r)]}))}))},Ce=function(e,t){return p(void 0,void 0,void 0,(function(){var n,r;return v(this,(function(i){return n=e.body,r=Ve(n,t),[2,h({name:"ResourceNotFoundException",$fault:"client",$metadata:We(e)},r)]}))}))},Te=function(e,t){return p(void 0,void 0,void 0,(function(){var n,r;return v(this,(function(i){return n=e.body,r=Ge(n,t),[2,h({name:"TooManyRequestsException",$fault:"client",$metadata:We(e)},r)]}))}))},Pe=function(e,t){return h(h(h({},void 0!==e.CustomRoleArn&&{CustomRoleArn:e.CustomRoleArn}),void 0!==e.IdentityId&&{IdentityId:e.IdentityId}),void 0!==e.Logins&&{Logins:Re(e.Logins,t)})},Ne=function(e,t){return h(h(h({},void 0!==e.AccountId&&{AccountId:e.AccountId}),void 0!==e.IdentityPoolId&&{IdentityPoolId:e.IdentityPoolId}),void 0!==e.Logins&&{Logins:Re(e.Logins,t)})},Re=function(e,t){return Object.entries(e).reduce((function(e,t){var n,r=g(t,2),i=r[0],o=r[1];return h(h({},e),((n={})[i]=o,n))}),{})},Le=function(e,t){return{AccessKeyId:void 0!==e.AccessKeyId&&null!==e.AccessKeyId?e.AccessKeyId:void 0,Expiration:void 0!==e.Expiration&&null!==e.Expiration?new Date(Math.round(1e3*e.Expiration)):void 0,SecretKey:void 0!==e.SecretKey&&null!==e.SecretKey?e.SecretKey:void 0,SessionToken:void 0!==e.SessionToken&&null!==e.SessionToken?e.SessionToken:void 0}},je=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},De=function(e,t){return{Credentials:void 0!==e.Credentials&&null!==e.Credentials?Le(e.Credentials):void 0,IdentityId:void 0!==e.IdentityId&&null!==e.IdentityId?e.IdentityId:void 0}},Ue=function(e,t){return{IdentityId:void 0!==e.IdentityId&&null!==e.IdentityId?e.IdentityId:void 0}},Be=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},Fe=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},ze=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},qe=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},Ke=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},He=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},Ve=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},Ge=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},We=function(e){return{httpStatusCode:e.statusCode,httpHeaders:e.headers,requestId:e.headers["x-amzn-requestid"]}},$e=function(e,t){return void 0===e&&(e=new Uint8Array),e instanceof Uint8Array?Promise.resolve(e):t.streamCollector(e)||Promise.resolve(new Uint8Array)},Ye=function(e,t,n,r,i){return p(void 0,void 0,void 0,(function(){var o,s,a,u,c,f;return v(this,(function(l){switch(l.label){case 0:return[4,e.endpoint()];case 1:return o=l.sent(),s=o.hostname,a=o.protocol,u=void 0===a?"https":a,c=o.port,f={protocol:u,hostname:s,port:c,method:"POST",path:n,headers:t},void 0!==r&&(f.hostname=r),void 0!==i&&(f.body=i),[2,new we.a(f)]}}))}))},Je=function(e,t){return function(e,t){return $e(e,t).then((function(e){return t.utf8Encoder(e)}))}(e,t).then((function(e){return e.length?JSON.parse(e):{}}))},Ze=n(10),Xe=n(0),Qe=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return d(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(Ze.a)(t,this.serialize,this.deserialize));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"CognitoIdentityClient",commandName:"GetCredentialsForIdentityCommand",inputFilterSensitiveLog:D.filterSensitiveLog,outputFilterSensitiveLog:B.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"CognitoIdentityClient",commandName:"GetCredentialsForIdentityCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return p(void 0,void 0,void 0,(function(){var n,r;return v(this,(function(i){return n={"Content-Type":"application/x-amz-json-1.1","X-Amz-Target":"AWSCognitoIdentityService.GetCredentialsForIdentity"},r=JSON.stringify(Pe(e,t)),[2,Ye(t,n,"/",void 0,r)]}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return p(void 0,void 0,void 0,(function(){var n,r,i;return v(this,(function(o){switch(o.label){case 0:return e.statusCode>=300?[2,_e(e,t)]:[4,Je(e.body,t)];case 1:return n=o.sent(),{},r=De(n,t),i=h({$metadata:We(e)},r),[2,Promise.resolve(i)]}}))}))}(e,t)},t}(Xe.b),et=function(e){function t(t,n){void 0===n&&(n=!0);var r=e.call(this,t)||this;return r.tryNextLink=n,r}return Object(f.__extends)(t,e),t}(Error);function tt(e){return Promise.all(Object.keys(e).reduce((function(t,n){var r=e[n];return"string"==typeof r?t.push([n,r]):t.push(r().then((function(e){return[n,e]}))),t}),[])).then((function(e){return e.reduce((function(e,t){var n=Object(f.__read)(t,2),r=n[0],i=n[1];return e[r]=i,e}),{})}))}function nt(e){var t=this;return function(){return Object(f.__awaiter)(t,void 0,void 0,(function(){var t,n,r,i,o,s,a,u,c,l,d,h,p;return Object(f.__generator)(this,(function(f){switch(f.label){case 0:return l=(c=e.client).send,d=Qe.bind,p={CustomRoleArn:e.customRoleArn,IdentityId:e.identityId},e.logins?[4,tt(e.logins)]:[3,2];case 1:return h=f.sent(),[3,3];case 2:h=void 0,f.label=3;case 3:return[4,l.apply(c,[new(d.apply(Qe,[void 0,(p.Logins=h,p)]))])];case 4:return t=f.sent().Credentials,n=void 0===t?function(){throw new et("Response from Amazon Cognito contained no credentials")}():t,r=n.AccessKeyId,i=void 0===r?function(){throw new et("Response from Amazon Cognito contained no access key ID")}():r,o=n.Expiration,s=n.SecretKey,a=void 0===s?function(){throw new et("Response from Amazon Cognito contained no secret key")}():s,u=n.SessionToken,[2,{identityId:e.identityId,accessKeyId:i,secretAccessKey:a,sessionToken:u,expiration:o}]}}))}))}}var rt=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return d(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(Ze.a)(t,this.serialize,this.deserialize));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"CognitoIdentityClient",commandName:"GetIdCommand",inputFilterSensitiveLog:z.filterSensitiveLog,outputFilterSensitiveLog:q.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"CognitoIdentityClient",commandName:"GetIdCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return p(void 0,void 0,void 0,(function(){var n,r;return v(this,(function(i){return n={"Content-Type":"application/x-amz-json-1.1","X-Amz-Target":"AWSCognitoIdentityService.GetId"},r=JSON.stringify(Ne(e,t)),[2,Ye(t,n,"/",void 0,r)]}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return p(void 0,void 0,void 0,(function(){var n,r,i;return v(this,(function(o){switch(o.label){case 0:return e.statusCode>=300?[2,Se(e,t)]:[4,Je(e.body,t)];case 1:return n=o.sent(),{},r=Ue(n,t),i=h({$metadata:We(e)},r),[2,Promise.resolve(i)]}}))}))}(e,t)},t}(Xe.b),it=function(){function e(e){void 0===e&&(e="aws:cognito-identity-ids"),this.dbName=e}return e.prototype.getItem=function(e){return this.withObjectStore("readonly",(function(t){var n=t.get(e);return new Promise((function(e){n.onerror=function(){return e(null)},n.onsuccess=function(){return e(n.result?n.result.value:null)}}))})).catch((function(){return null}))},e.prototype.removeItem=function(e){return this.withObjectStore("readwrite",(function(t){var n=t.delete(e);return new Promise((function(e,t){n.onerror=function(){return t(n.error)},n.onsuccess=function(){return e()}}))}))},e.prototype.setItem=function(e,t){return this.withObjectStore("readwrite",(function(n){var r=n.put({id:e,value:t});return new Promise((function(e,t){r.onerror=function(){return t(r.error)},r.onsuccess=function(){return e()}}))}))},e.prototype.getDb=function(){var e=self.indexedDB.open(this.dbName,1);return new Promise((function(t,n){e.onsuccess=function(){t(e.result)},e.onerror=function(){n(e.error)},e.onblocked=function(){n(new Error("Unable to access DB"))},e.onupgradeneeded=function(){var t=e.result;t.onerror=function(){n(new Error("Failed to create object store"))},t.createObjectStore("IdentityIds",{keyPath:"id"})}}))},e.prototype.withObjectStore=function(e,t){return this.getDb().then((function(n){var r=n.transaction("IdentityIds",e);return r.oncomplete=function(){return n.close()},new Promise((function(e,n){r.onerror=function(){return n(r.error)},e(t(r.objectStore("IdentityIds")))})).catch((function(e){throw n.close(),e}))}))},e}(),ot=new(function(){function e(e){void 0===e&&(e={}),this.store=e}return e.prototype.getItem=function(e){return e in this.store?this.store[e]:null},e.prototype.removeItem=function(e){delete this.store[e]},e.prototype.setItem=function(e,t){this.store[e]=t},e}());function st(e){var t=this,n=e.accountId,r=e.cache,i=void 0===r?"object"==typeof self&&self.indexedDB?new it:"object"==typeof window&&window.localStorage?window.localStorage:ot:r,o=e.client,s=e.customRoleArn,a=e.identityPoolId,u=e.logins,c=e.userIdentifier,l=void 0===c?u&&0!==Object.keys(u).length?void 0:"ANONYMOUS":c,d=l?"aws:cognito-identity-credentials:"+a+":"+l:void 0,h=function(){return Object(f.__awaiter)(t,void 0,void 0,(function(){var e,t,r,c,l,p,v,g,m;return Object(f.__generator)(this,(function(f){switch(f.label){case 0:return(t=d)?[4,i.getItem(d)]:[3,2];case 1:t=f.sent(),f.label=2;case 2:return(e=t)?[3,7]:(p=(l=o).send,v=rt.bind,m={AccountId:n,IdentityPoolId:a},u?[4,tt(u)]:[3,4]);case 3:return g=f.sent(),[3,5];case 4:g=void 0,f.label=5;case 5:return[4,p.apply(l,[new(v.apply(rt,[void 0,(m.Logins=g,m)]))])];case 6:r=f.sent().IdentityId,c=void 0===r?function(){throw new et("Response from Amazon Cognito contained no identity ID")}():r,e=c,d&&Promise.resolve(i.setItem(d,e)).catch((function(){})),f.label=7;case 7:return[2,(h=nt({client:o,customRoleArn:s,logins:u,identityId:e}))()]}}))}))};return function(){return h().catch((function(e){return Object(f.__awaiter)(t,void 0,void 0,(function(){return Object(f.__generator)(this,(function(t){throw d&&Promise.resolve(i.removeItem(d)).catch((function(){})),e}))}))}))}}var at=n(147),ut=n(38),ct=n(18),ft=n(24),lt=n(11),dt=n(39),ht=n(17),pt=n(40),vt=n(41),gt=n(15),mt="cognito-identity.{region}.amazonaws.com",bt=new Set(["ap-east-1","ap-northeast-1","ap-northeast-2","ap-south-1","ap-southeast-1","ap-southeast-2","ca-central-1","eu-central-1","eu-north-1","eu-west-1","eu-west-2","eu-west-3","me-south-1","sa-east-1","us-east-1","us-east-2","us-west-1","us-west-2"]),yt=new Set(["cn-north-1","cn-northwest-1"]),wt=new Set(["us-iso-east-1"]),_t=new Set(["us-isob-east-1"]),St=new Set(["us-gov-east-1","us-gov-west-1"]),Et=h(h({},{apiVersion:"2014-06-30",disableHostPrefix:!1,logger:{},regionInfoProvider:function(e,t){var n=void 0;switch(e){case"ap-northeast-1":n={hostname:"cognito-identity.ap-northeast-1.amazonaws.com",partition:"aws"};break;case"ap-northeast-2":n={hostname:"cognito-identity.ap-northeast-2.amazonaws.com",partition:"aws"};break;case"ap-south-1":n={hostname:"cognito-identity.ap-south-1.amazonaws.com",partition:"aws"};break;case"ap-southeast-1":n={hostname:"cognito-identity.ap-southeast-1.amazonaws.com",partition:"aws"};break;case"ap-southeast-2":n={hostname:"cognito-identity.ap-southeast-2.amazonaws.com",partition:"aws"};break;case"ca-central-1":n={hostname:"cognito-identity.ca-central-1.amazonaws.com",partition:"aws"};break;case"cn-north-1":n={hostname:"cognito-identity.cn-north-1.amazonaws.com.cn",partition:"aws-cn"};break;case"eu-central-1":n={hostname:"cognito-identity.eu-central-1.amazonaws.com",partition:"aws"};break;case"eu-west-1":n={hostname:"cognito-identity.eu-west-1.amazonaws.com",partition:"aws"};break;case"eu-west-2":n={hostname:"cognito-identity.eu-west-2.amazonaws.com",partition:"aws"};break;case"us-east-1":n={hostname:"cognito-identity.us-east-1.amazonaws.com",partition:"aws"};break;case"us-east-2":n={hostname:"cognito-identity.us-east-2.amazonaws.com",partition:"aws"};break;case"us-west-2":n={hostname:"cognito-identity.us-west-2.amazonaws.com",partition:"aws"};break;default:bt.has(e)&&(n={hostname:mt.replace("{region}",e),partition:"aws"}),yt.has(e)&&(n={hostname:"cognito-identity.{region}.amazonaws.com.cn".replace("{region}",e),partition:"aws-cn"}),wt.has(e)&&(n={hostname:"cognito-identity.{region}.c2s.ic.gov".replace("{region}",e),partition:"aws-iso"}),_t.has(e)&&(n={hostname:"cognito-identity.{region}.sc2s.sgov.gov".replace("{region}",e),partition:"aws-iso-b"}),St.has(e)&&(n={hostname:"cognito-identity.{region}.amazonaws.com".replace("{region}",e),partition:"aws-us-gov"}),void 0===n&&(n={hostname:mt.replace("{region}",e),partition:"aws"})}return Promise.resolve(n)},signingName:"cognito-identity"}),{runtime:"browser",base64Decoder:ht.a,base64Encoder:ht.b,bodyLengthChecker:pt.a,credentialDefaultProvider:function(){},defaultUserAgent:Object(vt.a)(at.name,at.version),maxAttempts:lt.a,region:Object(ft.a)("Region is missing"),requestHandler:new ct.a,sha256:ut.Sha256,streamCollector:ct.b,urlParser:dt.a,utf8Decoder:gt.a,utf8Encoder:gt.b}),Mt=n(22),At=n(37),It=n(21),kt=n(43),Ot=n(25),xt=n(23),Ct=function(e){function t(t){var n=this,r=h(h({},Et),t),i=Object(Mt.b)(r),o=Object(Mt.a)(i),s=Object(Ot.b)(o),a=Object(lt.c)(s),u=Object(xt.b)(a),c=Object(It.b)(u);return(n=e.call(this,c)||this).config=c,n.middlewareStack.use(Object(lt.b)(n.config)),n.middlewareStack.use(Object(xt.a)(n.config)),n.middlewareStack.use(Object(At.a)(n.config)),n.middlewareStack.use(Object(It.a)(n.config)),n.middlewareStack.use(Object(kt.a)(n.config)),n}return d(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this)},t}(Xe.a),Tt=function(){return(Tt=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},Pt=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},Nt=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},Rt=new r.a("Credentials"),Lt=new(function(){function e(e){this._gettingCredPromise=null,this._refreshHandlers={},this.Auth=void 0,this.configure(e),this._refreshHandlers.google=s.b.refreshGoogleToken,this._refreshHandlers.facebook=s.a.refreshFacebookToken}return e.prototype.getModuleName=function(){return"Credentials"},e.prototype.getCredSource=function(){return this._credentials_source},e.prototype.configure=function(e){if(!e)return this._config||{};this._config=Object.assign({},this._config,e);var t=this._config.refreshHandlers;return t&&(this._refreshHandlers=Tt(Tt({},this._refreshHandlers),t)),this._storage=this._config.storage,this._storage||(this._storage=(new i.a).getStorage()),this._storageSync=Promise.resolve(),"function"==typeof this._storage.sync&&(this._storageSync=this._storage.sync()),this._config},e.prototype.get=function(){return Rt.debug("getting credentials"),this._pickupCredentials()},e.prototype._pickupCredentials=function(){return Rt.debug("picking up credentials"),this._gettingCredPromise&&this._gettingCredPromise.isPending()?Rt.debug("getting old cred promise"):(Rt.debug("getting new cred promise"),this._gettingCredPromise=Object(o.d)(this._keepAlive())),this._gettingCredPromise},e.prototype._keepAlive=function(){return Pt(this,void 0,void 0,(function(){var e,t,n,r,i,o,s;return Nt(this,(function(a){switch(a.label){case 0:if(Rt.debug("checking if credentials exists and not expired"),(e=this._credentials)&&!this._isExpired(e)&&!this._isPastTTL())return Rt.debug("credentials not changed and not expired, directly return"),[2,Promise.resolve(e)];if(Rt.debug("need to get a new credential or refresh the existing one"),t=this.Auth,!(n=void 0===t?c.a.Auth:t)||"function"!=typeof n.currentUserCredentials)return[2,Promise.reject("No Auth module registered in Amplify")];if(this._isExpired(e)||!this._isPastTTL())return[3,6];Rt.debug("ttl has passed but token is not yet expired"),a.label=1;case 1:return a.trys.push([1,5,,6]),[4,n.currentUserPoolUser()];case 2:return r=a.sent(),[4,n.currentSession()];case 3:return i=a.sent(),o=i.refreshToken,[4,new Promise((function(e,t){r.refreshSession(o,(function(n,r){return n?t(n):e(r)}))}))];case 4:return a.sent(),[3,6];case 5:return s=a.sent(),Rt.debug("Error attempting to refreshing the session",s),[3,6];case 6:return[2,n.currentUserCredentials()]}}))}))},e.prototype.refreshFederatedToken=function(e){Rt.debug("Getting federated credentials");var t=e.provider,n=e.user,r=e.token,i=e.identity_id,o=e.expires_at;o=1970===new Date(o).getFullYear()?1e3*o:o;return Rt.debug("checking if federated jwt token expired"),o>(new Date).getTime()?(Rt.debug("token not expired"),this._setCredentialsFromFederation({provider:t,token:r,user:n,identity_id:i,expires_at:o})):this._refreshHandlers[t]&&"function"==typeof this._refreshHandlers[t]?(Rt.debug("getting refreshed jwt token from federation provider"),this._providerRefreshWithRetry({refreshHandler:this._refreshHandlers[t],provider:t,user:n})):(Rt.debug("no refresh handler for provider:",t),this.clear(),Promise.reject("no refresh handler for provider"))},e.prototype._providerRefreshWithRetry=function(e){var t=this,n=e.refreshHandler,r=e.provider,i=e.user;return Object(a.b)(n,[],1e4).then((function(e){return Rt.debug("refresh federated token sucessfully",e),t._setCredentialsFromFederation({provider:r,token:e.token,user:i,identity_id:e.identity_id,expires_at:e.expires_at})})).catch((function(e){return"string"==typeof e&&0===e.toLowerCase().lastIndexOf("network error",e.length)||t.clear(),Rt.debug("refresh federated token failed",e),Promise.reject("refreshing federation token failed: "+e)}))},e.prototype._isExpired=function(e){if(!e)return Rt.debug("no credentials for expiration check"),!0;Rt.debug("are these credentials expired?",e);var t=Date.now();return e.expiration.getTime()<=t},e.prototype._isPastTTL=function(){return this._nextCredentialsRefresh<=Date.now()},e.prototype._setCredentialsForGuest=function(){return Pt(this,void 0,void 0,(function(){var e,t,n,r,i,o,s,a=this;return Nt(this,(function(c){switch(c.label){case 0:if(Rt.debug("setting credentials for guest"),e=this._config,t=e.identityPoolId,n=e.region,e.mandatorySignIn)return[2,Promise.reject("cannot get guest credentials when mandatory signin enabled")];if(!t)return Rt.debug("No Cognito Identity pool provided for unauthenticated access"),[2,Promise.reject("No Cognito Identity pool provided for unauthenticated access")];if(!n)return Rt.debug("region is not configured for getting the credentials"),[2,Promise.reject("region is not configured for getting the credentials")];r=void 0,c.label=1;case 1:return c.trys.push([1,3,,4]),[4,this._storageSync];case 2:return c.sent(),r=this._storage.getItem("CognitoIdentityId-"+t),this._identityId=r,[3,4];case 3:return i=c.sent(),Rt.debug("Failed to get the cached identityId",i),[3,4];case 4:return o=new Ct({region:n,customUserAgent:Object(u.b)()}),s=void 0,s=r?nt({identityId:r,client:o})():function(){return Pt(a,void 0,void 0,(function(){var e;return Nt(this,(function(n){switch(n.label){case 0:return[4,o.send(new rt({IdentityPoolId:t}))];case 1:return e=n.sent().IdentityId,this._identityId=e,[2,nt({client:o,identityId:e})()]}}))}))}().catch((function(e){return Pt(a,void 0,void 0,(function(){return Nt(this,(function(t){throw e}))}))})),[2,this._loadCredentials(s,"guest",!1,null).then((function(e){return e})).catch((function(e){return Pt(a,void 0,void 0,(function(){var n=this;return Nt(this,(function(i){return"ResourceNotFoundException"===e.name&&e.message==="Identity '"+r+"' not found."?(Rt.debug("Failed to load guest credentials"),this._storage.removeItem("CognitoIdentityId-"+t),s=function(){return Pt(n,void 0,void 0,(function(){var e;return Nt(this,(function(n){switch(n.label){case 0:return[4,o.send(new rt({IdentityPoolId:t}))];case 1:return e=n.sent().IdentityId,this._identityId=e,[2,nt({client:o,identityId:e})()]}}))}))}().catch((function(e){return Pt(n,void 0,void 0,(function(){return Nt(this,(function(t){throw e}))}))})),[2,this._loadCredentials(s,"guest",!1,null)]):[2,e]}))}))}))]}}))}))},e.prototype._setCredentialsFromFederation=function(e){var t=e.provider,n=e.token,r=e.identity_id,i={google:"accounts.google.com",facebook:"graph.facebook.com",amazon:"www.amazon.com",developer:"cognito-identity.amazonaws.com"}[t]||t;if(!i)return Promise.reject("You must specify a federated provider");var o={};o[i]=n;var s=this._config,a=s.identityPoolId,c=s.region;if(!a)return Rt.debug("No Cognito Federated Identity pool provided"),Promise.reject("No Cognito Federated Identity pool provided");if(!c)return Rt.debug("region is not configured for getting the credentials"),Promise.reject("region is not configured for getting the credentials");var f=new Ct({region:c,customUserAgent:Object(u.b)()}),l=void 0;r?l=nt({identityId:r,logins:o,client:f})():l=st({logins:o,identityPoolId:a,client:f})();return this._loadCredentials(l,"federated",!0,e)},e.prototype._setCredentialsFromSession=function(e){var t=this;Rt.debug("set credentials from session");var n=e.getIdToken().getJwtToken(),r=this._config,i=r.region,o=r.userPoolId,s=r.identityPoolId;if(!s)return Rt.debug("No Cognito Federated Identity pool provided"),Promise.reject("No Cognito Federated Identity pool provided");if(!i)return Rt.debug("region is not configured for getting the credentials"),Promise.reject("region is not configured for getting the credentials");var a={};a["cognito-idp."+i+".amazonaws.com/"+o]=n;var c=new Ct({region:i,customUserAgent:Object(u.b)()}),f=Pt(t,void 0,void 0,(function(){var e;return Nt(this,(function(t){switch(t.label){case 0:return[4,c.send(new rt({IdentityPoolId:s,Logins:a}))];case 1:return e=t.sent().IdentityId,this._identityId=e,[2,nt({client:c,logins:a,identityId:e})()]}}))})).catch((function(e){return Pt(t,void 0,void 0,(function(){return Nt(this,(function(t){throw e}))}))}));return this._loadCredentials(f,"userPool",!0,null)},e.prototype._loadCredentials=function(e,t,n,r){var i=this,o=this,s=this._config.identityPoolId;return new Promise((function(a,u){e.then((function(e){return Pt(i,void 0,void 0,(function(){var i,u,c,f,l,d;return Nt(this,(function(h){switch(h.label){case 0:if(Rt.debug("Load credentials successfully",e),this._identityId&&!e.identityId&&(e.identityId=this._identityId),o._credentials=e,o._credentials.authenticated=n,o._credentials_source=t,o._nextCredentialsRefresh=(new Date).getTime()+3e6,"federated"===t){i=Object.assign({id:this._credentials.identityId},r.user),u=r.provider,c=r.token,f=r.expires_at,l=r.identity_id;try{this._storage.setItem("aws-amplify-federatedInfo",JSON.stringify({provider:u,token:c,user:i,expires_at:f,identity_id:l}))}catch(e){Rt.debug("Failed to put federated info into auth storage",e)}}if("guest"!==t)return[3,4];h.label=1;case 1:return h.trys.push([1,3,,4]),[4,this._storageSync];case 2:return h.sent(),this._storage.setItem("CognitoIdentityId-"+s,e.identityId),[3,4];case 3:return d=h.sent(),Rt.debug("Failed to cache identityId",d),[3,4];case 4:return a(o._credentials),[2]}}))}))})).catch((function(t){if(t)return Rt.debug("Failed to load credentials",e),Rt.debug("Error loading credentials",t),void u(t)}))}))},e.prototype.set=function(e,t){return"session"===t?this._setCredentialsFromSession(e):"federation"===t?this._setCredentialsFromFederation(e):"guest"===t?this._setCredentialsForGuest():(Rt.debug("no source specified for setting credentials"),Promise.reject("invalid source"))},e.prototype.clear=function(){return Pt(this,void 0,void 0,(function(){return Nt(this,(function(e){return this._credentials=null,this._credentials_source=null,Rt.debug("removing aws-amplify-federatedInfo from storage"),this._storage.removeItem("aws-amplify-federatedInfo"),[2]}))}))},e.prototype.shear=function(e){return{accessKeyId:e.accessKeyId,sessionToken:e.sessionToken,secretAccessKey:e.secretAccessKey,identityId:e.identityId,authenticated:e.authenticated}},e}())(null);c.a.register(Lt)},function(e,t,n){var r,i,o;e.exports=(o=n(32),i=(r=o).lib.WordArray,r.enc.Base64={stringify:function(e){var t=e.words,n=e.sigBytes,r=this._map;e.clamp();for(var i=[],o=0;o<n;o+=3)for(var s=(t[o>>>2]>>>24-o%4*8&255)<<16|(t[o+1>>>2]>>>24-(o+1)%4*8&255)<<8|t[o+2>>>2]>>>24-(o+2)%4*8&255,a=0;a<4&&o+.75*a<n;a++)i.push(r.charAt(s>>>6*(3-a)&63));var u=r.charAt(64);if(u)for(;i.length%4;)i.push(u);return i.join("")},parse:function(e){var t=e.length,n=this._map,r=this._reverseMap;if(!r){r=this._reverseMap=[];for(var o=0;o<n.length;o++)r[n.charCodeAt(o)]=o}var s=n.charAt(64);if(s){var a=e.indexOf(s);-1!==a&&(t=a)}return function(e,t,n){for(var r=[],o=0,s=0;s<t;s++)if(s%4){var a=n[e.charCodeAt(s-1)]<<s%4*2,u=n[e.charCodeAt(s)]>>>6-s%4*2;r[o>>>2]|=(a|u)<<24-o%4*8,o++}return i.create(r,o)}(e,t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="},o.enc.Base64)},function(e,t,n){"use strict";function r(e,t){for(var n,r=/\r\n|[\n\r]/g,i=1,o=t+1;(n=r.exec(e.body))&&n.index<t;)i+=1,o=t+1-(n.index+n[0].length);return{line:i,column:o}}function i(e,t){var n=e.locationOffset.column-1,r=o(n)+e.body,i=t.line-1,s=e.locationOffset.line-1,a=t.line+s,u=1===t.line?n:0,c=t.column+u,f=r.split(/\r\n|[\n\r]/g);return"".concat(e.name," (").concat(a,":").concat(c,")\n")+function(e){var t=e.filter((function(e){e[0];return void 0!==e[1]})),n=0,r=!0,i=!1,s=void 0;try{for(var a,u=t[Symbol.iterator]();!(r=(a=u.next()).done);r=!0){var c=a.value[0];n=Math.max(n,c.length)}}catch(e){i=!0,s=e}finally{try{r||null==u.return||u.return()}finally{if(i)throw s}}return t.map((function(e){var t,r=e[0],i=e[1];return o(n-(t=r).length)+t+i})).join("\n")}([["".concat(a-1,": "),f[i-1]],["".concat(a,": "),f[i]],["",o(c-1)+"^"],["".concat(a+1,": "),f[i+1]]])}function o(e){return Array(e+1).join(" ")}function s(e,t,n,i,o,a,u){var c=Array.isArray(t)?0!==t.length?t:void 0:t?[t]:void 0,f=n;if(!f&&c){var l=c[0];f=l&&l.loc&&l.loc.source}var d,h=i;!h&&c&&(h=c.reduce((function(e,t){return t.loc&&e.push(t.loc.start),e}),[])),h&&0===h.length&&(h=void 0),i&&n?d=i.map((function(e){return r(n,e)})):c&&(d=c.reduce((function(e,t){return t.loc&&e.push(r(t.loc.source,t.loc.start)),e}),[]));var p=u||a&&a.extensions;Object.defineProperties(this,{message:{value:e,enumerable:!0,writable:!0},locations:{value:d||void 0,enumerable:Boolean(d)},path:{value:o||void 0,enumerable:Boolean(o)},nodes:{value:c||void 0},source:{value:f||void 0},positions:{value:h||void 0},originalError:{value:a},extensions:{value:p||void 0,enumerable:Boolean(p)}}),a&&a.stack?Object.defineProperty(this,"stack",{value:a.stack,writable:!0,configurable:!0}):Error.captureStackTrace?Error.captureStackTrace(this,s):Object.defineProperty(this,"stack",{value:Error().stack,writable:!0,configurable:!0})}n.d(t,"a",(function(){return s})),s.prototype=Object.create(Error.prototype,{constructor:{value:s},name:{value:"GraphQLError"},toString:{value:function(){return function(e){var t=[];if(e.nodes){var n=!0,o=!1,s=void 0;try{for(var a,u=e.nodes[Symbol.iterator]();!(n=(a=u.next()).done);n=!0){var c=a.value;c.loc&&t.push(i(c.loc.source,r(c.loc.source,c.loc.start)))}}catch(e){o=!0,s=e}finally{try{n||null==u.return||u.return()}finally{if(o)throw s}}}else if(e.source&&e.locations){var f=e.source,l=!0,d=!1,h=void 0;try{for(var p,v=e.locations[Symbol.iterator]();!(l=(p=v.next()).done);l=!0){var g=p.value;t.push(i(f,g))}}catch(e){d=!0,h=e}finally{try{l||null==v.return||v.return()}finally{if(d)throw h}}}return 0===t.length?e.message:[e.message].concat(t).join("\n\n")+"\n"}(this)}}})},function(e,t,n){"use strict";(function(t){void 0===t||!t.version||0===t.version.indexOf("v0.")||0===t.version.indexOf("v1.")&&0!==t.version.indexOf("v1.8.")?e.exports={nextTick:function(e,n,r,i){if("function"!=typeof e)throw new TypeError('"callback" argument must be a function');var o,s,a=arguments.length;switch(a){case 0:case 1:return t.nextTick(e);case 2:return t.nextTick((function(){e.call(null,n)}));case 3:return t.nextTick((function(){e.call(null,n,r)}));case 4:return t.nextTick((function(){e.call(null,n,r,i)}));default:for(o=new Array(a-1),s=0;s<o.length;)o[s++]=arguments[s];return t.nextTick((function(){e.apply(null,o)}))}}}:e.exports=t}).call(this,n(20))},function(e,t,n){var r=n(8).Buffer;function i(e){r.isBuffer(e)||(e=r.from(e));for(var t=e.length/4|0,n=new Array(t),i=0;i<t;i++)n[i]=e.readUInt32BE(4*i);return n}function o(e){for(;0<e.length;e++)e[0]=0}function s(e,t,n,r,i){for(var o,s,a,u,c=n[0],f=n[1],l=n[2],d=n[3],h=e[0]^t[0],p=e[1]^t[1],v=e[2]^t[2],g=e[3]^t[3],m=4,b=1;b<i;b++)o=c[h>>>24]^f[p>>>16&255]^l[v>>>8&255]^d[255&g]^t[m++],s=c[p>>>24]^f[v>>>16&255]^l[g>>>8&255]^d[255&h]^t[m++],a=c[v>>>24]^f[g>>>16&255]^l[h>>>8&255]^d[255&p]^t[m++],u=c[g>>>24]^f[h>>>16&255]^l[p>>>8&255]^d[255&v]^t[m++],h=o,p=s,v=a,g=u;return o=(r[h>>>24]<<24|r[p>>>16&255]<<16|r[v>>>8&255]<<8|r[255&g])^t[m++],s=(r[p>>>24]<<24|r[v>>>16&255]<<16|r[g>>>8&255]<<8|r[255&h])^t[m++],a=(r[v>>>24]<<24|r[g>>>16&255]<<16|r[h>>>8&255]<<8|r[255&p])^t[m++],u=(r[g>>>24]<<24|r[h>>>16&255]<<16|r[p>>>8&255]<<8|r[255&v])^t[m++],[o>>>=0,s>>>=0,a>>>=0,u>>>=0]}var a=[0,1,2,4,8,16,32,64,128,27,54],u=function(){for(var e=new Array(256),t=0;t<256;t++)e[t]=t<128?t<<1:t<<1^283;for(var n=[],r=[],i=[[],[],[],[]],o=[[],[],[],[]],s=0,a=0,u=0;u<256;++u){var c=a^a<<1^a<<2^a<<3^a<<4;c=c>>>8^255&c^99,n[s]=c,r[c]=s;var f=e[s],l=e[f],d=e[l],h=257*e[c]^16843008*c;i[0][s]=h<<24|h>>>8,i[1][s]=h<<16|h>>>16,i[2][s]=h<<8|h>>>24,i[3][s]=h,h=16843009*d^65537*l^257*f^16843008*s,o[0][c]=h<<24|h>>>8,o[1][c]=h<<16|h>>>16,o[2][c]=h<<8|h>>>24,o[3][c]=h,0===s?s=a=1:(s=f^e[e[e[d^f]]],a^=e[e[a]])}return{SBOX:n,INV_SBOX:r,SUB_MIX:i,INV_SUB_MIX:o}}();function c(e){this._key=i(e),this._reset()}c.blockSize=16,c.keySize=32,c.prototype.blockSize=c.blockSize,c.prototype.keySize=c.keySize,c.prototype._reset=function(){for(var e=this._key,t=e.length,n=t+6,r=4*(n+1),i=[],o=0;o<t;o++)i[o]=e[o];for(o=t;o<r;o++){var s=i[o-1];o%t==0?(s=s<<8|s>>>24,s=u.SBOX[s>>>24]<<24|u.SBOX[s>>>16&255]<<16|u.SBOX[s>>>8&255]<<8|u.SBOX[255&s],s^=a[o/t|0]<<24):t>6&&o%t==4&&(s=u.SBOX[s>>>24]<<24|u.SBOX[s>>>16&255]<<16|u.SBOX[s>>>8&255]<<8|u.SBOX[255&s]),i[o]=i[o-t]^s}for(var c=[],f=0;f<r;f++){var l=r-f,d=i[l-(f%4?0:4)];c[f]=f<4||l<=4?d:u.INV_SUB_MIX[0][u.SBOX[d>>>24]]^u.INV_SUB_MIX[1][u.SBOX[d>>>16&255]]^u.INV_SUB_MIX[2][u.SBOX[d>>>8&255]]^u.INV_SUB_MIX[3][u.SBOX[255&d]]}this._nRounds=n,this._keySchedule=i,this._invKeySchedule=c},c.prototype.encryptBlockRaw=function(e){return s(e=i(e),this._keySchedule,u.SUB_MIX,u.SBOX,this._nRounds)},c.prototype.encryptBlock=function(e){var t=this.encryptBlockRaw(e),n=r.allocUnsafe(16);return n.writeUInt32BE(t[0],0),n.writeUInt32BE(t[1],4),n.writeUInt32BE(t[2],8),n.writeUInt32BE(t[3],12),n},c.prototype.decryptBlock=function(e){var t=(e=i(e))[1];e[1]=e[3],e[3]=t;var n=s(e,this._invKeySchedule,u.INV_SUB_MIX,u.INV_SBOX,this._nRounds),o=r.allocUnsafe(16);return o.writeUInt32BE(n[0],0),o.writeUInt32BE(n[3],4),o.writeUInt32BE(n[2],8),o.writeUInt32BE(n[1],12),o},c.prototype.scrub=function(){o(this._keySchedule),o(this._invKeySchedule),o(this._key)},e.exports.AES=c},function(e,t,n){var r=n(8).Buffer,i=n(113);e.exports=function(e,t,n,o){if(r.isBuffer(e)||(e=r.from(e,"binary")),t&&(r.isBuffer(t)||(t=r.from(t,"binary")),8!==t.length))throw new RangeError("salt should be Buffer with 8 byte length");for(var s=n/8,a=r.alloc(s),u=r.alloc(o||0),c=r.alloc(0);s>0||o>0;){var f=new i;f.update(c),f.update(e),t&&f.update(t),c=f.digest();var l=0;if(s>0){var d=a.length-s;l=Math.min(s,c.length),c.copy(a,d,0,l),s-=l}if(l<c.length&&o>0){var h=u.length-o,p=Math.min(o,c.length-l);c.copy(u,h,l,l+p),o-=p}}return c.fill(0),{key:a,iv:u}}},function(e,t,n){"use strict";var r=n(29),i=n(47),o=i.getNAF,s=i.getJSF,a=i.assert;function u(e,t){this.type=e,this.p=new r(t.p,16),this.red=t.prime?r.red(t.prime):r.mont(this.p),this.zero=new r(0).toRed(this.red),this.one=new r(1).toRed(this.red),this.two=new r(2).toRed(this.red),this.n=t.n&&new r(t.n,16),this.g=t.g&&this.pointFromJSON(t.g,t.gRed),this._wnafT1=new Array(4),this._wnafT2=new Array(4),this._wnafT3=new Array(4),this._wnafT4=new Array(4),this._bitLength=this.n?this.n.bitLength():0;var n=this.n&&this.p.div(this.n);!n||n.cmpn(100)>0?this.redN=null:(this._maxwellTrick=!0,this.redN=this.n.toRed(this.red))}function c(e,t){this.curve=e,this.type=t,this.precomputed=null}e.exports=u,u.prototype.point=function(){throw new Error("Not implemented")},u.prototype.validate=function(){throw new Error("Not implemented")},u.prototype._fixedNafMul=function(e,t){a(e.precomputed);var n=e._getDoubles(),r=o(t,1,this._bitLength),i=(1<<n.step+1)-(n.step%2==0?2:1);i/=3;var s,u,c=[];for(s=0;s<r.length;s+=n.step){u=0;for(var f=s+n.step-1;f>=s;f--)u=(u<<1)+r[f];c.push(u)}for(var l=this.jpoint(null,null,null),d=this.jpoint(null,null,null),h=i;h>0;h--){for(s=0;s<c.length;s++)(u=c[s])===h?d=d.mixedAdd(n.points[s]):u===-h&&(d=d.mixedAdd(n.points[s].neg()));l=l.add(d)}return l.toP()},u.prototype._wnafMul=function(e,t){var n=4,r=e._getNAFPoints(n);n=r.wnd;for(var i=r.points,s=o(t,n,this._bitLength),u=this.jpoint(null,null,null),c=s.length-1;c>=0;c--){for(var f=0;c>=0&&0===s[c];c--)f++;if(c>=0&&f++,u=u.dblp(f),c<0)break;var l=s[c];a(0!==l),u="affine"===e.type?l>0?u.mixedAdd(i[l-1>>1]):u.mixedAdd(i[-l-1>>1].neg()):l>0?u.add(i[l-1>>1]):u.add(i[-l-1>>1].neg())}return"affine"===e.type?u.toP():u},u.prototype._wnafMulAdd=function(e,t,n,r,i){var a,u,c,f=this._wnafT1,l=this._wnafT2,d=this._wnafT3,h=0;for(a=0;a<r;a++){var p=(c=t[a])._getNAFPoints(e);f[a]=p.wnd,l[a]=p.points}for(a=r-1;a>=1;a-=2){var v=a-1,g=a;if(1===f[v]&&1===f[g]){var m=[t[v],null,null,t[g]];0===t[v].y.cmp(t[g].y)?(m[1]=t[v].add(t[g]),m[2]=t[v].toJ().mixedAdd(t[g].neg())):0===t[v].y.cmp(t[g].y.redNeg())?(m[1]=t[v].toJ().mixedAdd(t[g]),m[2]=t[v].add(t[g].neg())):(m[1]=t[v].toJ().mixedAdd(t[g]),m[2]=t[v].toJ().mixedAdd(t[g].neg()));var b=[-3,-1,-5,-7,0,7,5,1,3],y=s(n[v],n[g]);for(h=Math.max(y[0].length,h),d[v]=new Array(h),d[g]=new Array(h),u=0;u<h;u++){var w=0|y[0][u],_=0|y[1][u];d[v][u]=b[3*(w+1)+(_+1)],d[g][u]=0,l[v]=m}}else d[v]=o(n[v],f[v],this._bitLength),d[g]=o(n[g],f[g],this._bitLength),h=Math.max(d[v].length,h),h=Math.max(d[g].length,h)}var S=this.jpoint(null,null,null),E=this._wnafT4;for(a=h;a>=0;a--){for(var M=0;a>=0;){var A=!0;for(u=0;u<r;u++)E[u]=0|d[u][a],0!==E[u]&&(A=!1);if(!A)break;M++,a--}if(a>=0&&M++,S=S.dblp(M),a<0)break;for(u=0;u<r;u++){var I=E[u];0!==I&&(I>0?c=l[u][I-1>>1]:I<0&&(c=l[u][-I-1>>1].neg()),S="affine"===c.type?S.mixedAdd(c):S.add(c))}}for(a=0;a<r;a++)l[a]=null;return i?S:S.toP()},u.BasePoint=c,c.prototype.eq=function(){throw new Error("Not implemented")},c.prototype.validate=function(){return this.curve.validate(this)},u.prototype.decodePoint=function(e,t){e=i.toArray(e,t);var n=this.p.byteLength();if((4===e[0]||6===e[0]||7===e[0])&&e.length-1==2*n)return 6===e[0]?a(e[e.length-1]%2==0):7===e[0]&&a(e[e.length-1]%2==1),this.point(e.slice(1,1+n),e.slice(1+n,1+2*n));if((2===e[0]||3===e[0])&&e.length-1===n)return this.pointFromX(e.slice(1,1+n),3===e[0]);throw new Error("Unknown point format")},c.prototype.encodeCompressed=function(e){return this.encode(e,!0)},c.prototype._encode=function(e){var t=this.curve.p.byteLength(),n=this.getX().toArray("be",t);return e?[this.getY().isEven()?2:3].concat(n):[4].concat(n,this.getY().toArray("be",t))},c.prototype.encode=function(e,t){return i.encode(this._encode(t),e)},c.prototype.precompute=function(e){if(this.precomputed)return this;var t={doubles:null,naf:null,beta:null};return t.naf=this._getNAFPoints(8),t.doubles=this._getDoubles(4,e),t.beta=this._getBeta(),this.precomputed=t,this},c.prototype._hasDoubles=function(e){if(!this.precomputed)return!1;var t=this.precomputed.doubles;return!!t&&t.points.length>=Math.ceil((e.bitLength()+1)/t.step)},c.prototype._getDoubles=function(e,t){if(this.precomputed&&this.precomputed.doubles)return this.precomputed.doubles;for(var n=[this],r=this,i=0;i<t;i+=e){for(var o=0;o<e;o++)r=r.dbl();n.push(r)}return{step:e,points:n}},c.prototype._getNAFPoints=function(e){if(this.precomputed&&this.precomputed.naf)return this.precomputed.naf;for(var t=[this],n=(1<<e)-1,r=1===n?null:this.dbl(),i=1;i<n;i++)t[i]=t[i-1].add(r);return{wnd:e,points:t}},c.prototype._getBeta=function(){return null},c.prototype.dblp=function(e){for(var t=this,n=0;n<e;n++)t=t.dbl();return t}},function(e,t,n){var r=n(350),i=n(357),o=n(358),s=n(122),a=n(178),u=n(8).Buffer;function c(e){var t;"object"!=typeof e||u.isBuffer(e)||(t=e.passphrase,e=e.key),"string"==typeof e&&(e=u.from(e));var n,c,f=o(e,t),l=f.tag,d=f.data;switch(l){case"CERTIFICATE":c=r.certificate.decode(d,"der").tbsCertificate.subjectPublicKeyInfo;case"PUBLIC KEY":switch(c||(c=r.PublicKey.decode(d,"der")),n=c.algorithm.algorithm.join(".")){case"1.2.840.113549.1.1.1":return r.RSAPublicKey.decode(c.subjectPublicKey.data,"der");case"1.2.840.10045.2.1":return c.subjectPrivateKey=c.subjectPublicKey,{type:"ec",data:c};case"1.2.840.10040.4.1":return c.algorithm.params.pub_key=r.DSAparam.decode(c.subjectPublicKey.data,"der"),{type:"dsa",data:c.algorithm.params};default:throw new Error("unknown key id "+n)}case"ENCRYPTED PRIVATE KEY":d=function(e,t){var n=e.algorithm.decrypt.kde.kdeparams.salt,r=parseInt(e.algorithm.decrypt.kde.kdeparams.iters.toString(),10),o=i[e.algorithm.decrypt.cipher.algo.join(".")],c=e.algorithm.decrypt.cipher.iv,f=e.subjectPrivateKey,l=parseInt(o.split("-")[1],10)/8,d=a.pbkdf2Sync(t,n,r,l,"sha1"),h=s.createDecipheriv(o,d,c),p=[];return p.push(h.update(f)),p.push(h.final()),u.concat(p)}(d=r.EncryptedPrivateKey.decode(d,"der"),t);case"PRIVATE KEY":switch(n=(c=r.PrivateKey.decode(d,"der")).algorithm.algorithm.join(".")){case"1.2.840.113549.1.1.1":return r.RSAPrivateKey.decode(c.subjectPrivateKey,"der");case"1.2.840.10045.2.1":return{curve:c.algorithm.curve,privateKey:r.ECPrivateKey.decode(c.subjectPrivateKey,"der").privateKey};case"1.2.840.10040.4.1":return c.algorithm.params.priv_key=r.DSAparam.decode(c.subjectPrivateKey,"der"),{type:"dsa",params:c.algorithm.params};default:throw new Error("unknown key id "+n)}case"RSA PUBLIC KEY":return r.RSAPublicKey.decode(d,"der");case"RSA PRIVATE KEY":return r.RSAPrivateKey.decode(d,"der");case"DSA PRIVATE KEY":return{type:"dsa",params:r.DSAPrivateKey.decode(d,"der")};case"EC PRIVATE KEY":return{curve:(d=r.ECPrivateKey.decode(d,"der")).parameters.value,privateKey:d.privateKey};default:throw new Error("unknown key type "+l)}}e.exports=c,c.signature=r.signature},function(e,t,n){var r=n(53).Symbol;e.exports=r},function(e,t,n){var r=n(72)(Object,"create");e.exports=r},function(e,t,n){var r=n(406),i=n(407),o=n(408),s=n(409),a=n(410);function u(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t<n;){var r=e[t];this.set(r[0],r[1])}}u.prototype.clear=r,u.prototype.delete=i,u.prototype.get=o,u.prototype.has=s,u.prototype.set=a,e.exports=u},function(e,t,n){var r=n(227);e.exports=function(e,t){for(var n=e.length;n--;)if(r(e[n][0],t))return n;return-1}},function(e,t,n){var r=n(412);e.exports=function(e,t){var n=e.__data__;return r(t)?n["string"==typeof t?"string":"hash"]:n.map}},function(e,t,n){"use strict";const r=n(54),i=n(54).buildOptions,o=n(460);"<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)".replace(/NAME/g,r.nameRegexp);!Number.parseInt&&window.parseInt&&(Number.parseInt=window.parseInt),!Number.parseFloat&&window.parseFloat&&(Number.parseFloat=window.parseFloat);const s={attributeNamePrefix:"@_",attrNodeName:!1,textNodeName:"#text",ignoreAttributes:!0,ignoreNameSpace:!1,allowBooleanAttributes:!1,parseNodeValue:!0,parseAttributeValue:!1,arrayMode:!1,trimValues:!0,cdataTagName:!1,cdataPositionChar:"\\c",tagValueProcessor:function(e,t){return e},attrValueProcessor:function(e,t){return e},stopNodes:[]};t.defaultOptions=s;const a=["attributeNamePrefix","attrNodeName","textNodeName","ignoreAttributes","ignoreNameSpace","allowBooleanAttributes","parseNodeValue","parseAttributeValue","arrayMode","trimValues","cdataTagName","cdataPositionChar","tagValueProcessor","attrValueProcessor","parseTrueNumberOnly","stopNodes"];function u(e,t,n){return t&&(n.trimValues&&(t=t.trim()),t=f(t=n.tagValueProcessor(t,e),n.parseNodeValue,n.parseTrueNumberOnly)),t}function c(e,t){if(t.ignoreNameSpace){const t=e.split(":"),n="/"===e.charAt(0)?"/":"";if("xmlns"===t[0])return"";2===t.length&&(e=n+t[1])}return e}function f(e,t,n){if(t&&"string"==typeof e){let t;return""===e.trim()||isNaN(e)?t="true"===e||"false"!==e&&e:(-1!==e.indexOf("0x")?t=Number.parseInt(e,16):-1!==e.indexOf(".")?(t=Number.parseFloat(e),e=e.replace(/\.?0+$/,"")):t=Number.parseInt(e,10),n&&(t=String(t)===e?t:e)),t}return r.isExist(e)?e:""}t.props=a;const l=new RegExp("([^\\s=]+)\\s*(=\\s*(['\"])(.*?)\\3)?","g");function d(e,t){if(!t.ignoreAttributes&&"string"==typeof e){e=e.replace(/\r?\n/g," ");const n=r.getAllMatches(e,l),i=n.length,o={};for(let e=0;e<i;e++){const r=c(n[e][1],t);r.length&&(void 0!==n[e][4]?(t.trimValues&&(n[e][4]=n[e][4].trim()),n[e][4]=t.attrValueProcessor(n[e][4],r),o[t.attributeNamePrefix+r]=f(n[e][4],t.parseAttributeValue,t.parseTrueNumberOnly)):t.allowBooleanAttributes&&(o[t.attributeNamePrefix+r]=!0))}if(!Object.keys(o).length)return;if(t.attrNodeName){const e={};return e[t.attrNodeName]=o,e}return o}}function h(e,t){let n,r="";for(let i=t;i<e.length;i++){let t=e[i];if(n)t===n&&(n="");else if('"'===t||"'"===t)n=t;else{if(">"===t)return{data:r,index:i};"\t"===t&&(t=" ")}r+=t}}function p(e,t,n,r){const i=e.indexOf(t,n);if(-1===i)throw new Error(r);return i+t.length-1}t.getTraversalObj=function(e,t){e=e.replace(/\r\n?/g,"\n"),t=i(t,s,a);const n=new o("!xml");let c=n,f="";for(let n=0;n<e.length;n++){if("<"===e[n])if("/"===e[n+1]){const i=p(e,">",n,"Closing Tag is not closed.");let o=e.substring(n+2,i).trim();if(t.ignoreNameSpace){const e=o.indexOf(":");-1!==e&&(o=o.substr(e+1))}c&&(c.val?c.val=r.getValue(c.val)+""+u(o,f,t):c.val=u(o,f,t)),t.stopNodes.length&&t.stopNodes.includes(c.tagname)&&(c.child=[],null==c.attrsMap&&(c.attrsMap={}),c.val=e.substr(c.startIndex+1,n-c.startIndex-1)),c=c.parent,f="",n=i}else if("?"===e[n+1])n=p(e,"?>",n,"Pi Tag is not closed.");else if("!--"===e.substr(n+1,3))n=p(e,"--\x3e",n,"Comment is not closed.");else if("!D"===e.substr(n+1,2)){const t=p(e,">",n,"DOCTYPE is not closed.");n=e.substring(n,t).indexOf("[")>=0?e.indexOf("]>",n)+1:t}else if("!["===e.substr(n+1,2)){const i=p(e,"]]>",n,"CDATA is not closed.")-2,s=e.substring(n+9,i);if(f&&(c.val=r.getValue(c.val)+""+u(c.tagname,f,t),f=""),t.cdataTagName){const e=new o(t.cdataTagName,c,s);c.addChild(e),c.val=r.getValue(c.val)+t.cdataPositionChar,s&&(e.val=s)}else c.val=(c.val||"")+(s||"");n=i+2}else{const i=h(e,n+1);let s=i.data;const a=i.index,l=s.indexOf(" ");let p=s;if(-1!==l&&(p=s.substr(0,l).replace(/\s\s*$/,""),s=s.substr(l+1)),t.ignoreNameSpace){const e=p.indexOf(":");-1!==e&&(p=p.substr(e+1))}if(c&&f&&"!xml"!==c.tagname&&(c.val=r.getValue(c.val)+""+u(c.tagname,f,t)),s.length>0&&s.lastIndexOf("/")===s.length-1){"/"===p[p.length-1]?(p=p.substr(0,p.length-1),s=p):s=s.substr(0,s.length-1);const e=new o(p,c,"");p!==s&&(e.attrsMap=d(s,t)),c.addChild(e)}else{const e=new o(p,c);t.stopNodes.length&&t.stopNodes.includes(e.tagname)&&(e.startIndex=a),p!==s&&(e.attrsMap=d(s,t)),c.addChild(e),c=e}f="",n=a}else f+=e[n]}return n}},function(e,t,n){"use strict";n.d(t,"a",(function(){return i})),n.d(t,"b",(function(){return o})),n.d(t,"c",(function(){return s}));var r="undefined"!=typeof Symbol&&"function"==typeof Symbol.for,i=r?Symbol.for("INTERNAL_AWS_APPSYNC_PUBSUB_PROVIDER"):"@@INTERNAL_AWS_APPSYNC_PUBSUB_PROVIDER",o=r?Symbol.for("INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER"):"@@INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER",s="x-amz-user-agent"},function(e,t,n){"use strict";n.d(t,"a",(function(){return _}));var r=n(44),i=n(148),o=n(28),s=n(16),a=n(77);function u(e){return(u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var c=function(){return(c=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},f=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]])}return n},l=new r.a("Signer"),d=function(e,t){var n=new i.Sha256(e);return n.update(t),n.digestSync()},h=function(e){var t=e||"",n=new i.Sha256;return n.update(t),Object(o.b)(n.digestSync())},p=function(e){return Object.keys(e).map((function(e){return e.toLowerCase()})).sort().join(";")},v=function(e){var t,n,r=Object(s.parse)(e.url);return[e.method||"/",encodeURIComponent(r.pathname).replace(/%2F/gi,"/"),(n=r.query,n&&0!==n.length?n.split("&").map((function(e){var t=e.split("=");if(1===t.length)return e;var n=t[1].replace(/[!'()*]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()}));return t[0]+"="+n})).sort((function(e,t){var n=e.split("=")[0],r=t.split("=")[0];return n===r?e<t?-1:1:n<r?-1:1})).join("&"):""),(t=e.headers,t&&0!==Object.keys(t).length?Object.keys(t).map((function(e){return{key:e.toLowerCase(),value:t[e]?t[e].trim().replace(/\s+/g," "):""}})).sort((function(e,t){return e.key<t.key?-1:1})).map((function(e){return e.key+":"+e.value})).join("\n")+"\n":""),p(e.headers),h(e.data)].join("\n")},g=function(e){var t=(Object(s.parse)(e.url).host.match(/([^\.]+)\.(?:([^\.]*)\.)?amazonaws\.com$/)||[]).slice(1,3);return"es"===t[1]&&(t=t.reverse()),{service:e.service||t[0],region:e.region||t[1]}},m=function(e,t,n){return[e,t,n,"aws4_request"].join("/")},b=function(e,t,n,r){return[e,n,r,h(t)].join("\n")},y=function(e,t,n){l.debug(n);var r=d("AWS4"+e,t),i=d(r,n.region),o=d(i,n.service);return d(o,"aws4_request")},w=function(e,t){return Object(o.b)(d(e,t))},_=function(){function e(){}return e.sign=function(e,t,n){void 0===n&&(n=null),e.headers=e.headers||{};var r=a.a.getDateWithClockOffset().toISOString().replace(/[:\-]|\.\d{3}/g,""),i=r.substr(0,8),o=Object(s.parse)(e.url);e.headers.host=o.host,e.headers["x-amz-date"]=r,t.session_token&&(e.headers["X-Amz-Security-Token"]=t.session_token);var u=v(e);l.debug(u);var c=n||g(e),f=m(i,c.region,c.service),d=b("AWS4-HMAC-SHA256",u,r,f),h=y(t.secret_key,i,c),_=w(h,d),S=function(e,t,n,r,i){return[e+" Credential="+t+"/"+n,"SignedHeaders="+r,"Signature="+i].join(", ")}("AWS4-HMAC-SHA256",t.access_key,f,p(e.headers),_);return e.headers.Authorization=S,e},e.signUrl=function(e,t,n,r){var i="object"===u(e)?e.url:e,o="object"===u(e)?e.method:"GET",l="object"===u(e)?e.body:void 0,d=a.a.getDateWithClockOffset().toISOString().replace(/[:\-]|\.\d{3}/g,""),h=d.substr(0,8),p=Object(s.parse)(i,!0,!0),_=(p.search,f(p,["search"])),S={host:_.host},E=n||g({url:Object(s.format)(_)}),M=E.region,A=E.service,I=m(h,M,A),k=t.session_token&&"iotdevicegateway"!==A,O=c(c(c({"X-Amz-Algorithm":"AWS4-HMAC-SHA256","X-Amz-Credential":[t.access_key,I].join("/"),"X-Amz-Date":d.substr(0,16)},k?{"X-Amz-Security-Token":""+t.session_token}:{}),r?{"X-Amz-Expires":""+r}:{}),{"X-Amz-SignedHeaders":Object.keys(S).join(",")}),x=v({method:o,url:Object(s.format)(c(c({},_),{query:c(c({},_.query),O)})),headers:S,data:l}),C=b("AWS4-HMAC-SHA256",x,d,I),T=y(t.secret_key,h,{region:M,service:A}),P=w(T,C),N=c({"X-Amz-Signature":P},t.session_token&&{"X-Amz-Security-Token":t.session_token});return Object(s.format)({protocol:_.protocol,slashes:!0,hostname:_.hostname,port:_.port,pathname:_.pathname,query:c(c(c({},_.query),O),N)})},e}()},function(e,t,n){"use strict";n.d(t,"a",(function(){return se}));var r=n(14),i=n(33),o=n(44),s=n(103),a=n(19),u=n(256),c=n(27),f=function(){return(f=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},l=new o.a("AbstractPubSubProvider"),d=function(){function e(e){void 0===e&&(e={}),this._config=e}return e.prototype.configure=function(e){return void 0===e&&(e={}),this._config=f(f({},e),this._config),l.debug("configure "+this.getProviderName(),this._config),this.options},e.prototype.getCategory=function(){return"PubSub"},Object.defineProperty(e.prototype,"options",{get:function(){return f({},this._config)},enumerable:!0,configurable:!0}),e}();function h(e){return(h="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var p,v=(p=function(e,t){return(p=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)},function(e,t){function n(){this.constructor=e}p(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),g=function(){return(g=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},m=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},b=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},y=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]])}return n},w=new o.a("MqttOverWSProvider");var _,S,E,M=function(){function e(){this.promises=new Map}return e.prototype.get=function(e,t){return m(this,void 0,void 0,(function(){var n;return b(this,(function(r){return(n=this.promises.get(e))||(n=t(e),this.promises.set(e,n)),[2,n]}))}))},Object.defineProperty(e.prototype,"allClients",{get:function(){return Array.from(this.promises.keys())},enumerable:!0,configurable:!0}),e.prototype.remove=function(e){this.promises.delete(e)},e}(),A="undefined"!=typeof Symbol?Symbol("topic"):"@@topic",I=function(e){function t(t){void 0===t&&(t={});var n=e.call(this,g(g({},t),{clientId:t.clientId||Object(c.v4)()}))||this;return n._clientsQueue=new M,n._topicObservers=new Map,n._clientIdObservers=new Map,n}return v(t,e),Object.defineProperty(t.prototype,"clientId",{get:function(){return this.options.clientId},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"endpoint",{get:function(){return this.options.aws_pubsub_endpoint},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"clientsQueue",{get:function(){return this._clientsQueue},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"isSSLEnabled",{get:function(){return!this.options.aws_appsync_dangerously_connect_to_http_endpoint_for_testing},enumerable:!0,configurable:!0}),t.prototype.getTopicForValue=function(e){return"object"===h(e)&&e[A]},t.prototype.getProviderName=function(){return"MqttOverWSProvider"},t.prototype.onDisconnect=function(e){var t=this,n=e.clientId,r=e.errorCode,i=y(e,["clientId","errorCode"]);if(0!==r){w.warn(n,JSON.stringify(g({errorCode:r},i),null,2));var o=[],s=this._clientIdObservers.get(n);if(!s)return;s.forEach((function(e){e.error("Disconnected, error code: "+r),t._topicObservers.forEach((function(t,n){t.delete(e),0===t.size&&o.push(n)}))})),this._clientIdObservers.delete(n),o.forEach((function(e){t._topicObservers.delete(e)}))}},t.prototype.newClient=function(e){var t=e.url,n=e.clientId;return m(this,void 0,void 0,(function(){var e,r=this;return b(this,(function(i){switch(i.label){case 0:return w.debug("Creating new MQTT client",n),(e=new u.Client(t,n)).onMessageArrived=function(e){var t=e.destinationName,n=e.payloadString;r._onMessage(t,n)},e.onConnectionLost=function(e){var t=e.errorCode,i=y(e,["errorCode"]);r.onDisconnect(g({clientId:n,errorCode:t},i))},[4,new Promise((function(t,n){e.connect({useSSL:r.isSSLEnabled,mqttVersion:3,onSuccess:function(){return t(e)},onFailure:n})}))];case 1:return i.sent(),[2,e]}}))}))},t.prototype.connect=function(e,t){return void 0===t&&(t={}),m(this,void 0,void 0,(function(){var n=this;return b(this,(function(r){switch(r.label){case 0:return[4,this.clientsQueue.get(e,(function(e){return n.newClient(g(g({},t),{clientId:e}))}))];case 1:return[2,r.sent()]}}))}))},t.prototype.disconnect=function(e){return m(this,void 0,void 0,(function(){var t;return b(this,(function(n){switch(n.label){case 0:return[4,this.clientsQueue.get(e,(function(){return null}))];case 1:return(t=n.sent())&&t.isConnected()&&t.disconnect(),this.clientsQueue.remove(e),[2]}}))}))},t.prototype.publish=function(e,t){return m(this,void 0,void 0,(function(){var n,r,i,o;return b(this,(function(s){switch(s.label){case 0:return n=[].concat(e),r=JSON.stringify(t),[4,this.endpoint];case 1:return i=s.sent(),[4,this.connect(this.clientId,{url:i})];case 2:return o=s.sent(),w.debug("Publishing to topic(s)",n.join(","),r),n.forEach((function(e){return o.send(e,r)})),[2]}}))}))},t.prototype._onMessage=function(e,t){try{var n=[];this._topicObservers.forEach((function(t,r){(function(e,t){for(var n=e.split("/"),r=n.length,i=t.split("/"),o=0;o<r;++o){var s=n[o],a=i[o];if("#"===s)return i.length>=r;if("+"!==s&&s!==a)return!1}return r===i.length})(r,e)&&n.push(t)}));var r=JSON.parse(t);"object"===h(r)&&(r[A]=e),n.forEach((function(e){e.forEach((function(e){return e.next(r)}))}))}catch(e){w.warn("Error handling message",e,t)}},t.prototype.subscribe=function(e,t){var n=this;void 0===t&&(t={});var i=[].concat(e);return w.debug("Subscribing to topic(s)",i.join(",")),new r.a((function(e){var r;i.forEach((function(t){var r=n._topicObservers.get(t);r||(r=new Set,n._topicObservers.set(t,r)),r.add(e)}));var o=t.clientId,s=void 0===o?n.clientId:o,a=n._clientIdObservers.get(s);return a||(a=new Set),a.add(e),n._clientIdObservers.set(s,a),m(n,void 0,void 0,(function(){var n,o,a,u;return b(this,(function(c){switch(c.label){case 0:return void 0!==(n=t.url)?[3,2]:[4,this.endpoint];case 1:return a=c.sent(),[3,3];case 2:a=n,c.label=3;case 3:o=a,c.label=4;case 4:return c.trys.push([4,6,,7]),[4,this.connect(s,{url:o})];case 5:return r=c.sent(),i.forEach((function(e){r.subscribe(e)})),[3,7];case 6:return u=c.sent(),e.error(u),[3,7];case 7:return[2]}}))})),function(){return w.debug("Unsubscribing from topic(s)",i.join(",")),r&&(n._clientIdObservers.get(s).delete(e),0===n._clientIdObservers.get(s).size&&(n.disconnect(s),n._clientIdObservers.delete(s)),i.forEach((function(t){var i=n._topicObservers.get(t)||new Set;i.delete(e),0===i.size&&(n._topicObservers.delete(t),r.isConnected()&&r.unsubscribe(t))}))),null}}))},t}(d),k=function(){var e=function(t,n){return(e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(t,n)};return function(t,n){function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}}(),O=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},x=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},C=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]])}return n},T=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},P=function(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(T(arguments[t]));return e},N=new o.a("AWSAppSyncProvider"),R=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t._topicClient=new Map,t._topicAlias=new Map,t}return k(t,e),Object.defineProperty(t.prototype,"endpoint",{get:function(){throw new Error("Not supported")},enumerable:!0,configurable:!0}),t.prototype.getProviderName=function(){return"AWSAppSyncProvider"},t.prototype.publish=function(e,t,n){return O(this,void 0,void 0,(function(){return x(this,(function(e){throw new Error("Operation not supported")}))}))},t.prototype._cleanUp=function(e){var t=this;Array.from(this._topicClient.entries()).filter((function(t){return T(t,2)[1].clientId===e})).map((function(e){return T(e,1)[0]})).forEach((function(e){return t._cleanUpForTopic(e)}))},t.prototype._cleanUpForTopic=function(e){this._topicClient.delete(e),this._topicAlias.delete(e)},t.prototype.onDisconnect=function(e){var t=this,n=e.clientId,r=e.errorCode,i=C(e,["clientId","errorCode"]);0!==r&&(Array.from(this._topicClient.entries()).filter((function(e){return T(e,2)[1].clientId===n})).map((function(e){return T(e,1)[0]})).forEach((function(e){t._topicObservers.has(e)&&(t._topicObservers.get(e).forEach((function(e){e.closed||e.error(i)})),t._topicObservers.delete(e))})),this._cleanUp(n))},t.prototype.disconnect=function(t){return O(this,void 0,void 0,(function(){return x(this,(function(n){switch(n.label){case 0:return[4,this.clientsQueue.get(t,(function(){return null}))];case 1:return n.sent(),[4,e.prototype.disconnect.call(this,t)];case 2:return n.sent(),this._cleanUp(t),[2]}}))}))},t.prototype.subscribe=function(e,t){var n=this;void 0===t&&(t={});var i=new r.a((function(r){var i=[].concat(e);return N.debug("Subscribing to topic(s)",i.join(",")),O(n,void 0,void 0,(function(){var e,n,o,s,a,u=this;return x(this,(function(c){switch(c.label){case 0:return i.forEach((function(e){u._topicObservers.has(e)||u._topicObservers.set(e,new Set),u._topicObservers.get(e).add(r)})),e=t.mqttConnections,n=void 0===e?[]:e,o=t.newSubscriptions,s=Object.entries(o).map((function(e){var t=T(e,2),n=t[0];return[t[1].topic,n]})),this._topicAlias=new Map(P(Array.from(this._topicAlias.entries()),s)),a=Object.entries(i.reduce((function(e,t){var r=n.find((function(e){return e.topics.indexOf(t)>-1}));if(r){var i=r.client,o=r.url;e[i]||(e[i]={url:o,topics:new Set}),e[i].topics.add(t)}return e}),{})),[4,Promise.all(a.map((function(e){var t=T(e,2),n=t[0],i=t[1],o=i.url,s=i.topics;return O(u,void 0,void 0,(function(){var e,t,i=this;return x(this,(function(a){switch(a.label){case 0:e=null,a.label=1;case 1:return a.trys.push([1,3,,4]),[4,this.connect(n,{clientId:n,url:o})];case 2:return e=a.sent(),[3,4];case 3:return t=a.sent(),r.error({message:"Failed to connect",error:t}),r.complete(),[2,void 0];case 4:return s.forEach((function(t){e.isConnected()&&(e.subscribe(t),i._topicClient.set(t,e))})),[2,e]}}))}))})))];case 1:return c.sent(),[2]}}))})),function(){N.debug("Unsubscribing from topic(s)",i.join(",")),i.forEach((function(e){var t=n._topicClient.get(e);t&&t.isConnected()&&(t.unsubscribe(e),n._topicClient.delete(e),Array.from(n._topicClient.values()).some((function(e){return e===t}))||n.disconnect(t.clientId)),n._topicObservers.delete(e)}))}}));return r.a.from(i).map((function(e){var t=n.getTopicForValue(e),r=n._topicAlias.get(t);return e.data=Object.entries(e.data).reduce((function(e,t){var n=T(t,2),i=n[0],o=n[1];return e[r||i]=o,e}),{}),e}))},t}(I),L=n(91),j=n(16),D=n(6),U=n(88),B=n(5),F=n(514),z=n(89),q=n(104),K=n(26),H=n(42),V=n(34),G=function(){var e=function(t,n){return(e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(t,n)};return function(t,n){function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}}(),W=function(){return(W=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},$=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},Y=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},J=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},Z=new o.a("AWSAppSyncRealTimeProvider"),X="undefined"!=typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("amplify_default"):"@@amplify_default",Q=[400,401,403];!function(e){e.GQL_CONNECTION_INIT="connection_init",e.GQL_CONNECTION_ERROR="connection_error",e.GQL_CONNECTION_ACK="connection_ack",e.GQL_START="start",e.GQL_START_ACK="start_ack",e.GQL_DATA="data",e.GQL_CONNECTION_KEEP_ALIVE="ka",e.GQL_STOP="stop",e.GQL_COMPLETE="complete",e.GQL_ERROR="error"}(_||(_={})),function(e){e[e.PENDING=0]="PENDING",e[e.CONNECTED=1]="CONNECTED",e[e.FAILED=2]="FAILED"}(S||(S={})),function(e){e[e.CLOSED=0]="CLOSED",e[e.READY=1]="READY",e[e.CONNECTING=2]="CONNECTING"}(E||(E={}));var ee={accept:"application/json, text/javascript","content-encoding":"amz-1.0","content-type":"application/json; charset=UTF-8"},te=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.socketStatus=E.CLOSED,t.keepAliveTimeout=3e5,t.subscriptionObserverMap=new Map,t.promiseArray=[],t}return G(t,e),t.prototype.getProviderName=function(){return"AWSAppSyncRealTimeProvider"},t.prototype.newClient=function(){throw new Error("Not used here")},t.prototype.publish=function(e,t,n){return $(this,void 0,void 0,(function(){return Y(this,(function(e){throw new Error("Operation not supported")}))}))},t.prototype.subscribe=function(e,t){var n=this,i=t.appSyncGraphqlEndpoint;return new r.a((function(e){if(i){var r=Object(c.v4)();return n._startSubscriptionWithAWSAppSyncRealTime({options:t,observer:e,subscriptionId:r}),function(){return $(n,void 0,void 0,(function(){var e,t;return Y(this,(function(n){switch(n.label){case 0:return n.trys.push([0,2,3,4]),[4,this._waitForSubscriptionToBeConnected(r)];case 1:if(n.sent(),!(e=(this.subscriptionObserverMap.get(r)||{}).subscriptionState))return[2];if(e!==S.CONNECTED)throw new Error("Subscription never connected");return this._sendUnsubscriptionMessage(r),[3,4];case 2:return t=n.sent(),Z.debug("Error while unsubscribing "+t),[3,4];case 3:return this._removeSubscriptionObserver(r),[7];case 4:return[2]}}))}))}}e.error({errors:[W({},new L.a("Subscribe only available for AWS AppSync endpoint"))]}),e.complete()}))},Object.defineProperty(t.prototype,"isSSLEnabled",{get:function(){return!this.options.aws_appsync_dangerously_connect_to_http_endpoint_for_testing},enumerable:!0,configurable:!0}),t.prototype._startSubscriptionWithAWSAppSyncRealTime=function(e){var t=e.options,n=e.observer,r=e.subscriptionId;return $(this,void 0,void 0,(function(){var e,i,o,a,u,c,f,l,d,h,p,v,g,m,b,y,w,E,M,A,I,k,O,x,C,T,P=this;return Y(this,(function(N){switch(N.label){case 0:return e=t.appSyncGraphqlEndpoint,i=t.authenticationType,o=t.query,a=t.variables,u=t.apiKey,c=t.region,f=t.graphql_headers,l=void 0===f?function(){return{}}:f,d=t.additionalHeaders,h=void 0===d?{}:d,p=S.PENDING,v={query:o,variables:a},this.subscriptionObserverMap.set(r,{observer:n,query:o,variables:a,subscriptionState:p,startAckTimeoutId:null}),g=JSON.stringify(v),b=[{}],[4,this._awsRealTimeHeaderBasedAuth({apiKey:u,appSyncGraphqlEndpoint:e,authenticationType:i,payload:g,canonicalUri:"",region:c})];case 1:return y=[W.apply(void 0,b.concat([N.sent()]))],[4,l()];case 2:m=W.apply(void 0,[W.apply(void 0,[W.apply(void 0,y.concat([N.sent()])),h]),(T={},T[s.c]=B.a.userAgent,T)]),w={id:r,payload:{data:g,extensions:{authorization:W({},m)}},type:_.GQL_START},E=JSON.stringify(w),N.label=3;case 3:return N.trys.push([3,5,,6]),[4,this._initializeWebSocketConnection({apiKey:u,appSyncGraphqlEndpoint:e,authenticationType:i,region:c})];case 4:return N.sent(),[3,6];case 5:return M=N.sent(),Z.debug({err:M}),A=M.message,I=void 0===A?"":A,n.error({errors:[W({},new L.a("Connection failed: "+I))]}),n.complete(),"function"==typeof(k=(this.subscriptionObserverMap.get(r)||{}).subscriptionFailedCallback)&&k(),[2];case 6:return O=this.subscriptionObserverMap.get(r),x=O.subscriptionFailedCallback,C=O.subscriptionReadyCallback,this.subscriptionObserverMap.set(r,{observer:n,subscriptionState:p,variables:a,query:o,subscriptionReadyCallback:C,subscriptionFailedCallback:x,startAckTimeoutId:setTimeout((function(){P._timeoutStartSubscriptionAck.call(P,r)}),15e3)}),this.awsRealTimeSocket&&this.awsRealTimeSocket.send(E),[2]}}))}))},t.prototype._waitForSubscriptionToBeConnected=function(e){return $(this,void 0,void 0,(function(){var t=this;return Y(this,(function(n){return this.subscriptionObserverMap.get(e).subscriptionState===S.PENDING?[2,new Promise((function(n,r){var i=t.subscriptionObserverMap.get(e),o=i.observer,s=i.subscriptionState,a=i.variables,u=i.query;t.subscriptionObserverMap.set(e,{observer:o,subscriptionState:s,variables:a,query:u,subscriptionReadyCallback:n,subscriptionFailedCallback:r})}))]:[2]}))}))},t.prototype._sendUnsubscriptionMessage=function(e){try{if(this.awsRealTimeSocket&&this.awsRealTimeSocket.readyState===WebSocket.OPEN&&this.socketStatus===E.READY){var t={id:e,type:_.GQL_STOP},n=JSON.stringify(t);this.awsRealTimeSocket.send(n)}}catch(e){Z.debug({err:e})}},t.prototype._removeSubscriptionObserver=function(e){this.subscriptionObserverMap.delete(e),setTimeout(this._closeSocketIfRequired.bind(this),1e3)},t.prototype._closeSocketIfRequired=function(){if(!(this.subscriptionObserverMap.size>0))if(this.awsRealTimeSocket)if(this.awsRealTimeSocket.bufferedAmount>0)setTimeout(this._closeSocketIfRequired.bind(this),1e3);else{Z.debug("closing WebSocket..."),clearTimeout(this.keepAliveTimeoutId);var e=this.awsRealTimeSocket;e.onclose=void 0,e.onerror=void 0,e.close(1e3),this.awsRealTimeSocket=null,this.socketStatus=E.CLOSED}else this.socketStatus=E.CLOSED},t.prototype._handleIncomingSubscriptionMessage=function(e){Z.debug("subscription message from AWS AppSync RealTime: "+e.data);var t=JSON.parse(e.data),n=t.id,r=void 0===n?"":n,i=t.payload,o=t.type,s=this.subscriptionObserverMap.get(r)||{},a=s.observer,u=void 0===a?null:a,c=s.query,f=void 0===c?"":c,l=s.variables,d=void 0===l?{}:l,h=s.startAckTimeoutId,p=s.subscriptionReadyCallback,v=s.subscriptionFailedCallback;if(Z.debug({id:r,observer:u,query:f,variables:d}),o===_.GQL_DATA&&i&&i.data)u?u.next(i):Z.debug("observer not found for id: "+r);else if(o!==_.GQL_START_ACK){if(o===_.GQL_CONNECTION_KEEP_ALIVE)return clearTimeout(this.keepAliveTimeoutId),void(this.keepAliveTimeoutId=setTimeout(this._errorDisconnect.bind(this,V.a.TIMEOUT_DISCONNECT),this.keepAliveTimeout));if(o===_.GQL_ERROR){g=S.FAILED;this.subscriptionObserverMap.set(r,{observer:u,query:f,variables:d,startAckTimeoutId:h,subscriptionReadyCallback:p,subscriptionFailedCallback:v,subscriptionState:g}),u.error({errors:[W({},new L.a("Connection failed: "+JSON.stringify(i)))]}),clearTimeout(h),u.complete(),"function"==typeof v&&v()}}else{Z.debug("subscription ready for "+JSON.stringify({query:f,variables:d})),"function"==typeof p&&p(),clearTimeout(h),function(e,t,n){U.a.dispatch("api",{event:e,data:t,message:n},"PubSub",X)}(V.a.SUBSCRIPTION_ACK,{query:f,variables:d},"Connection established for subscription");var g=S.CONNECTED;this.subscriptionObserverMap.set(r,{observer:u,query:f,variables:d,startAckTimeoutId:null,subscriptionState:g,subscriptionReadyCallback:p,subscriptionFailedCallback:v})}},t.prototype._errorDisconnect=function(e){Z.debug("Disconnect error: "+e),this.subscriptionObserverMap.forEach((function(t){var n=t.observer;n&&!n.closed&&n.error({errors:[W({},new L.a(e))]})})),this.subscriptionObserverMap.clear(),this.awsRealTimeSocket&&this.awsRealTimeSocket.close(),this.socketStatus=E.CLOSED},t.prototype._timeoutStartSubscriptionAck=function(e){var t=this.subscriptionObserverMap.get(e)||{},n=t.observer,r=t.query,i=t.variables;n&&(this.subscriptionObserverMap.set(e,{observer:n,query:r,variables:i,subscriptionState:S.FAILED}),n&&!n.closed&&(n.error({errors:[W({},new L.a("Subscription timeout "+JSON.stringify({query:r,variables:i})))]}),n.complete()),Z.debug("timeoutStartSubscription",JSON.stringify({query:r,variables:i})))},t.prototype._initializeWebSocketConnection=function(e){var t=this,n=e.appSyncGraphqlEndpoint,r=e.authenticationType,i=e.apiKey,o=e.region;if(this.socketStatus!==E.READY)return new Promise((function(e,s){return $(t,void 0,void 0,(function(){var t,a,u,c,f,l,d,h,p,v;return Y(this,(function(g){switch(g.label){case 0:if(this.promiseArray.push({res:e,rej:s}),this.socketStatus!==E.CLOSED)return[3,5];g.label=1;case 1:return g.trys.push([1,4,,5]),this.socketStatus=E.CONNECTING,t=this.isSSLEnabled?"wss://":"ws://",a=n.replace("https://",t).replace("http://",t).replace("appsync-api","appsync-realtime-api").replace("gogi-beta","grt-beta"),u="{}",l=(f=JSON).stringify,[4,this._awsRealTimeHeaderBasedAuth({authenticationType:r,payload:u,canonicalUri:"/connect",apiKey:i,appSyncGraphqlEndpoint:n,region:o})];case 2:return c=l.apply(f,[g.sent()]),d=D.Buffer.from(c).toString("base64"),h=D.Buffer.from(u).toString("base64"),p=a+"?header="+d+"&payload="+h,[4,this._initializeRetryableHandshake({awsRealTimeUrl:p})];case 3:return g.sent(),this.promiseArray.forEach((function(e){var t=e.res;Z.debug("Notifying connection successful"),t()})),this.socketStatus=E.READY,this.promiseArray=[],[3,5];case 4:return v=g.sent(),this.promiseArray.forEach((function(e){return(0,e.rej)(v)})),this.promiseArray=[],this.awsRealTimeSocket&&this.awsRealTimeSocket.readyState===WebSocket.OPEN&&this.awsRealTimeSocket.close(3001),this.awsRealTimeSocket=null,this.socketStatus=E.CLOSED,[3,5];case 5:return[2]}}))}))}))},t.prototype._initializeRetryableHandshake=function(e){var t=e.awsRealTimeUrl;return $(this,void 0,void 0,(function(){return Y(this,(function(e){switch(e.label){case 0:return Z.debug("Initializaling retryable Handshake"),[4,Object(F.b)(this._initializeHandshake.bind(this),[{awsRealTimeUrl:t}],5e3)];case 1:return e.sent(),[2]}}))}))},t.prototype._initializeHandshake=function(e){var t=e.awsRealTimeUrl;return $(this,void 0,void 0,(function(){var e,n,r,i=this;return Y(this,(function(o){switch(o.label){case 0:Z.debug("Initializing handshake "+t),o.label=1;case 1:return o.trys.push([1,4,,5]),[4,new Promise((function(e,n){var r=new WebSocket(t,"graphql-ws");r.onerror=function(){Z.debug("WebSocket connection error")},r.onclose=function(){n(new Error("Connection handshake error"))},r.onopen=function(){return i.awsRealTimeSocket=r,e()}}))];case 2:return o.sent(),[4,new Promise((function(e,t){var n=!1;i.awsRealTimeSocket.onerror=function(e){Z.debug("WebSocket error "+JSON.stringify(e))},i.awsRealTimeSocket.onclose=function(e){Z.debug("WebSocket closed "+e.reason),t(new Error(JSON.stringify(e)))},i.awsRealTimeSocket.onmessage=function(r){Z.debug("subscription message from AWS AppSyncRealTime: "+r.data+" ");var o=JSON.parse(r.data),s=o.type,a=o.payload,u=(void 0===a?{}:a).connectionTimeoutMs,c=void 0===u?3e5:u;if(s===_.GQL_CONNECTION_ACK)return n=!0,i.keepAliveTimeout=c,i.awsRealTimeSocket.onmessage=i._handleIncomingSubscriptionMessage.bind(i),i.awsRealTimeSocket.onerror=function(e){Z.debug(e),i._errorDisconnect(V.a.CONNECTION_CLOSED)},i.awsRealTimeSocket.onclose=function(e){Z.debug("WebSocket closed "+e.reason),i._errorDisconnect(V.a.CONNECTION_CLOSED)},void e("Cool, connected to AWS AppSyncRealTime");if(s===_.GQL_CONNECTION_ERROR){var f=o.payload,l=(void 0===f?{}:f).errors,d=J(void 0===l?[]:l,1)[0],h=void 0===d?{}:d,p=h.errorType,v=void 0===p?"":p,g=h.errorCode;t({errorType:v,errorCode:void 0===g?0:g})}};var r={type:_.GQL_CONNECTION_INIT};i.awsRealTimeSocket.send(JSON.stringify(r)),setTimeout(function(){n||t(new Error("Connection timeout: ack from AWSRealTime was not received on 15000 ms"))}.bind(i),15e3)}))];case 3:return o.sent(),[3,5];case 4:throw e=o.sent(),n=e.errorType,r=e.errorCode,Q.includes(r)?new F.a(n):n?new Error(n):e;case 5:return[2]}}))}))},t.prototype._awsRealTimeHeaderBasedAuth=function(e){var t=e.authenticationType,n=e.payload,r=e.canonicalUri,i=e.appSyncGraphqlEndpoint,o=e.apiKey,s=e.region;return $(this,void 0,void 0,(function(){var e,a,u;return Y(this,(function(c){switch(c.label){case 0:return e={API_KEY:this._awsRealTimeApiKeyHeader.bind(this),AWS_IAM:this._awsRealTimeIAMHeader.bind(this),OPENID_CONNECT:this._awsRealTimeOPENIDHeader.bind(this),AMAZON_COGNITO_USER_POOLS:this._awsRealTimeCUPHeader.bind(this)},"function"!=typeof(a=e[t])?(Z.debug("Authentication type "+t+" not supported"),[2,""]):(u=j.parse(i).host,[4,a({payload:n,canonicalUri:r,appSyncGraphqlEndpoint:i,apiKey:o,region:s,host:u})]);case 1:return[2,c.sent()]}}))}))},t.prototype._awsRealTimeCUPHeader=function(e){var t=e.host;return $(this,void 0,void 0,(function(){return Y(this,(function(e){switch(e.label){case 0:return[4,H.a.currentSession()];case 1:return[2,{Authorization:e.sent().getAccessToken().getJwtToken(),host:t}]}}))}))},t.prototype._awsRealTimeOPENIDHeader=function(e){var t=e.host;return $(this,void 0,void 0,(function(){var e,n,r;return Y(this,(function(i){switch(i.label){case 0:return[4,K.a.getItem("federatedInfo")];case 1:return(n=i.sent())?(e=n.token,[3,4]):[3,2];case 2:return[4,H.a.currentAuthenticatedUser()];case 3:(r=i.sent())&&(e=r.token),i.label=4;case 4:if(!e)throw new Error("No federated jwt");return[2,{Authorization:e,host:t}]}}))}))},t.prototype._awsRealTimeApiKeyHeader=function(e){var t=e.apiKey,n=e.host;return $(this,void 0,void 0,(function(){var e,r;return Y(this,(function(i){return e=new Date,r=e.toISOString().replace(/[:\-]|\.\d{3}/g,""),[2,{host:n,"x-amz-date":r,"x-api-key":t}]}))}))},t.prototype._awsRealTimeIAMHeader=function(e){var t=e.payload,n=e.canonicalUri,r=e.appSyncGraphqlEndpoint,i=e.region;return $(this,void 0,void 0,(function(){var e,o,s;return Y(this,(function(a){switch(a.label){case 0:return e={region:i,service:"appsync"},[4,this._ensureCredentials()];case 1:if(!a.sent())throw new Error("No credentials");return[4,z.a.get().then((function(e){return{secret_key:e.secretAccessKey,access_key:e.accessKeyId,session_token:e.sessionToken}}))];case 2:return o=a.sent(),s={url:""+r+n,data:t,method:"POST",headers:W({},ee)},[2,q.a.sign(s,o,e).headers]}}))}))},t.prototype._ensureCredentials=function(){return z.a.get().then((function(e){if(!e)return!1;var t=z.a.shear(e);return Z.debug("set credentials for AWSAppSyncRealTimeProvider",t),!0})).catch((function(e){return Z.warn("ensure credentials error",e),!1}))},t}(d),ne=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},re=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},ie=Object(i.b)().isNode,oe=new o.a("PubSub"),se=new(function(){function e(e){this._options=e,oe.debug("PubSub Options",this._options),this._pluggables=[],this.subscribe=this.subscribe.bind(this)}return Object.defineProperty(e.prototype,"awsAppSyncProvider",{get:function(){return this._awsAppSyncProvider||(this._awsAppSyncProvider=new R(this._options)),this._awsAppSyncProvider},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"awsAppSyncRealTimeProvider",{get:function(){return this._awsAppSyncRealTimeProvider||(this._awsAppSyncRealTimeProvider=new te(this._options)),this._awsAppSyncRealTimeProvider},enumerable:!0,configurable:!0}),e.prototype.getModuleName=function(){return"PubSub"},e.prototype.configure=function(e){var t=this,n=e?e.PubSub||e:{};return oe.debug("configure PubSub",{opt:n}),this._options=Object.assign({},this._options,n),this._pluggables.map((function(e){return e.configure(t._options)})),this._options},e.prototype.addPluggable=function(e){return ne(this,void 0,void 0,(function(){return re(this,(function(t){return e&&"PubSub"===e.getCategory()?(this._pluggables.push(e),[2,e.configure(this._options)]):[2]}))}))},e.prototype.getProviderByName=function(e){return e===s.a?this.awsAppSyncProvider:e===s.b?this.awsAppSyncRealTimeProvider:this._pluggables.find((function(t){return t.getProviderName()===e}))},e.prototype.getProviders=function(e){void 0===e&&(e={});var t=e.provider;if(!t)return this._pluggables;var n=this.getProviderByName(t);if(!n)throw new Error("Could not find provider named "+t);return[n]},e.prototype.publish=function(e,t,n){return ne(this,void 0,void 0,(function(){return re(this,(function(r){return[2,Promise.all(this.getProviders(n).map((function(r){return r.publish(e,t,n)})))]}))}))},e.prototype.subscribe=function(e,t){if(ie&&this._options&&this._options.ssr)throw new Error("Subscriptions are not supported for Server-Side Rendering (SSR)");oe.debug("subscribe options",t);var n=this.getProviders(t);return new r.a((function(r){var i=n.map((function(n){return{provider:n,observable:n.subscribe(e,t)}})).map((function(e){var t=e.provider;return e.observable.subscribe({start:console.error,next:function(e){return r.next({provider:t,value:e})},error:function(e){return r.error({provider:t,error:e})}})}));return function(){return i.forEach((function(e){return e.unsubscribe()}))}}))},e}())(null);a.a.register(se)},function(e,t,n){"use strict";n.d(t,"a",(function(){return i}));var r=n(1);function i(e){var t,n,i={};if(e=e.replace(/^\?/,""))try{for(var o=Object(r.__values)(e.split("&")),s=o.next();!s.done;s=o.next()){var a=s.value,u=Object(r.__read)(a.split("="),2),c=u[0],f=u[1],l=void 0===f?null:f;c=decodeURIComponent(c),l&&(l=decodeURIComponent(l)),c in i?Array.isArray(i[c])?i[c].push(l):i[c]=[i[c],l]:i[c]=l}}catch(e){t={error:e}}finally{try{s&&!s.done&&(n=o.return)&&n.call(o)}finally{if(t)throw t.error}}return i}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Crc32=t.crc32=void 0;var r=n(1);t.crc32=function(e){return(new i).update(e).digest()};var i=function(){function e(){this.checksum=4294967295}return e.prototype.update=function(e){var t,n;try{for(var i=r.__values(e),s=i.next();!s.done;s=i.next()){var a=s.value;this.checksum=this.checksum>>>8^o[255&(this.checksum^a)]}}catch(e){t={error:e}}finally{try{s&&!s.done&&(n=i.return)&&n.call(i)}finally{if(t)throw t.error}}return this},e.prototype.digest=function(){return(4294967295^this.checksum)>>>0},e}();t.Crc32=i;var o=Uint32Array.from([0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,936918e3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117])},function(e,t,n){var r=n(431);e.exports=function(e,t){return r(e,t)}},function(e,t,n){var r=n(483),i=n(484),o=i;o.v1=r,o.v4=i,e.exports=o},function(e,t,n){"use strict";n.d(t,"a",(function(){return M}));var r=n(107),i=n(1),o=n(28),s=function(){function e(e){if(this.bytes=e,8!==e.byteLength)throw new Error("Int64 buffers must be exactly 8 bytes")}return e.fromNumber=function(t){if(t>0x8000000000000000||t<-0x8000000000000000)throw new Error(t+" is too large (or, if negative, too small) to represent as an Int64");for(var n=new Uint8Array(8),r=7,i=Math.abs(Math.round(t));r>-1&&i>0;r--,i/=256)n[r]=i;return t<0&&a(n),new e(n)},e.prototype.valueOf=function(){var e=this.bytes.slice(0),t=128&e[0];return t&&a(e),parseInt(Object(o.b)(e),16)*(t?-1:1)},e.prototype.toString=function(){return String(this.valueOf())},e}();function a(e){for(var t=0;t<8;t++)e[t]^=255;for(t=7;t>-1&&(e[t]++,0===e[t]);t--);}var u,c=function(){function e(e,t){this.toUtf8=e,this.fromUtf8=t}return e.prototype.format=function(e){var t,n,r,o,s=[];try{for(var a=Object(i.__values)(Object.keys(e)),u=a.next();!u.done;u=a.next()){var c=u.value,f=this.fromUtf8(c);s.push(Uint8Array.from([f.byteLength]),f,this.formatHeaderValue(e[c]))}}catch(e){t={error:e}}finally{try{u&&!u.done&&(n=a.return)&&n.call(a)}finally{if(t)throw t.error}}var l=new Uint8Array(s.reduce((function(e,t){return e+t.byteLength}),0)),d=0;try{for(var h=Object(i.__values)(s),p=h.next();!p.done;p=h.next()){var v=p.value;l.set(v,d),d+=v.byteLength}}catch(e){r={error:e}}finally{try{p&&!p.done&&(o=h.return)&&o.call(h)}finally{if(r)throw r.error}}return l},e.prototype.formatHeaderValue=function(e){switch(e.type){case"boolean":return Uint8Array.from([e.value?0:1]);case"byte":return Uint8Array.from([2,e.value]);case"short":var t=new DataView(new ArrayBuffer(3));return t.setUint8(0,3),t.setInt16(1,e.value,!1),new Uint8Array(t.buffer);case"integer":var n=new DataView(new ArrayBuffer(5));return n.setUint8(0,4),n.setInt32(1,e.value,!1),new Uint8Array(n.buffer);case"long":var r=new Uint8Array(9);return r[0]=5,r.set(e.value.bytes,1),r;case"binary":var i=new DataView(new ArrayBuffer(3+e.value.byteLength));i.setUint8(0,6),i.setUint16(1,e.value.byteLength,!1);var a=new Uint8Array(i.buffer);return a.set(e.value,3),a;case"string":var u=this.fromUtf8(e.value),c=new DataView(new ArrayBuffer(3+u.byteLength));c.setUint8(0,7),c.setUint16(1,u.byteLength,!1);var f=new Uint8Array(c.buffer);return f.set(u,3),f;case"timestamp":var l=new Uint8Array(9);return l[0]=8,l.set(s.fromNumber(e.value.valueOf()).bytes,1),l;case"uuid":if(!y.test(e.value))throw new Error("Invalid UUID received: "+e.value);var d=new Uint8Array(17);return d[0]=9,d.set(Object(o.a)(e.value.replace(/\-/g,"")),1),d}},e.prototype.parse=function(e){for(var t={},n=0;n<e.byteLength;){var r=e.getUint8(n++),i=this.toUtf8(new Uint8Array(e.buffer,e.byteOffset+n,r));switch(n+=r,e.getUint8(n++)){case 0:t[i]={type:f,value:!0};break;case 1:t[i]={type:f,value:!1};break;case 2:t[i]={type:l,value:e.getInt8(n++)};break;case 3:t[i]={type:d,value:e.getInt16(n,!1)},n+=2;break;case 4:t[i]={type:h,value:e.getInt32(n,!1)},n+=4;break;case 5:t[i]={type:p,value:new s(new Uint8Array(e.buffer,e.byteOffset+n,8))},n+=8;break;case 6:var a=e.getUint16(n,!1);n+=2,t[i]={type:v,value:new Uint8Array(e.buffer,e.byteOffset+n,a)},n+=a;break;case 7:var u=e.getUint16(n,!1);n+=2,t[i]={type:g,value:this.toUtf8(new Uint8Array(e.buffer,e.byteOffset+n,u))},n+=u;break;case 8:t[i]={type:m,value:new Date(new s(new Uint8Array(e.buffer,e.byteOffset+n,8)).valueOf())},n+=8;break;case 9:var c=new Uint8Array(e.buffer,e.byteOffset+n,16);n+=16,t[i]={type:b,value:Object(o.b)(c.subarray(0,4))+"-"+Object(o.b)(c.subarray(4,6))+"-"+Object(o.b)(c.subarray(6,8))+"-"+Object(o.b)(c.subarray(8,10))+"-"+Object(o.b)(c.subarray(10))};break;default:throw new Error("Unrecognized header type tag")}}return t},e}();!function(e){e[e.boolTrue=0]="boolTrue",e[e.boolFalse=1]="boolFalse",e[e.byte=2]="byte",e[e.short=3]="short",e[e.integer=4]="integer",e[e.long=5]="long",e[e.byteArray=6]="byteArray",e[e.string=7]="string",e[e.timestamp=8]="timestamp",e[e.uuid=9]="uuid"}(u||(u={}));var f="boolean",l="byte",d="short",h="integer",p="long",v="binary",g="string",m="timestamp",b="uuid",y=/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;var w=function(){function e(e,t){this.headerMarshaller=new c(e,t)}return e.prototype.marshall=function(e){var t=e.headers,n=e.body,i=this.headerMarshaller.format(t),o=i.byteLength+n.byteLength+16,s=new Uint8Array(o),a=new DataView(s.buffer,s.byteOffset,s.byteLength),u=new r.Crc32;return a.setUint32(0,o,!1),a.setUint32(4,i.byteLength,!1),a.setUint32(8,u.update(s.subarray(0,8)).digest(),!1),s.set(i,12),s.set(n,i.byteLength+12),a.setUint32(o-4,u.update(s.subarray(8,o-4)).digest(),!1),s},e.prototype.unmarshall=function(e){var t=function(e){var t=e.byteLength,n=e.byteOffset,i=e.buffer;if(t<16)throw new Error("Provided message too short to accommodate event stream message overhead");var o=new DataView(i,n,t),s=o.getUint32(0,!1);if(t!==s)throw new Error("Reported message length does not match received message length");var a=o.getUint32(4,!1),u=o.getUint32(8,!1),c=o.getUint32(t-4,!1),f=(new r.Crc32).update(new Uint8Array(i,n,8));if(u!==f.digest())throw new Error("The prelude checksum specified in the message ("+u+") does not match the calculated CRC32 checksum ("+f.digest()+")");if(f.update(new Uint8Array(i,n+8,t-12)),c!==f.digest())throw new Error("The message checksum ("+f.digest()+") did not match the expected value of "+c);return{headers:new DataView(i,n+8+4,a),body:new Uint8Array(i,n+8+4+a,s-a-16)}}(e),n=t.headers,i=t.body;return{headers:this.headerMarshaller.parse(n),body:i}},e.prototype.formatHeaders=function(e){return this.headerMarshaller.format(e)},e}();var _=function(){function e(e){var t=e.utf8Encoder,n=e.utf8Decoder;this.eventMarshaller=new w(t,n),this.utfEncoder=t}return e.prototype.deserialize=function(e,t){var n,r,o,s,a,u,c;return function(e,t){var n;return(n={})[Symbol.asyncIterator]=function(){return Object(i.__asyncGenerator)(this,arguments,(function(){var n,r,o,s,a,u,c,f,l,d,h,p,v,g,m,b,y;return Object(i.__generator)(this,(function(w){switch(w.label){case 0:w.trys.push([0,12,13,18]),n=Object(i.__asyncValues)(e),w.label=1;case 1:return[4,Object(i.__await)(n.next())];case 2:if((r=w.sent()).done)return[3,11];if(o=r.value,s=t.eventMarshaller.unmarshall(o),"error"!==(a=s.headers[":message-type"].value))return[3,3];throw(u=new Error(s.headers[":error-message"].value||"UnknownError")).name=s.headers[":error-code"].value,u;case 3:return"exception"!==a?[3,5]:(c=s.headers[":exception-type"].value,(g={})[c]=s,f=g,[4,Object(i.__await)(t.deserializer(f))]);case 4:if((l=w.sent()).$unknown)throw(d=new Error(t.toUtf8(s.body))).name=c,d;throw l[c];case 5:return"event"!==a?[3,9]:((m={})[s.headers[":event-type"].value]=s,h=m,[4,Object(i.__await)(t.deserializer(h))]);case 6:return(p=w.sent()).$unknown?[3,10]:[4,Object(i.__await)(p)];case 7:return[4,w.sent()];case 8:return w.sent(),[3,10];case 9:throw Error("Unrecognizable event type: "+s.headers[":event-type"].value);case 10:return[3,1];case 11:return[3,18];case 12:return v=w.sent(),b={error:v},[3,18];case 13:return w.trys.push([13,,16,17]),r&&!r.done&&(y=n.return)?[4,Object(i.__await)(y.call(n))]:[3,15];case 14:w.sent(),w.label=15;case 15:return[3,17];case 16:if(b)throw b.error;return[7];case 17:return[7];case 18:return[2]}}))}))},n}((n=e,o=0,s=0,a=null,u=null,c=function(e){if("number"!=typeof e)throw new Error("Attempted to allocate an event message where size was not a number: "+e);o=e,s=4,a=new Uint8Array(e),new DataView(a.buffer).setUint32(0,e,!1)},(r={})[Symbol.asyncIterator]=function(){return Object(i.__asyncGenerator)(this,arguments,(function(){var e,t,r,f,l,d,h,p;return Object(i.__generator)(this,(function(v){switch(v.label){case 0:e=n[Symbol.asyncIterator](),v.label=1;case 1:return[4,Object(i.__await)(e.next())];case 2:return t=v.sent(),r=t.value,t.done?o?[3,4]:[4,Object(i.__await)(void 0)]:[3,10];case 3:return[2,v.sent()];case 4:return o!==s?[3,7]:[4,Object(i.__await)(a)];case 5:return[4,v.sent()];case 6:return v.sent(),[3,8];case 7:throw new Error("Truncated event message received.");case 8:return[4,Object(i.__await)(void 0)];case 9:return[2,v.sent()];case 10:f=r.length,l=0,v.label=11;case 11:if(!(l<f))return[3,15];if(!a){if(d=f-l,u||(u=new Uint8Array(4)),h=Math.min(4-s,d),u.set(r.slice(l,l+h),s),l+=h,(s+=h)<4)return[3,15];c(new DataView(u.buffer).getUint32(0,!1)),u=null}return p=Math.min(o-s,f-l),a.set(r.slice(l,l+p),s),s+=p,l+=p,o&&o===s?[4,Object(i.__await)(a)]:[3,14];case 12:return[4,v.sent()];case 13:v.sent(),a=null,o=0,s=0,v.label=14;case 14:return[3,11];case 15:return[3,1];case 16:return[2]}}))}))},r),{eventMarshaller:this.eventMarshaller,deserializer:t,toUtf8:this.utfEncoder})},e.prototype.serialize=function(e,t){var n,r=this;return(n={})[Symbol.asyncIterator]=function(){return Object(i.__asyncGenerator)(this,arguments,(function(){var n,o,s,a,u,c,f;return Object(i.__generator)(this,(function(l){switch(l.label){case 0:l.trys.push([0,7,8,13]),n=Object(i.__asyncValues)(e),l.label=1;case 1:return[4,Object(i.__await)(n.next())];case 2:return(o=l.sent()).done?[3,6]:(s=o.value,a=r.eventMarshaller.marshall(t(s)),[4,Object(i.__await)(a)]);case 3:return[4,l.sent()];case 4:l.sent(),l.label=5;case 5:return[3,1];case 6:return[3,13];case 7:return u=l.sent(),c={error:u},[3,13];case 8:return l.trys.push([8,,11,12]),o&&!o.done&&(f=n.return)?[4,Object(i.__await)(f.call(n))]:[3,10];case 9:l.sent(),l.label=10;case 10:return[3,12];case 11:if(c)throw c.error;return[7];case 12:return[7];case 13:return[4,Object(i.__await)(new Uint8Array(0))];case 14:return[4,l.sent()];case 15:return l.sent(),[2]}}))}))},n},e}(),S=function(){function e(e){var t=e.utf8Encoder,n=e.utf8Decoder;this.eventMarshaller=new w(t,n),this.universalMarshaller=new _({utf8Decoder:n,utf8Encoder:t})}return e.prototype.deserialize=function(e,t){var n,r,o=E(e)?(n=e,(r={})[Symbol.asyncIterator]=function(){return Object(i.__asyncGenerator)(this,arguments,(function(){var e,t,r,o;return Object(i.__generator)(this,(function(s){switch(s.label){case 0:e=n.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,Object(i.__await)(e.read())];case 3:return t=s.sent(),r=t.done,o=t.value,r?[4,Object(i.__await)(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,Object(i.__await)(o)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return e.releaseLock(),[7];case 10:return[2]}}))}))},r):e;return this.universalMarshaller.deserialize(o,t)},e.prototype.serialize=function(e,t){var n,r=this.universalMarshaller.serialize(e,t);return"function"==typeof ReadableStream?(n=r[Symbol.asyncIterator](),new ReadableStream({pull:function(e){return Object(i.__awaiter)(this,void 0,void 0,(function(){var t,r,o;return Object(i.__generator)(this,(function(i){switch(i.label){case 0:return[4,n.next()];case 1:return t=i.sent(),r=t.done,o=t.value,r?[2,e.close()]:(e.enqueue(o),[2])}}))}))}})):r},e}(),E=function(e){return"function"==typeof ReadableStream&&e instanceof ReadableStream},M=function(e){return new S(e)}},function(e,t,n){"use strict";n.d(t,"a",(function(){return M}));var r=n(1),i="X-Amz-Date".toLowerCase(),o=["authorization",i,"date"],s="X-Amz-Signature".toLowerCase(),a="X-Amz-Security-Token".toLowerCase(),u={authorization:!0,"cache-control":!0,connection:!0,expect:!0,from:!0,"keep-alive":!0,"max-forwards":!0,pragma:!0,referer:!0,te:!0,trailer:!0,"transfer-encoding":!0,upgrade:!0,"user-agent":!0,"x-amzn-trace-id":!0},c=/^proxy-/,f=/^sec-/,l="AWS4-HMAC-SHA256-PAYLOAD",d={},h=[];function p(e,t,n){return e+"/"+t+"/"+n+"/aws4_request"}var v=n(28);function g(e,t,n){var i,o,s=e.headers,a={};try{for(var l=Object(r.__values)(Object.keys(s).sort()),d=l.next();!d.done;d=l.next()){var h=d.value,p=h.toLowerCase();(p in u||(null==t?void 0:t.has(p))||c.test(p)||f.test(p))&&(!n||n&&!n.has(p))||(a[p]=s[h].trim().replace(/\s+/g," "))}}catch(e){i={error:e}}finally{try{d&&!d.done&&(o=l.return)&&o.call(l)}finally{if(i)throw i.error}}return a}var m=n(55);var b=n(251);function y(e,t){var n=e.headers,i=e.body;return Object(r.__awaiter)(this,void 0,void 0,(function(){var e,o,s,a,u,c,f;return Object(r.__generator)(this,(function(l){switch(l.label){case 0:try{for(e=Object(r.__values)(Object.keys(n)),o=e.next();!o.done;o=e.next())if("x-amz-content-sha256"===(s=o.value).toLowerCase())return[2,n[s]]}catch(e){c={error:e}}finally{try{o&&!o.done&&(f=e.return)&&f.call(e)}finally{if(c)throw c.error}}return null!=i?[3,1]:[2,"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"];case 1:return"string"==typeof i||ArrayBuffer.isView(i)||Object(b.a)(i)?((a=new t).update(i),u=v.b,[4,a.digest()]):[3,3];case 2:return[2,u.apply(void 0,[l.sent()])];case 3:return[2,"UNSIGNED-PAYLOAD"]}}))}))}function w(e){var t=e.headers,n=e.query,i=Object(r.__rest)(e,["headers","query"]);return Object(r.__assign)(Object(r.__assign)({},i),{headers:Object(r.__assign)({},t),query:n?_(n):void 0})}function _(e){return Object.keys(e).reduce((function(t,n){var i,o=e[n];return Object(r.__assign)(Object(r.__assign)({},t),((i={})[n]=Array.isArray(o)?Object(r.__spread)(o):o,i))}),{})}function S(e){var t,n;e="function"==typeof e.clone?e.clone():w(e);try{for(var i=Object(r.__values)(Object.keys(e.headers)),s=i.next();!s.done;s=i.next()){var a=s.value;o.indexOf(a.toLowerCase())>-1&&delete e.headers[a]}}catch(e){t={error:e}}finally{try{s&&!s.done&&(n=i.return)&&n.call(i)}finally{if(t)throw t.error}}return e}function E(e){return function(e){if("number"==typeof e)return new Date(1e3*e);if("string"==typeof e)return Number(e)?new Date(1e3*Number(e)):new Date(e);return e}(e).toISOString().replace(/\.\d{3}Z$/,"Z")}var M=function(){function e(e){var t=e.applyChecksum,n=e.credentials,r=e.region,i=e.service,o=e.sha256,s=e.uriEscapePath,a=void 0===s||s;this.service=i,this.sha256=o,this.uriEscapePath=a,this.applyChecksum="boolean"!=typeof t||t,this.regionProvider=k(r),this.credentialProvider=O(n)}return e.prototype.presign=function(e,t){return void 0===t&&(t={}),Object(r.__awaiter)(this,void 0,void 0,(function(){var n,i,o,s,a,u,c,f,l,d,h,v,m,b,_,E,M,k,O,x,C,T,P;return Object(r.__generator)(this,(function(N){switch(N.label){case 0:return n=t.signingDate,i=void 0===n?new Date:n,o=t.expiresIn,s=void 0===o?3600:o,a=t.unsignableHeaders,u=t.signableHeaders,c=t.signingRegion,f=t.signingService,[4,this.credentialProvider()];case 1:return l=N.sent(),null==c?[3,2]:(h=c,[3,4]);case 2:return[4,this.regionProvider()];case 3:h=N.sent(),N.label=4;case 4:return d=h,v=A(i),m=v.longDate,b=v.shortDate,s>604800?[2,Promise.reject("Signature version 4 presigned URLs must have an expiration date less than one week in the future")]:(_=p(b,d,null!=f?f:this.service),E=function(e){var t,n,i="function"==typeof e.clone?e.clone():w(e),o=i.headers,s=i.query,a=void 0===s?{}:s;try{for(var u=Object(r.__values)(Object.keys(o)),c=u.next();!c.done;c=u.next()){var f=c.value;"x-amz-"===f.toLowerCase().substr(0,6)&&(a[f]=o[f],delete o[f])}}catch(e){t={error:e}}finally{try{c&&!c.done&&(n=u.return)&&n.call(u)}finally{if(t)throw t.error}}return Object(r.__assign)(Object(r.__assign)({},e),{headers:o,query:a})}(S(e)),l.sessionToken&&(E.query["X-Amz-Security-Token"]=l.sessionToken),E.query["X-Amz-Algorithm"]="AWS4-HMAC-SHA256",E.query["X-Amz-Credential"]=l.accessKeyId+"/"+_,E.query["X-Amz-Date"]=m,E.query["X-Amz-Expires"]=s.toString(10),M=g(E,a,u),E.query["X-Amz-SignedHeaders"]=I(M),k=E.query,O="X-Amz-Signature",x=this.getSignature,C=[m,_,this.getSigningKey(l,d,b,f)],T=this.createCanonicalRequest,P=[E,M],[4,y(e,this.sha256)]);case 5:return[4,x.apply(this,C.concat([T.apply(this,P.concat([N.sent()]))]))];case 6:return k[O]=N.sent(),[2,E]}}))}))},e.prototype.sign=function(e,t){return Object(r.__awaiter)(this,void 0,void 0,(function(){return Object(r.__generator)(this,(function(n){return"string"==typeof e?[2,this.signString(e,t)]:e.headers&&e.payload?[2,this.signEvent(e,t)]:[2,this.signRequest(e,t)]}))}))},e.prototype.signEvent=function(e,t){var n=e.headers,i=e.payload,o=t.signingDate,s=void 0===o?new Date:o,a=t.priorSignature,u=t.signingRegion,c=t.signingService;return Object(r.__awaiter)(this,void 0,void 0,(function(){var e,t,o,f,d,h,g,m,b,w,_;return Object(r.__generator)(this,(function(r){switch(r.label){case 0:return null==u?[3,1]:(t=u,[3,3]);case 1:return[4,this.regionProvider()];case 2:t=r.sent(),r.label=3;case 3:return e=t,o=A(s),f=o.shortDate,d=o.longDate,h=p(f,e,null!=c?c:this.service),[4,y({headers:{},body:i},this.sha256)];case 4:return g=r.sent(),(m=new this.sha256).update(n),w=v.b,[4,m.digest()];case 5:return b=w.apply(void 0,[r.sent()]),_=[l,d,h,a,b,g].join("\n"),[2,this.signString(_,{signingDate:s,signingRegion:e,signingService:c})]}}))}))},e.prototype.signString=function(e,t){var n=void 0===t?{}:t,i=n.signingDate,o=void 0===i?new Date:i,s=n.signingRegion,a=n.signingService;return Object(r.__awaiter)(this,void 0,void 0,(function(){var t,n,i,u,c,f,l,d;return Object(r.__generator)(this,(function(r){switch(r.label){case 0:return[4,this.credentialProvider()];case 1:return t=r.sent(),null==s?[3,2]:(i=s,[3,4]);case 2:return[4,this.regionProvider()];case 3:i=r.sent(),r.label=4;case 4:return n=i,u=A(o).shortDate,l=(f=this.sha256).bind,[4,this.getSigningKey(t,n,u,a)];case 5:return(c=new(l.apply(f,[void 0,r.sent()]))).update(e),d=v.b,[4,c.digest()];case 6:return[2,d.apply(void 0,[r.sent()])]}}))}))},e.prototype.signRequest=function(e,t){var n=void 0===t?{}:t,o=n.signingDate,s=void 0===o?new Date:o,u=n.signableHeaders,c=n.unsignableHeaders,f=n.signingRegion,l=n.signingService;return Object(r.__awaiter)(this,void 0,void 0,(function(){var t,n,o,d,h,v,m,b,w,_,E;return Object(r.__generator)(this,(function(M){switch(M.label){case 0:return[4,this.credentialProvider()];case 1:return t=M.sent(),null==f?[3,2]:(o=f,[3,4]);case 2:return[4,this.regionProvider()];case 3:o=M.sent(),M.label=4;case 4:return n=o,d=S(e),h=A(s),v=h.longDate,m=h.shortDate,b=p(m,n,null!=l?l:this.service),d.headers[i]=v,t.sessionToken&&(d.headers[a]=t.sessionToken),[4,y(d,this.sha256)];case 5:return w=M.sent(),!function(e,t){var n,i;e=e.toLowerCase();try{for(var o=Object(r.__values)(Object.keys(t)),s=o.next();!s.done;s=o.next()){if(e===s.value.toLowerCase())return!0}}catch(e){n={error:e}}finally{try{s&&!s.done&&(i=o.return)&&i.call(o)}finally{if(n)throw n.error}}return!1}("x-amz-content-sha256",d.headers)&&this.applyChecksum&&(d.headers["x-amz-content-sha256"]=w),_=g(d,c,u),[4,this.getSignature(v,b,this.getSigningKey(t,n,m,l),this.createCanonicalRequest(d,_,w))];case 6:return E=M.sent(),d.headers.authorization="AWS4-HMAC-SHA256 Credential="+t.accessKeyId+"/"+b+", SignedHeaders="+I(_)+", Signature="+E,[2,d]}}))}))},e.prototype.createCanonicalRequest=function(e,t,n){var i=Object.keys(t).sort();return e.method+"\n"+this.getCanonicalPath(e)+"\n"+function(e){var t,n,i=e.query,o=void 0===i?{}:i,a=[],u={},c=function(e){if(e.toLowerCase()===s)return"continue";a.push(e);var t=o[e];"string"==typeof t?u[e]=Object(m.a)(e)+"="+Object(m.a)(t):Array.isArray(t)&&(u[e]=t.slice(0).sort().reduce((function(t,n){return t.concat([Object(m.a)(e)+"="+Object(m.a)(n)])}),[]).join("&"))};try{for(var f=Object(r.__values)(Object.keys(o).sort()),l=f.next();!l.done;l=f.next()){c(l.value)}}catch(e){t={error:e}}finally{try{l&&!l.done&&(n=f.return)&&n.call(f)}finally{if(t)throw t.error}}return a.map((function(e){return u[e]})).filter((function(e){return e})).join("&")}(e)+"\n"+i.map((function(e){return e+":"+t[e]})).join("\n")+"\n\n"+i.join(";")+"\n"+n},e.prototype.createStringToSign=function(e,t,n){return Object(r.__awaiter)(this,void 0,void 0,(function(){var i,o;return Object(r.__generator)(this,(function(r){switch(r.label){case 0:return(i=new this.sha256).update(n),[4,i.digest()];case 1:return o=r.sent(),[2,"AWS4-HMAC-SHA256\n"+e+"\n"+t+"\n"+Object(v.b)(o)]}}))}))},e.prototype.getCanonicalPath=function(e){var t=e.path;return this.uriEscapePath?"/"+encodeURIComponent(t.replace(/^\//,"")).replace(/%2F/g,"/"):t},e.prototype.getSignature=function(e,t,n,i){return Object(r.__awaiter)(this,void 0,void 0,(function(){var o,s,a,u,c;return Object(r.__generator)(this,(function(r){switch(r.label){case 0:return[4,this.createStringToSign(e,t,i)];case 1:return o=r.sent(),u=(a=this.sha256).bind,[4,n];case 2:return(s=new(u.apply(a,[void 0,r.sent()]))).update(o),c=v.b,[4,s.digest()];case 3:return[2,c.apply(void 0,[r.sent()])]}}))}))},e.prototype.getSigningKey=function(e,t,n,i){return function(e,t,n,i,o){var s=n+":"+i+":"+o+":"+t.accessKeyId+":"+t.sessionToken;if(s in d)return d[s];for(h.push(s);h.length>50;)delete d[h.shift()];return d[s]=new Promise((function(a,u){var c,f,l=Promise.resolve("AWS4"+t.secretAccessKey),h=function(t){(l=l.then((function(n){return r=t,(i=new e(n)).update(r),i.digest();var r,i}))).catch((function(){}))};try{for(var p=Object(r.__values)([n,i,o,"aws4_request"]),v=p.next();!v.done;v=p.next()){h(v.value)}}catch(e){c={error:e}}finally{try{v&&!v.done&&(f=p.return)&&f.call(p)}finally{if(c)throw c.error}}l.then(a,(function(e){delete d[s],u(e)}))}))}(this.sha256,e,n,t,i||this.service)},e}(),A=function(e){var t=E(e).replace(/[\-:]/g,"");return{longDate:t,shortDate:t.substr(0,8)}},I=function(e){return Object.keys(e).sort().join(";")},k=function(e){if("string"==typeof e){var t=Promise.resolve(e);return function(){return t}}return e},O=function(e){if("object"==typeof e){var t=Promise.resolve(e);return function(){return t}}return e}},function(e,t,n){"use strict";n.d(t,"a",(function(){return i}));var r=n(1),i=function(e){return Object(r.__assign)(Object(r.__assign)({},e),{eventStreamMarshaller:e.eventStreamSerdeProvider(e)})}},function(e,t,n){"use strict";var r=n(7),i=n(162),o=n(8).Buffer,s=new Array(16);function a(){i.call(this,64),this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878}function u(e,t){return e<<t|e>>>32-t}function c(e,t,n,r,i,o,s){return u(e+(t&n|~t&r)+i+o|0,s)+t|0}function f(e,t,n,r,i,o,s){return u(e+(t&r|n&~r)+i+o|0,s)+t|0}function l(e,t,n,r,i,o,s){return u(e+(t^n^r)+i+o|0,s)+t|0}function d(e,t,n,r,i,o,s){return u(e+(n^(t|~r))+i+o|0,s)+t|0}r(a,i),a.prototype._update=function(){for(var e=s,t=0;t<16;++t)e[t]=this._block.readInt32LE(4*t);var n=this._a,r=this._b,i=this._c,o=this._d;n=c(n,r,i,o,e[0],3614090360,7),o=c(o,n,r,i,e[1],3905402710,12),i=c(i,o,n,r,e[2],606105819,17),r=c(r,i,o,n,e[3],3250441966,22),n=c(n,r,i,o,e[4],4118548399,7),o=c(o,n,r,i,e[5],1200080426,12),i=c(i,o,n,r,e[6],2821735955,17),r=c(r,i,o,n,e[7],4249261313,22),n=c(n,r,i,o,e[8],1770035416,7),o=c(o,n,r,i,e[9],2336552879,12),i=c(i,o,n,r,e[10],4294925233,17),r=c(r,i,o,n,e[11],2304563134,22),n=c(n,r,i,o,e[12],1804603682,7),o=c(o,n,r,i,e[13],4254626195,12),i=c(i,o,n,r,e[14],2792965006,17),n=f(n,r=c(r,i,o,n,e[15],1236535329,22),i,o,e[1],4129170786,5),o=f(o,n,r,i,e[6],3225465664,9),i=f(i,o,n,r,e[11],643717713,14),r=f(r,i,o,n,e[0],3921069994,20),n=f(n,r,i,o,e[5],3593408605,5),o=f(o,n,r,i,e[10],38016083,9),i=f(i,o,n,r,e[15],3634488961,14),r=f(r,i,o,n,e[4],3889429448,20),n=f(n,r,i,o,e[9],568446438,5),o=f(o,n,r,i,e[14],3275163606,9),i=f(i,o,n,r,e[3],4107603335,14),r=f(r,i,o,n,e[8],1163531501,20),n=f(n,r,i,o,e[13],2850285829,5),o=f(o,n,r,i,e[2],4243563512,9),i=f(i,o,n,r,e[7],1735328473,14),n=l(n,r=f(r,i,o,n,e[12],2368359562,20),i,o,e[5],4294588738,4),o=l(o,n,r,i,e[8],2272392833,11),i=l(i,o,n,r,e[11],1839030562,16),r=l(r,i,o,n,e[14],4259657740,23),n=l(n,r,i,o,e[1],2763975236,4),o=l(o,n,r,i,e[4],1272893353,11),i=l(i,o,n,r,e[7],4139469664,16),r=l(r,i,o,n,e[10],3200236656,23),n=l(n,r,i,o,e[13],681279174,4),o=l(o,n,r,i,e[0],3936430074,11),i=l(i,o,n,r,e[3],3572445317,16),r=l(r,i,o,n,e[6],76029189,23),n=l(n,r,i,o,e[9],3654602809,4),o=l(o,n,r,i,e[12],3873151461,11),i=l(i,o,n,r,e[15],530742520,16),n=d(n,r=l(r,i,o,n,e[2],3299628645,23),i,o,e[0],4096336452,6),o=d(o,n,r,i,e[7],1126891415,10),i=d(i,o,n,r,e[14],2878612391,15),r=d(r,i,o,n,e[5],4237533241,21),n=d(n,r,i,o,e[12],1700485571,6),o=d(o,n,r,i,e[3],2399980690,10),i=d(i,o,n,r,e[10],4293915773,15),r=d(r,i,o,n,e[1],2240044497,21),n=d(n,r,i,o,e[8],1873313359,6),o=d(o,n,r,i,e[15],4264355552,10),i=d(i,o,n,r,e[6],2734768916,15),r=d(r,i,o,n,e[13],1309151649,21),n=d(n,r,i,o,e[4],4149444226,6),o=d(o,n,r,i,e[11],3174756917,10),i=d(i,o,n,r,e[2],718787259,15),r=d(r,i,o,n,e[9],3951481745,21),this._a=this._a+n|0,this._b=this._b+r|0,this._c=this._c+i|0,this._d=this._d+o|0},a.prototype._digest=function(){this._block[this._blockOffset++]=128,this._blockOffset>56&&(this._block.fill(0,this._blockOffset,64),this._update(),this._blockOffset=0),this._block.fill(0,this._blockOffset,56),this._block.writeUInt32LE(this._length[0],56),this._block.writeUInt32LE(this._length[1],60),this._update();var e=o.allocUnsafe(16);return e.writeInt32LE(this._a,0),e.writeInt32LE(this._b,4),e.writeInt32LE(this._c,8),e.writeInt32LE(this._d,12),e},e.exports=a},function(e,t,n){(function(t){function n(e){try{if(!t.localStorage)return!1}catch(e){return!1}var n=t.localStorage[e];return null!=n&&"true"===String(n).toLowerCase()}e.exports=function(e,t){if(n("noDeprecation"))return e;var r=!1;return function(){if(!r){if(n("throwDeprecation"))throw new Error(t);n("traceDeprecation")?console.trace(t):console.warn(t),r=!0}return e.apply(this,arguments)}}}).call(this,n(31))},function(e,t,n){"use strict";var r=n(67).codes.ERR_STREAM_PREMATURE_CLOSE;function i(){}e.exports=function e(t,n,o){if("function"==typeof n)return e(t,null,n);n||(n={}),o=function(e){var t=!1;return function(){if(!t){t=!0;for(var n=arguments.length,r=new Array(n),i=0;i<n;i++)r[i]=arguments[i];e.apply(this,r)}}}(o||i);var s=n.readable||!1!==n.readable&&t.readable,a=n.writable||!1!==n.writable&&t.writable,u=function(){t.writable||f()},c=t._writableState&&t._writableState.finished,f=function(){a=!1,c=!0,s||o.call(t)},l=t._readableState&&t._readableState.endEmitted,d=function(){s=!1,l=!0,a||o.call(t)},h=function(e){o.call(t,e)},p=function(){var e;return s&&!l?(t._readableState&&t._readableState.ended||(e=new r),o.call(t,e)):a&&!c?(t._writableState&&t._writableState.ended||(e=new r),o.call(t,e)):void 0},v=function(){t.req.on("finish",f)};return!function(e){return e.setHeader&&"function"==typeof e.abort}(t)?a&&!t._writableState&&(t.on("end",u),t.on("close",u)):(t.on("complete",f),t.on("abort",p),t.req?v():t.on("request",v)),t.on("end",d),t.on("finish",f),!1!==n.error&&t.on("error",h),t.on("close",p),function(){t.removeListener("complete",f),t.removeListener("abort",p),t.removeListener("request",v),t.req&&t.req.removeListener("finish",f),t.removeListener("end",u),t.removeListener("close",u),t.removeListener("finish",f),t.removeListener("end",d),t.removeListener("error",h),t.removeListener("close",p)}}},function(e,t,n){"use strict";var r=n(6).Buffer,i=n(7),o=n(162),s=new Array(16),a=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13],u=[5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11],c=[11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6],f=[8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11],l=[0,1518500249,1859775393,2400959708,2840853838],d=[1352829926,1548603684,1836072691,2053994217,0];function h(){o.call(this,64),this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520}function p(e,t){return e<<t|e>>>32-t}function v(e,t,n,r,i,o,s,a){return p(e+(t^n^r)+o+s|0,a)+i|0}function g(e,t,n,r,i,o,s,a){return p(e+(t&n|~t&r)+o+s|0,a)+i|0}function m(e,t,n,r,i,o,s,a){return p(e+((t|~n)^r)+o+s|0,a)+i|0}function b(e,t,n,r,i,o,s,a){return p(e+(t&r|n&~r)+o+s|0,a)+i|0}function y(e,t,n,r,i,o,s,a){return p(e+(t^(n|~r))+o+s|0,a)+i|0}i(h,o),h.prototype._update=function(){for(var e=s,t=0;t<16;++t)e[t]=this._block.readInt32LE(4*t);for(var n=0|this._a,r=0|this._b,i=0|this._c,o=0|this._d,h=0|this._e,w=0|this._a,_=0|this._b,S=0|this._c,E=0|this._d,M=0|this._e,A=0;A<80;A+=1){var I,k;A<16?(I=v(n,r,i,o,h,e[a[A]],l[0],c[A]),k=y(w,_,S,E,M,e[u[A]],d[0],f[A])):A<32?(I=g(n,r,i,o,h,e[a[A]],l[1],c[A]),k=b(w,_,S,E,M,e[u[A]],d[1],f[A])):A<48?(I=m(n,r,i,o,h,e[a[A]],l[2],c[A]),k=m(w,_,S,E,M,e[u[A]],d[2],f[A])):A<64?(I=b(n,r,i,o,h,e[a[A]],l[3],c[A]),k=g(w,_,S,E,M,e[u[A]],d[3],f[A])):(I=y(n,r,i,o,h,e[a[A]],l[4],c[A]),k=v(w,_,S,E,M,e[u[A]],d[4],f[A])),n=h,h=o,o=p(i,10),i=r,r=I,w=M,M=E,E=p(S,10),S=_,_=k}var O=this._b+i+E|0;this._b=this._c+o+M|0,this._c=this._d+h+w|0,this._d=this._e+n+_|0,this._e=this._a+r+S|0,this._a=O},h.prototype._digest=function(){this._block[this._blockOffset++]=128,this._blockOffset>56&&(this._block.fill(0,this._blockOffset,64),this._update(),this._blockOffset=0),this._block.fill(0,this._blockOffset,56),this._block.writeUInt32LE(this._length[0],56),this._block.writeUInt32LE(this._length[1],60),this._update();var e=r.alloc?r.alloc(20):new r(20);return e.writeInt32LE(this._a,0),e.writeInt32LE(this._b,4),e.writeInt32LE(this._c,8),e.writeInt32LE(this._d,12),e.writeInt32LE(this._e,16),e},e.exports=h},function(e,t,n){(t=e.exports=function(e){e=e.toLowerCase();var n=t[e];if(!n)throw new Error(e+" is not supported (we accept pull requests)");return new n}).sha=n(281),t.sha1=n(282),t.sha224=n(283),t.sha256=n(169),t.sha384=n(284),t.sha512=n(170)},function(e,t,n){(t=e.exports=n(171)).Stream=t,t.Readable=t,t.Writable=n(120),t.Duplex=n(60),t.Transform=n(174),t.PassThrough=n(291)},function(e,t,n){var r=n(6),i=r.Buffer;function o(e,t){for(var n in e)t[n]=e[n]}function s(e,t,n){return i(e,t,n)}i.from&&i.alloc&&i.allocUnsafe&&i.allocUnsafeSlow?e.exports=r:(o(r,t),t.Buffer=s),o(i,s),s.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return i(e,t,n)},s.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=i(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},s.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return i(e)},s.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){"use strict";(function(t,r,i){var o=n(92);function s(e){var t=this;this.next=null,this.entry=null,this.finish=function(){!function(e,t,n){var r=e.entry;e.entry=null;for(;r;){var i=r.callback;t.pendingcb--,i(n),r=r.next}t.corkedRequestsFree?t.corkedRequestsFree.next=e:t.corkedRequestsFree=e}(t,e)}}e.exports=b;var a,u=!t.browser&&["v0.10","v0.9."].indexOf(t.version.slice(0,5))>-1?r:o.nextTick;b.WritableState=m;var c=Object.create(n(80));c.inherits=n(7);var f={deprecate:n(114)},l=n(172),d=n(119).Buffer,h=i.Uint8Array||function(){};var p,v=n(173);function g(){}function m(e,t){a=a||n(60),e=e||{};var r=t instanceof a;this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.writableObjectMode);var i=e.highWaterMark,c=e.writableHighWaterMark,f=this.objectMode?16:16384;this.highWaterMark=i||0===i?i:r&&(c||0===c)?c:f,this.highWaterMark=Math.floor(this.highWaterMark),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var l=!1===e.decodeStrings;this.decodeStrings=!l,this.defaultEncoding=e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){!function(e,t){var n=e._writableState,r=n.sync,i=n.writecb;if(function(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}(n),t)!function(e,t,n,r,i){--t.pendingcb,n?(o.nextTick(i,r),o.nextTick(M,e,t),e._writableState.errorEmitted=!0,e.emit("error",r)):(i(r),e._writableState.errorEmitted=!0,e.emit("error",r),M(e,t))}(e,n,r,t,i);else{var s=S(n);s||n.corked||n.bufferProcessing||!n.bufferedRequest||_(e,n),r?u(w,e,n,s,i):w(e,n,s,i)}}(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.bufferedRequestCount=0,this.corkedRequestsFree=new s(this)}function b(e){if(a=a||n(60),!(p.call(b,this)||this instanceof a))return new b(e);this._writableState=new m(e,this),this.writable=!0,e&&("function"==typeof e.write&&(this._write=e.write),"function"==typeof e.writev&&(this._writev=e.writev),"function"==typeof e.destroy&&(this._destroy=e.destroy),"function"==typeof e.final&&(this._final=e.final)),l.call(this)}function y(e,t,n,r,i,o,s){t.writelen=r,t.writecb=s,t.writing=!0,t.sync=!0,n?e._writev(i,t.onwrite):e._write(i,o,t.onwrite),t.sync=!1}function w(e,t,n,r){n||function(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit("drain"))}(e,t),t.pendingcb--,r(),M(e,t)}function _(e,t){t.bufferProcessing=!0;var n=t.bufferedRequest;if(e._writev&&n&&n.next){var r=t.bufferedRequestCount,i=new Array(r),o=t.corkedRequestsFree;o.entry=n;for(var a=0,u=!0;n;)i[a]=n,n.isBuf||(u=!1),n=n.next,a+=1;i.allBuffers=u,y(e,t,!0,t.length,i,"",o.finish),t.pendingcb++,t.lastBufferedRequest=null,o.next?(t.corkedRequestsFree=o.next,o.next=null):t.corkedRequestsFree=new s(t),t.bufferedRequestCount=0}else{for(;n;){var c=n.chunk,f=n.encoding,l=n.callback;if(y(e,t,!1,t.objectMode?1:c.length,c,f,l),n=n.next,t.bufferedRequestCount--,t.writing)break}null===n&&(t.lastBufferedRequest=null)}t.bufferedRequest=n,t.bufferProcessing=!1}function S(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function E(e,t){e._final((function(n){t.pendingcb--,n&&e.emit("error",n),t.prefinished=!0,e.emit("prefinish"),M(e,t)}))}function M(e,t){var n=S(t);return n&&(!function(e,t){t.prefinished||t.finalCalled||("function"==typeof e._final?(t.pendingcb++,t.finalCalled=!0,o.nextTick(E,e,t)):(t.prefinished=!0,e.emit("prefinish")))}(e,t),0===t.pendingcb&&(t.finished=!0,e.emit("finish"))),n}c.inherits(b,l),m.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(m.prototype,"buffer",{get:f.deprecate((function(){return this.getBuffer()}),"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(e){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(p=Function.prototype[Symbol.hasInstance],Object.defineProperty(b,Symbol.hasInstance,{value:function(e){return!!p.call(this,e)||this===b&&(e&&e._writableState instanceof m)}})):p=function(e){return e instanceof this},b.prototype.pipe=function(){this.emit("error",new Error("Cannot pipe, not readable"))},b.prototype.write=function(e,t,n){var r,i=this._writableState,s=!1,a=!i.objectMode&&(r=e,d.isBuffer(r)||r instanceof h);return a&&!d.isBuffer(e)&&(e=function(e){return d.from(e)}(e)),"function"==typeof t&&(n=t,t=null),a?t="buffer":t||(t=i.defaultEncoding),"function"!=typeof n&&(n=g),i.ended?function(e,t){var n=new Error("write after end");e.emit("error",n),o.nextTick(t,n)}(this,n):(a||function(e,t,n,r){var i=!0,s=!1;return null===n?s=new TypeError("May not write null values to stream"):"string"==typeof n||void 0===n||t.objectMode||(s=new TypeError("Invalid non-string/buffer chunk")),s&&(e.emit("error",s),o.nextTick(r,s),i=!1),i}(this,i,e,n))&&(i.pendingcb++,s=function(e,t,n,r,i,o){if(!n){var s=function(e,t,n){e.objectMode||!1===e.decodeStrings||"string"!=typeof t||(t=d.from(t,n));return t}(t,r,i);r!==s&&(n=!0,i="buffer",r=s)}var a=t.objectMode?1:r.length;t.length+=a;var u=t.length<t.highWaterMark;u||(t.needDrain=!0);if(t.writing||t.corked){var c=t.lastBufferedRequest;t.lastBufferedRequest={chunk:r,encoding:i,isBuf:n,callback:o,next:null},c?c.next=t.lastBufferedRequest:t.bufferedRequest=t.lastBufferedRequest,t.bufferedRequestCount+=1}else y(e,t,!1,a,r,i,o);return u}(this,i,a,e,t,n)),s},b.prototype.cork=function(){this._writableState.corked++},b.prototype.uncork=function(){var e=this._writableState;e.corked&&(e.corked--,e.writing||e.corked||e.finished||e.bufferProcessing||!e.bufferedRequest||_(this,e))},b.prototype.setDefaultEncoding=function(e){if("string"==typeof e&&(e=e.toLowerCase()),!(["hex","utf8","utf-8","ascii","binary","base64","ucs2","ucs-2","utf16le","utf-16le","raw"].indexOf((e+"").toLowerCase())>-1))throw new TypeError("Unknown encoding: "+e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(b.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),b.prototype._write=function(e,t,n){n(new Error("_write() is not implemented"))},b.prototype._writev=null,b.prototype.end=function(e,t,n){var r=this._writableState;"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!=e&&this.write(e,t),r.corked&&(r.corked=1,this.uncork()),r.ending||r.finished||function(e,t,n){t.ending=!0,M(e,t),n&&(t.finished?o.nextTick(n):e.once("finish",n));t.ended=!0,e.writable=!1}(this,r,n)},Object.defineProperty(b.prototype,"destroyed",{get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),b.prototype.destroy=v.destroy,b.prototype._undestroy=v.undestroy,b.prototype._destroy=function(e,t){this.end(),t(e)}}).call(this,n(20),n(289).setImmediate,n(31))},function(e,t,n){"use strict";var r=n(46);function i(e){this.options=e,this.type=this.options.type,this.blockSize=8,this._init(),this.buffer=new Array(this.blockSize),this.bufferOff=0}e.exports=i,i.prototype._init=function(){},i.prototype.update=function(e){return 0===e.length?[]:"decrypt"===this.type?this._updateDecrypt(e):this._updateEncrypt(e)},i.prototype._buffer=function(e,t){for(var n=Math.min(this.buffer.length-this.bufferOff,e.length-t),r=0;r<n;r++)this.buffer[this.bufferOff+r]=e[t+r];return this.bufferOff+=n,n},i.prototype._flushBuffer=function(e,t){return this._update(this.buffer,0,e,t),this.bufferOff=0,this.blockSize},i.prototype._updateEncrypt=function(e){var t=0,n=0,r=(this.bufferOff+e.length)/this.blockSize|0,i=new Array(r*this.blockSize);0!==this.bufferOff&&(t+=this._buffer(e,t),this.bufferOff===this.buffer.length&&(n+=this._flushBuffer(i,n)));for(var o=e.length-(e.length-t)%this.blockSize;t<o;t+=this.blockSize)this._update(e,t,i,n),n+=this.blockSize;for(;t<e.length;t++,this.bufferOff++)this.buffer[this.bufferOff]=e[t];return i},i.prototype._updateDecrypt=function(e){for(var t=0,n=0,r=Math.ceil((this.bufferOff+e.length)/this.blockSize)-1,i=new Array(r*this.blockSize);r>0;r--)t+=this._buffer(e,t),n+=this._flushBuffer(i,n);return t+=this._buffer(e,t),i},i.prototype.final=function(e){var t,n;return e&&(t=this.update(e)),n="encrypt"===this.type?this._finalEncrypt():this._finalDecrypt(),t?t.concat(n):n},i.prototype._pad=function(e,t){if(0===t)return!1;for(;t<e.length;)e[t++]=0;return!0},i.prototype._finalEncrypt=function(){if(!this._pad(this.buffer,this.bufferOff))return[];var e=new Array(this.blockSize);return this._update(this.buffer,0,e,0),e},i.prototype._unpad=function(e){return e},i.prototype._finalDecrypt=function(){r.equal(this.bufferOff,this.blockSize,"Not enough data to decrypt");var e=new Array(this.blockSize);return this._flushBuffer(e,0),this._unpad(e)}},function(e,t,n){var r=n(304),i=n(312),o=n(187);t.createCipher=t.Cipher=r.createCipher,t.createCipheriv=t.Cipheriv=r.createCipheriv,t.createDecipher=t.Decipher=i.createDecipher,t.createDecipheriv=t.Decipheriv=i.createDecipheriv,t.listCiphers=t.getCiphers=function(){return Object.keys(o)}},function(e,t,n){var r={ECB:n(305),CBC:n(306),CFB:n(307),CFB8:n(308),CFB1:n(309),OFB:n(310),CTR:n(185),GCM:n(185)},i=n(187);for(var o in i)i[o].module=r[i[o].mode];e.exports=i},function(e,t,n){var r;function i(e){this.rand=e}if(e.exports=function(e){return r||(r=new i(null)),r.generate(e)},e.exports.Rand=i,i.prototype.generate=function(e){return this._rand(e)},i.prototype._rand=function(e){if(this.rand.getBytes)return this.rand.getBytes(e);for(var t=new Uint8Array(e),n=0;n<t.length;n++)t[n]=this.rand.getByte();return t},"object"==typeof self)self.crypto&&self.crypto.getRandomValues?i.prototype._rand=function(e){var t=new Uint8Array(e);return self.crypto.getRandomValues(t),t}:self.msCrypto&&self.msCrypto.getRandomValues?i.prototype._rand=function(e){var t=new Uint8Array(e);return self.msCrypto.getRandomValues(t),t}:"object"==typeof window&&(i.prototype._rand=function(){throw new Error("Not implemented yet")});else try{var o=n(316);if("function"!=typeof o.randomBytes)throw new Error("Not supported");i.prototype._rand=function(e){return o.randomBytes(e)}}catch(e){}},function(e,t,n){"use strict";var r=n(70).codes.ERR_STREAM_PREMATURE_CLOSE;function i(){}e.exports=function e(t,n,o){if("function"==typeof n)return e(t,null,n);n||(n={}),o=function(e){var t=!1;return function(){if(!t){t=!0;for(var n=arguments.length,r=new Array(n),i=0;i<n;i++)r[i]=arguments[i];e.apply(this,r)}}}(o||i);var s=n.readable||!1!==n.readable&&t.readable,a=n.writable||!1!==n.writable&&t.writable,u=function(){t.writable||f()},c=t._writableState&&t._writableState.finished,f=function(){a=!1,c=!0,s||o.call(t)},l=t._readableState&&t._readableState.endEmitted,d=function(){s=!1,l=!0,a||o.call(t)},h=function(e){o.call(t,e)},p=function(){var e;return s&&!l?(t._readableState&&t._readableState.ended||(e=new r),o.call(t,e)):a&&!c?(t._writableState&&t._writableState.ended||(e=new r),o.call(t,e)):void 0},v=function(){t.req.on("finish",f)};return!function(e){return e.setHeader&&"function"==typeof e.abort}(t)?a&&!t._writableState&&(t.on("end",u),t.on("close",u)):(t.on("complete",f),t.on("abort",p),t.req?v():t.on("request",v)),t.on("end",d),t.on("finish",f),!1!==n.error&&t.on("error",h),t.on("close",p),function(){t.removeListener("complete",f),t.removeListener("abort",p),t.removeListener("request",v),t.req&&t.req.removeListener("finish",f),t.removeListener("end",u),t.removeListener("close",u),t.removeListener("finish",f),t.removeListener("end",d),t.removeListener("error",h),t.removeListener("close",p)}}},function(e,t,n){(function(t){var r=n(329),i=n(66);function o(e){var t,n=e.modulus.byteLength();do{t=new r(i(n))}while(t.cmp(e.modulus)>=0||!t.umod(e.prime1)||!t.umod(e.prime2));return t}function s(e,n){var i=function(e){var t=o(e);return{blinder:t.toRed(r.mont(e.modulus)).redPow(new r(e.publicExponent)).fromRed(),unblinder:t.invm(e.modulus)}}(n),s=n.modulus.byteLength(),a=new r(e).mul(i.blinder).umod(n.modulus),u=a.toRed(r.mont(n.prime1)),c=a.toRed(r.mont(n.prime2)),f=n.coefficient,l=n.prime1,d=n.prime2,h=u.redPow(n.exponent1).fromRed(),p=c.redPow(n.exponent2).fromRed(),v=h.isub(p).imul(f).umod(l).imul(d);return p.iadd(v).imul(i.unblinder).umod(n.modulus).toArrayLike(t,"be",s)}s.getr=o,e.exports=s}).call(this,n(6).Buffer)},function(e,t,n){"use strict";var r=t;r.version=n(331).version,r.utils=n(47),r.rand=n(124),r.curve=n(199),r.curves=n(128),r.ec=n(342),r.eddsa=n(346)},function(e,t,n){"use strict";var r,i=t,o=n(129),s=n(199),a=n(47).assert;function u(e){"short"===e.type?this.curve=new s.short(e):"edwards"===e.type?this.curve=new s.edwards(e):this.curve=new s.mont(e),this.g=this.curve.g,this.n=this.curve.n,this.hash=e.hash,a(this.g.validate(),"Invalid curve"),a(this.g.mul(this.n).isInfinity(),"Invalid curve, G*N != O")}function c(e,t){Object.defineProperty(i,e,{configurable:!0,enumerable:!0,get:function(){var n=new u(t);return Object.defineProperty(i,e,{configurable:!0,enumerable:!0,value:n}),n}})}i.PresetCurve=u,c("p192",{type:"short",prime:"p192",p:"ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff",a:"ffffffff ffffffff ffffffff fffffffe ffffffff fffffffc",b:"64210519 e59c80e7 0fa7e9ab 72243049 feb8deec c146b9b1",n:"ffffffff ffffffff ffffffff 99def836 146bc9b1 b4d22831",hash:o.sha256,gRed:!1,g:["188da80e b03090f6 7cbf20eb 43a18800 f4ff0afd 82ff1012","07192b95 ffc8da78 631011ed 6b24cdd5 73f977a1 1e794811"]}),c("p224",{type:"short",prime:"p224",p:"ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001",a:"ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff fffffffe",b:"b4050a85 0c04b3ab f5413256 5044b0b7 d7bfd8ba 270b3943 2355ffb4",n:"ffffffff ffffffff ffffffff ffff16a2 e0b8f03e 13dd2945 5c5c2a3d",hash:o.sha256,gRed:!1,g:["b70e0cbd 6bb4bf7f 321390b9 4a03c1d3 56c21122 343280d6 115c1d21","bd376388 b5f723fb 4c22dfe6 cd4375a0 5a074764 44d58199 85007e34"]}),c("p256",{type:"short",prime:null,p:"ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff ffffffff",a:"ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff fffffffc",b:"5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6 3bce3c3e 27d2604b",n:"ffffffff 00000000 ffffffff ffffffff bce6faad a7179e84 f3b9cac2 fc632551",hash:o.sha256,gRed:!1,g:["6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 f4a13945 d898c296","4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece cbb64068 37bf51f5"]}),c("p384",{type:"short",prime:null,p:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe ffffffff 00000000 00000000 ffffffff",a:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe ffffffff 00000000 00000000 fffffffc",b:"b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef",n:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff c7634d81 f4372ddf 581a0db2 48b0a77a ecec196a ccc52973",hash:o.sha384,gRed:!1,g:["aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7","3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f"]}),c("p521",{type:"short",prime:null,p:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff",a:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffc",b:"00000051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b 99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd 3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00",n:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffa 51868783 bf2f966b 7fcc0148 f709a5d0 3bb5c9b8 899c47ae bb6fb71e 91386409",hash:o.sha512,gRed:!1,g:["000000c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139 053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127 a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66","00000118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449 579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901 3fad0761 353c7086 a272c240 88be9476 9fd16650"]}),c("curve25519",{type:"mont",prime:"p25519",p:"7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed",a:"76d06",b:"1",n:"1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed",hash:o.sha256,gRed:!1,g:["9"]}),c("ed25519",{type:"edwards",prime:"p25519",p:"7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed",a:"-1",c:"1",d:"52036cee2b6ffe73 8cc740797779e898 00700a4d4141d8ab 75eb4dca135978a3",n:"1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed",hash:o.sha256,gRed:!1,g:["216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a","6666666666666666666666666666666666666666666666666666666666666658"]});try{r=n(341)}catch(e){r=void 0}c("secp256k1",{type:"short",prime:"k256",p:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f",a:"0",b:"7",n:"ffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141",h:"1",hash:o.sha256,beta:"7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee",lambda:"5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72",basis:[{a:"3086d221a7d46bcde86c90e49284eb15",b:"-e4437ed6010e88286f547fa90abfe4c3"},{a:"114ca50f7a8e2f3f657c1108d9d44cfd8",b:"3086d221a7d46bcde86c90e49284eb15"}],gRed:!1,g:["79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",r]})},function(e,t,n){var r=t;r.utils=n(51),r.common=n(82),r.sha=n(335),r.ripemd=n(339),r.hmac=n(340),r.sha1=r.sha.sha1,r.sha256=r.sha.sha256,r.sha224=r.sha.sha224,r.sha384=r.sha.sha384,r.sha512=r.sha.sha512,r.ripemd160=r.ripemd.ripemd160},function(e,t,n){"use strict";(function(t){var r,i=n(6),o=i.Buffer,s={};for(r in i)i.hasOwnProperty(r)&&"SlowBuffer"!==r&&"Buffer"!==r&&(s[r]=i[r]);var a=s.Buffer={};for(r in o)o.hasOwnProperty(r)&&"allocUnsafe"!==r&&"allocUnsafeSlow"!==r&&(a[r]=o[r]);if(s.Buffer.prototype=o.prototype,a.from&&a.from!==Uint8Array.from||(a.from=function(e,t,n){if("number"==typeof e)throw new TypeError('The "value" argument must not be of type number. Received type '+typeof e);if(e&&void 0===e.length)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e);return o(e,t,n)}),a.alloc||(a.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError('The "size" argument must be of type number. Received type '+typeof e);if(e<0||e>=2*(1<<30))throw new RangeError('The value "'+e+'" is invalid for option "size"');var r=o(e);return t&&0!==t.length?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r}),!s.kStringMaxLength)try{s.kStringMaxLength=t.binding("buffer").kStringMaxLength}catch(e){}s.constants||(s.constants={MAX_LENGTH:s.kMaxLength},s.kStringMaxLength&&(s.constants.MAX_STRING_LENGTH=s.kStringMaxLength)),e.exports=s}).call(this,n(20))},function(e,t,n){"use strict";const r=n(132).Reporter,i=n(83).EncoderBuffer,o=n(83).DecoderBuffer,s=n(46),a=["seq","seqof","set","setof","objid","bool","gentime","utctime","null_","enum","int","objDesc","bitstr","bmpstr","charstr","genstr","graphstr","ia5str","iso646str","numstr","octstr","printstr","t61str","unistr","utf8str","videostr"],u=["key","obj","use","optional","explicit","implicit","def","choice","any","contains"].concat(a);function c(e,t,n){const r={};this._baseState=r,r.name=n,r.enc=e,r.parent=t||null,r.children=null,r.tag=null,r.args=null,r.reverseArgs=null,r.choice=null,r.optional=!1,r.any=!1,r.obj=!1,r.use=null,r.useDecoder=null,r.key=null,r.default=null,r.explicit=null,r.implicit=null,r.contains=null,r.parent||(r.children=[],this._wrap())}e.exports=c;const f=["enc","parent","children","tag","args","reverseArgs","choice","optional","any","obj","use","alteredUse","key","default","explicit","implicit","contains"];c.prototype.clone=function(){const e=this._baseState,t={};f.forEach((function(n){t[n]=e[n]}));const n=new this.constructor(t.parent);return n._baseState=t,n},c.prototype._wrap=function(){const e=this._baseState;u.forEach((function(t){this[t]=function(){const n=new this.constructor(this);return e.children.push(n),n[t].apply(n,arguments)}}),this)},c.prototype._init=function(e){const t=this._baseState;s(null===t.parent),e.call(this),t.children=t.children.filter((function(e){return e._baseState.parent===this}),this),s.equal(t.children.length,1,"Root node can have only one child")},c.prototype._useArgs=function(e){const t=this._baseState,n=e.filter((function(e){return e instanceof this.constructor}),this);e=e.filter((function(e){return!(e instanceof this.constructor)}),this),0!==n.length&&(s(null===t.children),t.children=n,n.forEach((function(e){e._baseState.parent=this}),this)),0!==e.length&&(s(null===t.args),t.args=e,t.reverseArgs=e.map((function(e){if("object"!=typeof e||e.constructor!==Object)return e;const t={};return Object.keys(e).forEach((function(n){n==(0|n)&&(n|=0);const r=e[n];t[r]=n})),t})))},["_peekTag","_decodeTag","_use","_decodeStr","_decodeObjid","_decodeTime","_decodeNull","_decodeInt","_decodeBool","_decodeList","_encodeComposite","_encodeStr","_encodeObjid","_encodeTime","_encodeNull","_encodeInt","_encodeBool"].forEach((function(e){c.prototype[e]=function(){const t=this._baseState;throw new Error(e+" not implemented for encoding: "+t.enc)}})),a.forEach((function(e){c.prototype[e]=function(){const t=this._baseState,n=Array.prototype.slice.call(arguments);return s(null===t.tag),t.tag=e,this._useArgs(n),this}})),c.prototype.use=function(e){s(e);const t=this._baseState;return s(null===t.use),t.use=e,this},c.prototype.optional=function(){return this._baseState.optional=!0,this},c.prototype.def=function(e){const t=this._baseState;return s(null===t.default),t.default=e,t.optional=!0,this},c.prototype.explicit=function(e){const t=this._baseState;return s(null===t.explicit&&null===t.implicit),t.explicit=e,this},c.prototype.implicit=function(e){const t=this._baseState;return s(null===t.explicit&&null===t.implicit),t.implicit=e,this},c.prototype.obj=function(){const e=this._baseState,t=Array.prototype.slice.call(arguments);return e.obj=!0,0!==t.length&&this._useArgs(t),this},c.prototype.key=function(e){const t=this._baseState;return s(null===t.key),t.key=e,this},c.prototype.any=function(){return this._baseState.any=!0,this},c.prototype.choice=function(e){const t=this._baseState;return s(null===t.choice),t.choice=e,this._useArgs(Object.keys(e).map((function(t){return e[t]}))),this},c.prototype.contains=function(e){const t=this._baseState;return s(null===t.use),t.contains=e,this},c.prototype._decode=function(e,t){const n=this._baseState;if(null===n.parent)return e.wrapResult(n.children[0]._decode(e,t));let r,i=n.default,s=!0,a=null;if(null!==n.key&&(a=e.enterKey(n.key)),n.optional){let r=null;if(null!==n.explicit?r=n.explicit:null!==n.implicit?r=n.implicit:null!==n.tag&&(r=n.tag),null!==r||n.any){if(s=this._peekTag(e,r,n.any),e.isError(s))return s}else{const r=e.save();try{null===n.choice?this._decodeGeneric(n.tag,e,t):this._decodeChoice(e,t),s=!0}catch(e){s=!1}e.restore(r)}}if(n.obj&&s&&(r=e.enterObject()),s){if(null!==n.explicit){const t=this._decodeTag(e,n.explicit);if(e.isError(t))return t;e=t}const r=e.offset;if(null===n.use&&null===n.choice){let t;n.any&&(t=e.save());const r=this._decodeTag(e,null!==n.implicit?n.implicit:n.tag,n.any);if(e.isError(r))return r;n.any?i=e.raw(t):e=r}if(t&&t.track&&null!==n.tag&&t.track(e.path(),r,e.length,"tagged"),t&&t.track&&null!==n.tag&&t.track(e.path(),e.offset,e.length,"content"),n.any||(i=null===n.choice?this._decodeGeneric(n.tag,e,t):this._decodeChoice(e,t)),e.isError(i))return i;if(n.any||null!==n.choice||null===n.children||n.children.forEach((function(n){n._decode(e,t)})),n.contains&&("octstr"===n.tag||"bitstr"===n.tag)){const r=new o(i);i=this._getUse(n.contains,e._reporterState.obj)._decode(r,t)}}return n.obj&&s&&(i=e.leaveObject(r)),null===n.key||null===i&&!0!==s?null!==a&&e.exitKey(a):e.leaveKey(a,n.key,i),i},c.prototype._decodeGeneric=function(e,t,n){const r=this._baseState;return"seq"===e||"set"===e?null:"seqof"===e||"setof"===e?this._decodeList(t,e,r.args[0],n):/str$/.test(e)?this._decodeStr(t,e,n):"objid"===e&&r.args?this._decodeObjid(t,r.args[0],r.args[1],n):"objid"===e?this._decodeObjid(t,null,null,n):"gentime"===e||"utctime"===e?this._decodeTime(t,e,n):"null_"===e?this._decodeNull(t,n):"bool"===e?this._decodeBool(t,n):"objDesc"===e?this._decodeStr(t,e,n):"int"===e||"enum"===e?this._decodeInt(t,r.args&&r.args[0],n):null!==r.use?this._getUse(r.use,t._reporterState.obj)._decode(t,n):t.error("unknown tag: "+e)},c.prototype._getUse=function(e,t){const n=this._baseState;return n.useDecoder=this._use(e,t),s(null===n.useDecoder._baseState.parent),n.useDecoder=n.useDecoder._baseState.children[0],n.implicit!==n.useDecoder._baseState.implicit&&(n.useDecoder=n.useDecoder.clone(),n.useDecoder._baseState.implicit=n.implicit),n.useDecoder},c.prototype._decodeChoice=function(e,t){const n=this._baseState;let r=null,i=!1;return Object.keys(n.choice).some((function(o){const s=e.save(),a=n.choice[o];try{const n=a._decode(e,t);if(e.isError(n))return!1;r={type:o,value:n},i=!0}catch(t){return e.restore(s),!1}return!0}),this),i?r:e.error("Choice not matched")},c.prototype._createEncoderBuffer=function(e){return new i(e,this.reporter)},c.prototype._encode=function(e,t,n){const r=this._baseState;if(null!==r.default&&r.default===e)return;const i=this._encodeValue(e,t,n);return void 0===i||this._skipDefault(i,t,n)?void 0:i},c.prototype._encodeValue=function(e,t,n){const i=this._baseState;if(null===i.parent)return i.children[0]._encode(e,t||new r);let o=null;if(this.reporter=t,i.optional&&void 0===e){if(null===i.default)return;e=i.default}let s=null,a=!1;if(i.any)o=this._createEncoderBuffer(e);else if(i.choice)o=this._encodeChoice(e,t);else if(i.contains)s=this._getUse(i.contains,n)._encode(e,t),a=!0;else if(i.children)s=i.children.map((function(n){if("null_"===n._baseState.tag)return n._encode(null,t,e);if(null===n._baseState.key)return t.error("Child should have a key");const r=t.enterKey(n._baseState.key);if("object"!=typeof e)return t.error("Child expected, but input is not object");const i=n._encode(e[n._baseState.key],t,e);return t.leaveKey(r),i}),this).filter((function(e){return e})),s=this._createEncoderBuffer(s);else if("seqof"===i.tag||"setof"===i.tag){if(!i.args||1!==i.args.length)return t.error("Too many args for : "+i.tag);if(!Array.isArray(e))return t.error("seqof/setof, but data is not Array");const n=this.clone();n._baseState.implicit=null,s=this._createEncoderBuffer(e.map((function(n){const r=this._baseState;return this._getUse(r.args[0],e)._encode(n,t)}),n))}else null!==i.use?o=this._getUse(i.use,n)._encode(e,t):(s=this._encodePrimitive(i.tag,e),a=!0);if(!i.any&&null===i.choice){const e=null!==i.implicit?i.implicit:i.tag,n=null===i.implicit?"universal":"context";null===e?null===i.use&&t.error("Tag could be omitted only for .use()"):null===i.use&&(o=this._encodeComposite(e,a,n,s))}return null!==i.explicit&&(o=this._encodeComposite(i.explicit,!1,"context",o)),o},c.prototype._encodeChoice=function(e,t){const n=this._baseState,r=n.choice[e.type];return r||s(!1,e.type+" not found in "+JSON.stringify(Object.keys(n.choice))),r._encode(e.value,t)},c.prototype._encodePrimitive=function(e,t){const n=this._baseState;if(/str$/.test(e))return this._encodeStr(t,e);if("objid"===e&&n.args)return this._encodeObjid(t,n.reverseArgs[0],n.args[1]);if("objid"===e)return this._encodeObjid(t,null,null);if("gentime"===e||"utctime"===e)return this._encodeTime(t,e);if("null_"===e)return this._encodeNull();if("int"===e||"enum"===e)return this._encodeInt(t,n.args&&n.reverseArgs[0]);if("bool"===e)return this._encodeBool(t);if("objDesc"===e)return this._encodeStr(t,e);throw new Error("Unsupported tag: "+e)},c.prototype._isNumstr=function(e){return/^[0-9 ]*$/.test(e)},c.prototype._isPrintstr=function(e){return/^[A-Za-z0-9 '()+,-./:=?]*$/.test(e)}},function(e,t,n){"use strict";const r=n(7);function i(e){this._reporterState={obj:null,path:[],options:e||{},errors:[]}}function o(e,t){this.path=e,this.rethrow(t)}t.Reporter=i,i.prototype.isError=function(e){return e instanceof o},i.prototype.save=function(){const e=this._reporterState;return{obj:e.obj,pathLen:e.path.length}},i.prototype.restore=function(e){const t=this._reporterState;t.obj=e.obj,t.path=t.path.slice(0,e.pathLen)},i.prototype.enterKey=function(e){return this._reporterState.path.push(e)},i.prototype.exitKey=function(e){const t=this._reporterState;t.path=t.path.slice(0,e-1)},i.prototype.leaveKey=function(e,t,n){const r=this._reporterState;this.exitKey(e),null!==r.obj&&(r.obj[t]=n)},i.prototype.path=function(){return this._reporterState.path.join("/")},i.prototype.enterObject=function(){const e=this._reporterState,t=e.obj;return e.obj={},t},i.prototype.leaveObject=function(e){const t=this._reporterState,n=t.obj;return t.obj=e,n},i.prototype.error=function(e){let t;const n=this._reporterState,r=e instanceof o;if(t=r?e:new o(n.path.map((function(e){return"["+JSON.stringify(e)+"]"})).join(""),e.message||e,e.stack),!n.options.partial)throw t;return r||n.errors.push(t),t},i.prototype.wrapResult=function(e){const t=this._reporterState;return t.options.partial?{result:this.isError(e)?null:e,errors:t.errors}:e},r(o,Error),o.prototype.rethrow=function(e){if(this.message=e+" at: "+(this.path||"(shallow)"),Error.captureStackTrace&&Error.captureStackTrace(this,o),!this.stack)try{throw new Error(this.message)}catch(e){this.stack=e.stack}return this}},function(e,t,n){"use strict";function r(e){const t={};return Object.keys(e).forEach((function(n){(0|n)==n&&(n|=0);const r=e[n];t[r]=n})),t}t.tagClass={0:"universal",1:"application",2:"context",3:"private"},t.tagClassByName=r(t.tagClass),t.tag={0:"end",1:"bool",2:"int",3:"bitstr",4:"octstr",5:"null_",6:"objid",7:"objDesc",8:"external",9:"real",10:"enum",11:"embed",12:"utf8str",13:"relativeOid",16:"seq",17:"set",18:"numstr",19:"printstr",20:"t61str",21:"videostr",22:"ia5str",23:"utctime",24:"gentime",25:"graphstr",26:"iso646str",27:"genstr",28:"unistr",29:"charstr",30:"bmpstr"},t.tagByName=r(t.tag)},function(e,t,n){"use strict";n.r(t),n.d(t,"locateWindow",(function(){return i}));var r={};function i(){return"undefined"!=typeof window?window:"undefined"!=typeof self?self:r}},function(e,t,n){var r=n(84),i=n(85);e.exports=function(e){return"symbol"==typeof e||i(e)&&"[object Symbol]"==r(e)}},function(e,t,n){var r=n(395),i=n(411),o=n(413),s=n(414),a=n(415);function u(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t<n;){var r=e[t];this.set(r[0],r[1])}}u.prototype.clear=r,u.prototype.delete=i,u.prototype.get=o,u.prototype.has=s,u.prototype.set=a,e.exports=u},function(e,t,n){var r=n(72)(n(53),"Map");e.exports=r},function(e,t,n){(function(e){var r=n(53),i=n(427),o=t&&!t.nodeType&&t,s=o&&"object"==typeof e&&e&&!e.nodeType&&e,a=s&&s.exports===o?r.Buffer:void 0,u=(a?a.isBuffer:void 0)||i;e.exports=u}).call(this,n(57)(e))},function(e,t,n){var r=n(428),i=n(429),o=n(430),s=o&&o.isTypedArray,a=s?i(s):r;e.exports=a},function(e,t,n){"use strict";n.d(t,"a",(function(){return $s})),n.d(t,"b",(function(){return Ks}));var r=n(44),i=n(221),o=n(88),s=n(89),a=n(50),u=function(e,t){return(u=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};function c(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}u(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var f=function(){return(f=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};function l(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))}function d(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}}Object.create;Object.create;var h,p,v,g,m,b,y,w,_,S,E,M,A,I,k,O,x,C,T,P,N,R,L,j,D,U,B,F,z,q,K,H,V,G,W,$,Y,J,Z,X,Q,ee,te,ne,re,ie,oe,se,ae,ue,ce,fe,le,de,he,pe,ve,ge,me,be,ye,we,_e,Se,Ee,Me,Ae,Ie,ke,Oe,xe,Ce,Te,Pe,Ne,Re,Le,je,De,Ue,Be,Fe,ze,qe,Ke,He,Ve,Ge,We,$e,Ye,Je,Ze,Xe,Qe,et,tt,nt,rt,it,ot,st,at,ut,ct,ft,lt,dt,ht,pt,vt,gt,mt,bt,yt,wt,_t,St,Et,Mt,At,It,kt,Ot,xt,Ct,Tt,Pt,Nt,Rt,Lt,jt,Dt,Ut,Bt,Ft,zt,qt,Kt,Ht,Vt,Gt,Wt,$t,Yt,Jt,Zt,Xt,Qt,en,tn,nn,rn,on,sn,an,un,cn,fn,ln,dn,hn,pn,vn,gn,mn,bn,yn,wn,_n,Sn,En,Mn,An,In,kn,On,xn,Cn,Tn,Pn,Nn,Rn,Ln,jn,Dn,Un,Bn,Fn,zn,qn,Kn,Hn,Vn,Gn,Wn,$n,Yn,Jn,Zn,Xn,Qn,er,tr,nr,rr,ir,or,sr,ar,ur,cr,fr,lr,dr,hr,pr,vr,gr,mr,br,yr,wr,_r,Sr,Er,Mr,Ar,Ir,kr,Or,xr,Cr,Tr,Pr,Nr,Rr,Lr,jr,Dr,Ur,Br,Fr,zr,qr,Kr,Hr,Vr,Gr,Wr,$r,Yr,Jr,Zr=n(0);(h||(h={})).filterSensitiveLog=function(e){return f({},e)},(p||(p={})).filterSensitiveLog=function(e){return f({},e)},(v||(v={})).filterSensitiveLog=function(e){return f({},e)},(g||(g={})).filterSensitiveLog=function(e){return f({},e)},(m||(m={})).filterSensitiveLog=function(e){return f({},e)},(b||(b={})).filterSensitiveLog=function(e){return f({},e)},(y||(y={})).filterSensitiveLog=function(e){return f({},e)},(w||(w={})).filterSensitiveLog=function(e){return f({},e)},(_||(_={})).filterSensitiveLog=function(e){return f({},e)},(S||(S={})).filterSensitiveLog=function(e){return f({},e)},(E||(E={})).filterSensitiveLog=function(e){return f(f({},e),e.SSEKMSKeyId&&{SSEKMSKeyId:Zr.d})},(M||(M={})).filterSensitiveLog=function(e){return f({},e)},(A||(A={})).filterSensitiveLog=function(e){return f({},e)},(I||(I={})).filterSensitiveLog=function(e){return f({},e)},(k||(k={})).filterSensitiveLog=function(e){return f({},e)},(O||(O={})).filterSensitiveLog=function(e){return f(f(f({},e),e.SSEKMSEncryptionContext&&{SSEKMSEncryptionContext:Zr.d}),e.SSEKMSKeyId&&{SSEKMSKeyId:Zr.d})},(x||(x={})).filterSensitiveLog=function(e){return f(f(f(f(f({},e),e.SSEKMSEncryptionContext&&{SSEKMSEncryptionContext:Zr.d}),e.SSECustomerKey&&{SSECustomerKey:Zr.d}),e.CopySourceSSECustomerKey&&{CopySourceSSECustomerKey:Zr.d}),e.SSEKMSKeyId&&{SSEKMSKeyId:Zr.d})},(C||(C={})).filterSensitiveLog=function(e){return f({},e)},(T||(T={})).filterSensitiveLog=function(e){return f({},e)},(P||(P={})).filterSensitiveLog=function(e){return f({},e)},(N||(N={})).filterSensitiveLog=function(e){return f({},e)},(R||(R={})).filterSensitiveLog=function(e){return f({},e)},(L||(L={})).filterSensitiveLog=function(e){return f({},e)},(j||(j={})).filterSensitiveLog=function(e){return f(f(f({},e),e.SSEKMSEncryptionContext&&{SSEKMSEncryptionContext:Zr.d}),e.SSEKMSKeyId&&{SSEKMSKeyId:Zr.d})},(D||(D={})).filterSensitiveLog=function(e){return f(f(f(f({},e),e.SSEKMSKeyId&&{SSEKMSKeyId:Zr.d}),e.SSEKMSEncryptionContext&&{SSEKMSEncryptionContext:Zr.d}),e.SSECustomerKey&&{SSECustomerKey:Zr.d})},(U||(U={})).filterSensitiveLog=function(e){return f({},e)},(B||(B={})).filterSensitiveLog=function(e){return f({},e)},(F||(F={})).filterSensitiveLog=function(e){return f({},e)},(z||(z={})).filterSensitiveLog=function(e){return f({},e)},(q||(q={})).filterSensitiveLog=function(e){return f({},e)},(K||(K={})).filterSensitiveLog=function(e){return f({},e)},(H||(H={})).filterSensitiveLog=function(e){return f({},e)},(V||(V={})).filterSensitiveLog=function(e){return f({},e)},(G||(G={})).filterSensitiveLog=function(e){return f({},e)},(W||(W={})).filterSensitiveLog=function(e){return f({},e)},($||($={})).filterSensitiveLog=function(e){return f({},e)},(Y||(Y={})).filterSensitiveLog=function(e){return f({},e)},(J||(J={})).filterSensitiveLog=function(e){return f({},e)},(Z||(Z={})).filterSensitiveLog=function(e){return f({},e)},(X||(X={})).filterSensitiveLog=function(e){return f({},e)},(Q||(Q={})).filterSensitiveLog=function(e){return f({},e)},(ee||(ee={})).filterSensitiveLog=function(e){return f({},e)},(te||(te={})).filterSensitiveLog=function(e){return f({},e)},(ne||(ne={})).filterSensitiveLog=function(e){return f({},e)},(re||(re={})).filterSensitiveLog=function(e){return f({},e)},(ie||(ie={})).filterSensitiveLog=function(e){return f({},e)},(oe||(oe={})).filterSensitiveLog=function(e){return f({},e)},(se||(se={})).filterSensitiveLog=function(e){return f({},e)},(ae||(ae={})).filterSensitiveLog=function(e){return f({},e)},(ue||(ue={})).filterSensitiveLog=function(e){return f({},e)},(ce||(ce={})).filterSensitiveLog=function(e){return f({},e)},(fe||(fe={})).filterSensitiveLog=function(e){return f({},e)},(le||(le={})).filterSensitiveLog=function(e){return f({},e)},(de||(de={})).filterSensitiveLog=function(e){return f({},e)},(he||(he={})).filterSensitiveLog=function(e){return f({},e)},(pe||(pe={})).filterSensitiveLog=function(e){return f({},e)},(ve||(ve={})).filterSensitiveLog=function(e){return f({},e)},(ge||(ge={})).filterSensitiveLog=function(e){return f({},e)},(me||(me={})).filterSensitiveLog=function(e){return f({},e)},(be||(be={})).filterSensitiveLog=function(e){return f({},e)},(ye||(ye={})).filterSensitiveLog=function(e){return f({},e)},(we||(we={})).filterSensitiveLog=function(e){return f({},e)},(_e||(_e={})).filterSensitiveLog=function(e){return f({},e)},(Se||(Se={})).filterSensitiveLog=function(e){return f({},e)},(Ee||(Ee={})).filterSensitiveLog=function(e){return f({},e)},(Me||(Me={})).filterSensitiveLog=function(e){return f(f({},e),e.KMSMasterKeyID&&{KMSMasterKeyID:Zr.d})},(Ae||(Ae={})).filterSensitiveLog=function(e){return f(f({},e),e.ApplyServerSideEncryptionByDefault&&{ApplyServerSideEncryptionByDefault:Me.filterSensitiveLog(e.ApplyServerSideEncryptionByDefault)})},(Ie||(Ie={})).filterSensitiveLog=function(e){return f(f({},e),e.Rules&&{Rules:e.Rules.map((function(e){return Ae.filterSensitiveLog(e)}))})},(ke||(ke={})).filterSensitiveLog=function(e){return f(f({},e),e.ServerSideEncryptionConfiguration&&{ServerSideEncryptionConfiguration:Ie.filterSensitiveLog(e.ServerSideEncryptionConfiguration)})},(Oe||(Oe={})).filterSensitiveLog=function(e){return f({},e)},(xe||(xe={})).filterSensitiveLog=function(e){return f(f({},e),e.KeyId&&{KeyId:Zr.d})},(Ce||(Ce={})).filterSensitiveLog=function(e){return f({},e)},(Te||(Te={})).filterSensitiveLog=function(e){return f(f({},e),e.SSEKMS&&{SSEKMS:xe.filterSensitiveLog(e.SSEKMS)})},(Pe||(Pe={})).filterSensitiveLog=function(e){return f(f({},e),e.Encryption&&{Encryption:Te.filterSensitiveLog(e.Encryption)})},(Ne||(Ne={})).filterSensitiveLog=function(e){return f(f({},e),e.S3BucketDestination&&{S3BucketDestination:Pe.filterSensitiveLog(e.S3BucketDestination)})},(Re||(Re={})).filterSensitiveLog=function(e){return f({},e)},(Le||(Le={})).filterSensitiveLog=function(e){return f({},e)},(je||(je={})).filterSensitiveLog=function(e){return f(f({},e),e.Destination&&{Destination:Ne.filterSensitiveLog(e.Destination)})},(De||(De={})).filterSensitiveLog=function(e){return f(f({},e),e.InventoryConfiguration&&{InventoryConfiguration:je.filterSensitiveLog(e.InventoryConfiguration)})},(Ue||(Ue={})).filterSensitiveLog=function(e){return f({},e)},(Be||(Be={})).filterSensitiveLog=function(e){return f({},e)},(Fe||(Fe={})).filterSensitiveLog=function(e){return f({},e)},(ze||(ze={})).filterSensitiveLog=function(e){return f({},e)},(qe||(qe={})).filterSensitiveLog=function(e){return f({},e)},(Ke||(Ke={})).filterSensitiveLog=function(e){return f({},e)},(He||(He={})).filterSensitiveLog=function(e){return f({},e)},(Ve||(Ve={})).filterSensitiveLog=function(e){return f({},e)},(Ge||(Ge={})).filterSensitiveLog=function(e){return f({},e)},(We||(We={})).filterSensitiveLog=function(e){return f({},e)},($e||($e={})).filterSensitiveLog=function(e){return f({},e)},(Ye||(Ye={})).filterSensitiveLog=function(e){return f({},e)},(Je||(Je={})).filterSensitiveLog=function(e){return f({},e)},(Ze||(Ze={})).filterSensitiveLog=function(e){return f({},e)},(Xe||(Xe={})).filterSensitiveLog=function(e){return f({},e)},(Qe||(Qe={})).filterSensitiveLog=function(e){return f({},e)},(et||(et={})).filterSensitiveLog=function(e){return f({},e)},(tt||(tt={})).filterSensitiveLog=function(e){return f({},e)},(nt||(nt={})).filterSensitiveLog=function(e){return f({},e)},(rt||(rt={})).filterSensitiveLog=function(e){return f({},e)},(it||(it={})).filterSensitiveLog=function(e){return f({},e)},(ot||(ot={})).filterSensitiveLog=function(e){return f({},e)},(st||(st={})).filterSensitiveLog=function(e){return f({},e)},(at||(at={})).filterSensitiveLog=function(e){return f({},e)},(ut||(ut={})).filterSensitiveLog=function(e){return f({},e)},(ct||(ct={})).filterSensitiveLog=function(e){return f({},e)},(ft||(ft={})).filterSensitiveLog=function(e){return f({},e)},(lt||(lt={})).filterSensitiveLog=function(e){return f({},e)},(dt||(dt={})).filterSensitiveLog=function(e){return f({},e)},(ht||(ht={})).filterSensitiveLog=function(e){return f({},e)},(pt||(pt={})).filterSensitiveLog=function(e){return f({},e)},(vt||(vt={})).filterSensitiveLog=function(e){return f({},e)},(gt||(gt={})).filterSensitiveLog=function(e){return f({},e)},(mt||(mt={})).filterSensitiveLog=function(e){return f({},e)},(bt||(bt={})).filterSensitiveLog=function(e){return f({},e)},(yt||(yt={})).filterSensitiveLog=function(e){return f({},e)},(wt||(wt={})).filterSensitiveLog=function(e){return f({},e)},(_t||(_t={})).filterSensitiveLog=function(e){return f({},e)},(St||(St={})).filterSensitiveLog=function(e){return f({},e)},(Et||(Et={})).filterSensitiveLog=function(e){return f({},e)},(Mt||(Mt={})).filterSensitiveLog=function(e){return f({},e)},(At||(At={})).filterSensitiveLog=function(e){return f({},e)},(It||(It={})).filterSensitiveLog=function(e){return f({},e)},(kt||(kt={})).filterSensitiveLog=function(e){return f({},e)},(Ot||(Ot={})).filterSensitiveLog=function(e){return f({},e)},(xt||(xt={})).filterSensitiveLog=function(e){return f({},e)},(Ct||(Ct={})).filterSensitiveLog=function(e){return f({},e)},(Tt||(Tt={})).filterSensitiveLog=function(e){return f({},e)},(Pt||(Pt={})).filterSensitiveLog=function(e){return f({},e)},(Nt||(Nt={})).filterSensitiveLog=function(e){return f({},e)},(Rt||(Rt={})).filterSensitiveLog=function(e){return f({},e)},(Lt||(Lt={})).filterSensitiveLog=function(e){return f({},e)},(jt||(jt={})).filterSensitiveLog=function(e){return f({},e)},(Dt||(Dt={})).filterSensitiveLog=function(e){return f({},e)},(Ut||(Ut={})).filterSensitiveLog=function(e){return f({},e)},(Bt||(Bt={})).filterSensitiveLog=function(e){return f({},e)},(Ft||(Ft={})).filterSensitiveLog=function(e){return f({},e)},(zt||(zt={})).filterSensitiveLog=function(e){return f({},e)},(qt||(qt={})).filterSensitiveLog=function(e){return f({},e)},(Kt||(Kt={})).filterSensitiveLog=function(e){return f({},e)},(Ht||(Ht={})).filterSensitiveLog=function(e){return f({},e)},(Vt||(Vt={})).filterSensitiveLog=function(e){return f({},e)},(Gt||(Gt={})).filterSensitiveLog=function(e){return f({},e)},(Wt||(Wt={})).filterSensitiveLog=function(e){return f({},e)},($t||($t={})).filterSensitiveLog=function(e){return f({},e)},(Yt||(Yt={})).filterSensitiveLog=function(e){return f({},e)},(Jt||(Jt={})).filterSensitiveLog=function(e){return f({},e)},(Zt||(Zt={})).filterSensitiveLog=function(e){return f(f({},e),e.SSEKMSKeyId&&{SSEKMSKeyId:Zr.d})},(Xt||(Xt={})).filterSensitiveLog=function(e){return f(f({},e),e.SSECustomerKey&&{SSECustomerKey:Zr.d})},(Qt||(Qt={})).filterSensitiveLog=function(e){return f({},e)},(en||(en={})).filterSensitiveLog=function(e){return f({},e)},(tn||(tn={})).filterSensitiveLog=function(e){return f({},e)},(nn||(nn={})).filterSensitiveLog=function(e){return f({},e)},(rn||(rn={})).filterSensitiveLog=function(e){return f({},e)},(on||(on={})).filterSensitiveLog=function(e){return f({},e)},(sn||(sn={})).filterSensitiveLog=function(e){return f({},e)},(an||(an={})).filterSensitiveLog=function(e){return f({},e)},(un||(un={})).filterSensitiveLog=function(e){return f({},e)},(cn||(cn={})).filterSensitiveLog=function(e){return f({},e)},(fn||(fn={})).filterSensitiveLog=function(e){return f({},e)},(ln||(ln={})).filterSensitiveLog=function(e){return f({},e)},(dn||(dn={})).filterSensitiveLog=function(e){return f({},e)},(hn||(hn={})).filterSensitiveLog=function(e){return f({},e)},(pn||(pn={})).filterSensitiveLog=function(e){return f({},e)},(vn||(vn={})).filterSensitiveLog=function(e){return f({},e)},(gn||(gn={})).filterSensitiveLog=function(e){return f({},e)},(mn||(mn={})).filterSensitiveLog=function(e){return f({},e)},(bn||(bn={})).filterSensitiveLog=function(e){return f({},e)},(yn||(yn={})).filterSensitiveLog=function(e){return f({},e)},(wn||(wn={})).filterSensitiveLog=function(e){return f({},e)},(_n||(_n={})).filterSensitiveLog=function(e){return f({},e)},(Sn||(Sn={})).filterSensitiveLog=function(e){return f({},e)},(En||(En={})).filterSensitiveLog=function(e){return f(f({},e),e.SSEKMSKeyId&&{SSEKMSKeyId:Zr.d})},(Mn||(Mn={})).filterSensitiveLog=function(e){return f(f({},e),e.SSECustomerKey&&{SSECustomerKey:Zr.d})},(An||(An={})).filterSensitiveLog=function(e){return f({},e)},(In||(In={})).filterSensitiveLog=function(e){return f({},e)},(kn||(kn={})).filterSensitiveLog=function(e){return f(f({},e),e.InventoryConfigurationList&&{InventoryConfigurationList:e.InventoryConfigurationList.map((function(e){return je.filterSensitiveLog(e)}))})},(On||(On={})).filterSensitiveLog=function(e){return f({},e)},(xn||(xn={})).filterSensitiveLog=function(e){return f({},e)},(Cn||(Cn={})).filterSensitiveLog=function(e){return f({},e)},(Tn||(Tn={})).filterSensitiveLog=function(e){return f({},e)},(Pn||(Pn={})).filterSensitiveLog=function(e){return f({},e)},(Nn||(Nn={})).filterSensitiveLog=function(e){return f({},e)},(Rn||(Rn={})).filterSensitiveLog=function(e){return f({},e)},(Ln||(Ln={})).filterSensitiveLog=function(e){return f({},e)},(jn||(jn={})).filterSensitiveLog=function(e){return f({},e)},(Dn||(Dn={})).filterSensitiveLog=function(e){return f({},e)},(Un||(Un={})).filterSensitiveLog=function(e){return f({},e)},(Bn||(Bn={})).filterSensitiveLog=function(e){return f({},e)},(Fn||(Fn={})).filterSensitiveLog=function(e){return f({},e)},(zn||(zn={})).filterSensitiveLog=function(e){return f({},e)},(qn||(qn={})).filterSensitiveLog=function(e){return f({},e)},(Kn||(Kn={})).filterSensitiveLog=function(e){return f({},e)},(Hn||(Hn={})).filterSensitiveLog=function(e){return f({},e)},(Vn||(Vn={})).filterSensitiveLog=function(e){return f({},e)},(Gn||(Gn={})).filterSensitiveLog=function(e){return f({},e)},(Wn||(Wn={})).filterSensitiveLog=function(e){return f({},e)},($n||($n={})).filterSensitiveLog=function(e){return f({},e)},(Yn||(Yn={})).filterSensitiveLog=function(e){return f({},e)},(Jn||(Jn={})).filterSensitiveLog=function(e){return f({},e)},(Zn||(Zn={})).filterSensitiveLog=function(e){return f({},e)},(Xn||(Xn={})).filterSensitiveLog=function(e){return f({},e)},(Qn||(Qn={})).filterSensitiveLog=function(e){return f({},e)},(er||(er={})).filterSensitiveLog=function(e){return f({},e)},(tr||(tr={})).filterSensitiveLog=function(e){return f(f({},e),e.ServerSideEncryptionConfiguration&&{ServerSideEncryptionConfiguration:Ie.filterSensitiveLog(e.ServerSideEncryptionConfiguration)})},(nr||(nr={})).filterSensitiveLog=function(e){return f(f({},e),e.InventoryConfiguration&&{InventoryConfiguration:je.filterSensitiveLog(e.InventoryConfiguration)})},(rr||(rr={})).filterSensitiveLog=function(e){return f({},e)},(ir||(ir={})).filterSensitiveLog=function(e){return f({},e)},(or||(or={})).filterSensitiveLog=function(e){return f({},e)},(sr||(sr={})).filterSensitiveLog=function(e){return f({},e)},(ar||(ar={})).filterSensitiveLog=function(e){return f({},e)},(ur||(ur={})).filterSensitiveLog=function(e){return f({},e)},(cr||(cr={})).filterSensitiveLog=function(e){return f({},e)},(fr||(fr={})).filterSensitiveLog=function(e){return f({},e)},(lr||(lr={})).filterSensitiveLog=function(e){return f({},e)},(dr||(dr={})).filterSensitiveLog=function(e){return f({},e)},(hr||(hr={})).filterSensitiveLog=function(e){return f({},e)},(pr||(pr={})).filterSensitiveLog=function(e){return f({},e)},(vr||(vr={})).filterSensitiveLog=function(e){return f({},e)},(gr||(gr={})).filterSensitiveLog=function(e){return f({},e)},(mr||(mr={})).filterSensitiveLog=function(e){return f({},e)},(br||(br={})).filterSensitiveLog=function(e){return f({},e)},(yr||(yr={})).filterSensitiveLog=function(e){return f({},e)},(wr||(wr={})).filterSensitiveLog=function(e){return f(f(f({},e),e.SSEKMSEncryptionContext&&{SSEKMSEncryptionContext:Zr.d}),e.SSEKMSKeyId&&{SSEKMSKeyId:Zr.d})},(_r||(_r={})).filterSensitiveLog=function(e){return f(f(f(f({},e),e.SSECustomerKey&&{SSECustomerKey:Zr.d}),e.SSEKMSEncryptionContext&&{SSEKMSEncryptionContext:Zr.d}),e.SSEKMSKeyId&&{SSEKMSKeyId:Zr.d})},(Sr||(Sr={})).filterSensitiveLog=function(e){return f({},e)},(Er||(Er={})).filterSensitiveLog=function(e){return f({},e)},(Mr||(Mr={})).filterSensitiveLog=function(e){return f({},e)},(Ar||(Ar={})).filterSensitiveLog=function(e){return f({},e)},(Ir||(Ir={})).filterSensitiveLog=function(e){return f({},e)},(kr||(kr={})).filterSensitiveLog=function(e){return f({},e)},(Or||(Or={})).filterSensitiveLog=function(e){return f({},e)},(xr||(xr={})).filterSensitiveLog=function(e){return f({},e)},(Cr||(Cr={})).filterSensitiveLog=function(e){return f({},e)},(Tr||(Tr={})).filterSensitiveLog=function(e){return f({},e)},(Pr||(Pr={})).filterSensitiveLog=function(e){return f({},e)},(Nr||(Nr={})).filterSensitiveLog=function(e){return f({},e)},(Rr||(Rr={})).filterSensitiveLog=function(e){return f({},e)},(Lr||(Lr={})).filterSensitiveLog=function(e){return f({},e)},(jr||(jr={})).filterSensitiveLog=function(e){return f(f({},e),e.KMSKeyId&&{KMSKeyId:Zr.d})},(Dr||(Dr={})).filterSensitiveLog=function(e){return f({},e)},(Ur||(Ur={})).filterSensitiveLog=function(e){return f(f({},e),e.Encryption&&{Encryption:jr.filterSensitiveLog(e.Encryption)})},(Br||(Br={})).filterSensitiveLog=function(e){return f(f({},e),e.S3&&{S3:Ur.filterSensitiveLog(e.S3)})},function(e){e.IGNORE="IGNORE",e.NONE="NONE",e.USE="USE"}(Fr||(Fr={})),(zr||(zr={})).filterSensitiveLog=function(e){return f({},e)},function(e){e.DOCUMENT="DOCUMENT",e.LINES="LINES"}(qr||(qr={})),(Kr||(Kr={})).filterSensitiveLog=function(e){return f({},e)},(Hr||(Hr={})).filterSensitiveLog=function(e){return f({},e)},(Vr||(Vr={})).filterSensitiveLog=function(e){return f({},e)},function(e){e.ALWAYS="ALWAYS",e.ASNEEDED="ASNEEDED"}(Gr||(Gr={})),(Wr||(Wr={})).filterSensitiveLog=function(e){return f({},e)},($r||($r={})).filterSensitiveLog=function(e){return f({},e)},(Yr||(Yr={})).filterSensitiveLog=function(e){return f({},e)},(Jr||(Jr={})).filterSensitiveLog=function(e){return f({},e)};var Xr=n(2),Qr=n(1);var ei=function(){function e(e,t){void 0===t&&(t=[]),this.name=e,this.children=t,this.attributes={}}return e.prototype.withName=function(e){return this.name=e,this},e.prototype.addAttribute=function(e,t){return this.attributes[e]=t,this},e.prototype.addChildNode=function(e){return this.children.push(e),this},e.prototype.removeAttribute=function(e){return delete this.attributes[e],this},e.prototype.toString=function(){var e,t,n=Boolean(this.children.length),r="<"+this.name,i=this.attributes;try{for(var o=Object(Qr.__values)(Object.keys(i)),s=o.next();!s.done;s=o.next()){var a=s.value,u=i[a];null!=u&&(r+=" "+a+'="'+(""+u).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")+'"')}}catch(t){e={error:t}}finally{try{s&&!s.done&&(t=o.return)&&t.call(o)}finally{if(e)throw e.error}}return r+(n?">"+this.children.map((function(e){return e.toString()})).join("")+"</"+this.name+">":"/>")},e}();var ti=function(){function e(e){this.value=e}return e.prototype.toString=function(){return(""+this.value).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")},e}(),ni=n(253),ri=function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c;return d(this,(function(l){switch(l.label){case 0:return r=[f({},e)],c={},[4,Ai(e.body,t)];case 1:switch(n=f.apply(void 0,r.concat([(c.body=l.sent(),c)])),o="UnknownError",o=Ii(e,n.body),o){case"NoSuchUpload":case"com.amazonaws.s3#NoSuchUpload":return[3,2]}return[3,4];case 2:return s=[{}],[4,pi(n,t)];case 3:return i=f.apply(void 0,[f.apply(void 0,s.concat([l.sent()])),{name:o,$metadata:Si(e)}]),[3,5];case 4:a=n.body,o=a.code||a.Code||o,i=f(f({},a),{name:""+o,message:a.message||a.Message||o,$fault:"client",$metadata:Si(e)}),l.label=5;case 5:return u=i.message||i.Message||o,i.message=u,delete i.Message,[2,Promise.reject(Object.assign(new Error(u),i))]}}))}))},ii=function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u;return d(this,(function(c){switch(c.label){case 0:return r=[f({},e)],u={},[4,Ai(e.body,t)];case 1:return n=f.apply(void 0,r.concat([(u.body=c.sent(),u)])),o="UnknownError",o=Ii(e,n.body),s=n.body,o=s.code||s.Code||o,i=f(f({},s),{name:""+o,message:s.message||s.Message||o,$fault:"client",$metadata:Si(e)}),a=i.message||i.Message||o,i.message=a,delete i.Message,[2,Promise.reject(Object.assign(new Error(a),i))]}}))}))},oi=function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u;return d(this,(function(c){switch(c.label){case 0:return r=[f({},e)],u={},[4,Ai(e.body,t)];case 1:return n=f.apply(void 0,r.concat([(u.body=c.sent(),u)])),o="UnknownError",o=Ii(e,n.body),s=n.body,o=s.code||s.Code||o,i=f(f({},s),{name:""+o,message:s.message||s.Message||o,$fault:"client",$metadata:Si(e)}),a=i.message||i.Message||o,i.message=a,delete i.Message,[2,Promise.reject(Object.assign(new Error(a),i))]}}))}))},si=function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u;return d(this,(function(c){switch(c.label){case 0:return r=[f({},e)],u={},[4,Ai(e.body,t)];case 1:return n=f.apply(void 0,r.concat([(u.body=c.sent(),u)])),o="UnknownError",o=Ii(e,n.body),s=n.body,o=s.code||s.Code||o,i=f(f({},s),{name:""+o,message:s.message||s.Message||o,$fault:"client",$metadata:Si(e)}),a=i.message||i.Message||o,i.message=a,delete i.Message,[2,Promise.reject(Object.assign(new Error(a),i))]}}))}))},ai=function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c;return d(this,(function(l){switch(l.label){case 0:return r=[f({},e)],c={},[4,Ai(e.body,t)];case 1:switch(n=f.apply(void 0,r.concat([(c.body=l.sent(),c)])),o="UnknownError",o=Ii(e,n.body),o){case"NoSuchKey":case"com.amazonaws.s3#NoSuchKey":return[3,2]}return[3,4];case 2:return s=[{}],[4,hi(n,t)];case 3:return i=f.apply(void 0,[f.apply(void 0,s.concat([l.sent()])),{name:o,$metadata:Si(e)}]),[3,5];case 4:a=n.body,o=a.code||a.Code||o,i=f(f({},a),{name:""+o,message:a.message||a.Message||o,$fault:"client",$metadata:Si(e)}),l.label=5;case 5:return u=i.message||i.Message||o,i.message=u,delete i.Message,[2,Promise.reject(Object.assign(new Error(u),i))]}}))}))},ui=function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c;return d(this,(function(l){switch(l.label){case 0:return r=[f({},e)],c={},[4,Ai(e.body,t)];case 1:switch(n=f.apply(void 0,r.concat([(c.body=l.sent(),c)])),o="UnknownError",o=Ii(e,n.body),o){case"NoSuchBucket":case"com.amazonaws.s3#NoSuchBucket":return[3,2]}return[3,4];case 2:return s=[{}],[4,di(n,t)];case 3:return i=f.apply(void 0,[f.apply(void 0,s.concat([l.sent()])),{name:o,$metadata:Si(e)}]),[3,5];case 4:a=n.body,o=a.code||a.Code||o,i=f(f({},a),{name:""+o,message:a.message||a.Message||o,$fault:"client",$metadata:Si(e)}),l.label=5;case 5:return u=i.message||i.Message||o,i.message=u,delete i.Message,[2,Promise.reject(Object.assign(new Error(u),i))]}}))}))},ci=function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u;return d(this,(function(c){switch(c.label){case 0:return r=[f({},e)],u={},[4,Ai(e.body,t)];case 1:return n=f.apply(void 0,r.concat([(u.body=c.sent(),u)])),o="UnknownError",o=Ii(e,n.body),s=n.body,o=s.code||s.Code||o,i=f(f({},s),{name:""+o,message:s.message||s.Message||o,$fault:"client",$metadata:Si(e)}),a=i.message||i.Message||o,i.message=a,delete i.Message,[2,Promise.reject(Object.assign(new Error(a),i))]}}))}))},fi=function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u;return d(this,(function(c){switch(c.label){case 0:return r=[f({},e)],u={},[4,Ai(e.body,t)];case 1:return n=f.apply(void 0,r.concat([(u.body=c.sent(),u)])),o="UnknownError",o=Ii(e,n.body),s=n.body,o=s.code||s.Code||o,i=f(f({},s),{name:""+o,message:s.message||s.Message||o,$fault:"client",$metadata:Si(e)}),a=i.message||i.Message||o,i.message=a,delete i.Message,[2,Promise.reject(Object.assign(new Error(a),i))]}}))}))},li=function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u;return d(this,(function(c){switch(c.label){case 0:return r=[f({},e)],u={},[4,Ai(e.body,t)];case 1:return n=f.apply(void 0,r.concat([(u.body=c.sent(),u)])),o="UnknownError",o=Ii(e,n.body),s=n.body,o=s.code||s.Code||o,i=f(f({},s),{name:""+o,message:s.message||s.Message||o,$fault:"client",$metadata:Si(e)}),a=i.message||i.Message||o,i.message=a,delete i.Message,[2,Promise.reject(Object.assign(new Error(a),i))]}}))}))},di=function(e,t){return l(void 0,void 0,void 0,(function(){var t;return d(this,(function(n){return t={name:"NoSuchBucket",$fault:"client",$metadata:Si(e)},e.body,[2,t]}))}))},hi=function(e,t){return l(void 0,void 0,void 0,(function(){var t;return d(this,(function(n){return t={name:"NoSuchKey",$fault:"client",$metadata:Si(e)},e.body,[2,t]}))}))},pi=function(e,t){return l(void 0,void 0,void 0,(function(){var t;return d(this,(function(n){return t={name:"NoSuchUpload",$fault:"client",$metadata:Si(e)},e.body,[2,t]}))}))},vi=function(e,t){var n=new ei("CompletedMultipartUpload");void 0!==e.Parts&&gi(e.Parts,t).map((function(e){e=e.withName("Part"),n.addChildNode(e)}));return n},gi=function(e,t){return e.map((function(e){return function(e,t){var n=new ei("CompletedPart");if(void 0!==e.ETag){var r=new ei("ETag").addChildNode(new ti(e.ETag)).withName("ETag");n.addChildNode(r)}if(void 0!==e.PartNumber){r=new ei("PartNumber").addChildNode(new ti(String(e.PartNumber))).withName("PartNumber");n.addChildNode(r)}return n}(e).withName("member")}))},mi=function(e,t){return(e||[]).map((function(e){return function(e,t){var n={Prefix:void 0};return void 0!==e.Prefix&&(n.Prefix=e.Prefix),n}(e)}))},bi=function(e,t){var n={ID:void 0,DisplayName:void 0};return void 0!==e.ID&&(n.ID=e.ID),void 0!==e.DisplayName&&(n.DisplayName=e.DisplayName),n},yi=function(e,t){return(e||[]).map((function(e){return function(e,t){var n={Size:void 0,ETag:void 0,Owner:void 0,StorageClass:void 0,Key:void 0,LastModified:void 0};return void 0!==e.Size&&(n.Size=parseInt(e.Size)),void 0!==e.ETag&&(n.ETag=e.ETag),void 0!==e.Owner&&(n.Owner=wi(e.Owner,t)),void 0!==e.StorageClass&&(n.StorageClass=e.StorageClass),void 0!==e.Key&&(n.Key=e.Key),void 0!==e.LastModified&&(n.LastModified=new Date(e.LastModified)),n}(e,t)}))},wi=function(e,t){var n={DisplayName:void 0,ID:void 0};return void 0!==e.DisplayName&&(n.DisplayName=e.DisplayName),void 0!==e.ID&&(n.ID=e.ID),n},_i=function(e,t){return(e||[]).map((function(e){return function(e,t){var n={Size:void 0,LastModified:void 0,PartNumber:void 0,ETag:void 0};return void 0!==e.Size&&(n.Size=parseInt(e.Size)),void 0!==e.LastModified&&(n.LastModified=new Date(e.LastModified)),void 0!==e.PartNumber&&(n.PartNumber=parseInt(e.PartNumber)),void 0!==e.ETag&&(n.ETag=e.ETag),n}(e)}))},Si=function(e){return{httpStatusCode:e.statusCode,httpHeaders:e.headers,requestId:e.headers["x-amzn-requestid"]}},Ei=function(e,t){return void 0===e&&(e=new Uint8Array),e instanceof Uint8Array?Promise.resolve(e):t.streamCollector(e)||Promise.resolve(new Uint8Array)},Mi=function(e){return!(void 0===e||""===e||Object.getOwnPropertyNames(e).includes("length")&&0==e.length||Object.getOwnPropertyNames(e).includes("size")&&0==e.size)},Ai=function(e,t){return function(e,t){return Ei(e,t).then((function(e){return t.utf8Encoder(e)}))}(e,t).then((function(e){if(e.length){var t=Object(ni.parse)(e,{attributeNamePrefix:"",ignoreAttributes:!1,parseNodeValue:!1,tagValueProcessor:function(e,t){return e.replace(/&/g,"&").replace(/'/g,"'").replace(/"/g,'"').replace(/>/g,">").replace(/</g,"<")}}),n=Object.keys(t)[0],r=t[n];return r["#text"]&&(r[n]=r["#text"],delete r["#text"]),Object(Zr.h)(r)}return{}}))},Ii=function(e,t){return void 0!==t.Code?t.Code:404==e.statusCode?"NotFound":""},ki=function(e){return"string"==typeof e&&0===e.indexOf("arn:")&&e.split(":").length>=6},Oi=/^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$/,xi=/(\d+\.){3}\d+/,Ci=/\.\./,Ti=/\./,Pi=/^(.+\.)?s3[.-]([a-z0-9-]+)\./,Ni=/^s3(-external-1)?\.amazonaws\.com$/,Ri=function(e){return ji(e)?e.replace(/fips-|-fips/,""):e},Li=function(e){var t=e.match(Pi);return[t[2],e.replace(new RegExp("^"+t[0]),"")]},ji=function(e){return e.startsWith("fips-")||e.endsWith("-fips")},Di=function(e,t){return e===t||Ri(e)===t||e===Ri(t)},Ui=function(e,t){if(void 0===t&&(t={tlsCompatible:!0}),e.length>=64||!/^[a-z0-9][a-z0-9.-]+[a-z0-9]$/.test(e)||/(\d+\.){3}\d+/.test(e)||/[.-]{2}/.test(e)||(null==t?void 0:t.tlsCompatible)&&Ti.test(e))throw new Error("Invalid DNS label "+e)},Bi=function(e){var t=e.baseHostname;return Pi.test(t)?function(e){return"string"==typeof e.bucketName}(e)?zi(e):Fi(e):{bucketEndpoint:!1,hostname:t}},Fi=function(e){var t,n=Object(Qr.__read)((t=e.baseHostname,Ni.test(t)?[t.replace(".amazonaws.com",""),"amazonaws.com"]:Li(t)),2),r=n[0],i=n[1],o=e.pathStyleEndpoint,s=e.dualstackEndpoint,a=void 0!==s&&s,u=e.accelerateEndpoint,c=void 0!==u&&u,f=e.tlsCompatible,l=void 0===f||f,d=e.useArnRegion,h=e.bucketName,p=e.clientPartition,v=void 0===p?"aws":p,g=e.clientSigningRegion,m=void 0===g?r:g;!function(e){if(e.pathStyleEndpoint)throw new Error("Path-style S3 endpoint is not supported when bucket is an ARN");if(e.accelerateEndpoint)throw new Error("Accelerate endpoint is not supported when bucket is an ARN");if(!e.tlsCompatible)throw new Error("HTTPS is required when bucket is an ARN")}({pathStyleEndpoint:o,accelerateEndpoint:c,tlsCompatible:l});var b=h.service,y=h.partition,w=h.accountId,_=h.region,S=h.resource;!function(e){if("s3"!==e&&"s3-outposts"!==e)throw new Error("Expect 's3' or 's3-outposts' in ARN service component")}(b),function(e,t){if(e!==t.clientPartition)throw new Error('Partition in ARN is incompatible, got "'+e+'" but expected "'+t.clientPartition+'"')}(y,{clientPartition:v}),function(e){if(!/[0-9]{12}/.exec(e))throw new Error("Access point ARN accountID does not match regex '[0-9]{12}'")}(w),function(e,t){if(""===e)throw new Error("ARN region is empty");if(!t.useArnRegion&&!Di(e,t.clientRegion)&&!Di(e,t.clientSigningRegion))throw new Error("Region in ARN is incompatible, got "+e+" but expected "+t.clientRegion);if(t.useArnRegion&&ji(e))throw new Error("Endpoint does not support FIPS region")}(_,{useArnRegion:d,clientRegion:r,clientSigningRegion:m});var E=function(e){var t=e.includes(":")?":":"/",n=Object(Qr.__read)(e.split(t)),r=n[0],i=n.slice(1);if("accesspoint"===r){if(1!==i.length||""===i[0])throw new Error("Access Point ARN should have one resource accesspoint"+t+"{accesspointname}");return{accesspointName:i[0]}}if("outpost"===r){if(!i[0]||"accesspoint"!==i[1]||!i[2]||3!==i.length)throw new Error("Outpost ARN should have resource outpost"+t+"{outpostId}"+t+"accesspoint"+t+"{accesspointName}");var o=Object(Qr.__read)(i,3),s=o[0];o[1];return{outpostId:s,accesspointName:o[2]}}throw new Error("ARN resource should begin with 'accesspoint"+t+"' or 'outpost"+t+"'")}(S),M=E.accesspointName,A=E.outpostId;Ui(M+"-"+w,{tlsCompatible:l});var I=d?_:r,k=d?_:m;return A?(function(e){if("s3-outposts"!==e)throw new Error("Expect 's3-posts' in Outpost ARN service component")}(b),Ui(A,{tlsCompatible:l}),function(e){if(e)throw new Error("Dualstack endpoint is not supported with Outpost")}(a),function(e){if(ji(null!=e?e:""))throw new Error("FIPS region is not supported with Outpost, got "+e)}(I),{bucketEndpoint:!0,hostname:M+"-"+w+"."+A+".s3-outposts."+I+"."+i,signingRegion:k,signingService:"s3-outposts"}):(function(e){if("s3"!==e)throw new Error("Expect 's3' in Accesspoint ARN service component")}(b),{bucketEndpoint:!0,hostname:M+"-"+w+".s3-accesspoint"+(a?".dualstack":"")+"."+I+"."+i,signingRegion:k})},zi=function(e){var t,n=e.accelerateEndpoint,r=void 0!==n&&n,i=e.baseHostname,o=e.bucketName,s=e.dualstackEndpoint,a=void 0!==s&&s,u=e.pathStyleEndpoint,c=void 0!==u&&u,f=e.tlsCompatible,l=void 0===f||f,d=Object(Qr.__read)((t=i,Ni.test(t)?["us-east-1","amazonaws.com"]:Li(t)),2),h=d[0],p=d[1];return c||!function(e){return Oi.test(e)&&!xi.test(e)&&!Ci.test(e)}(o)||l&&Ti.test(o)?{bucketEndpoint:!1,hostname:a?"s3.dualstack."+h+"."+p:i}:(r?i="s3-accelerate"+(a?".dualstack":"")+"."+p:a&&(i="s3.dualstack."+h+"."+p),{bucketEndpoint:!0,hostname:o+"."+i})},qi=function(e){return function(t,n){return function(r){return Object(Qr.__awaiter)(void 0,void 0,void 0,(function(){var i,o,s,a,u,c,f,l,d,h,p,v,g,m,b,y,w;return Object(Qr.__generator)(this,(function(_){switch(_.label){case 0:return i=r.input.Bucket,o=e.bucketEndpoint,s=r.request,Xr.a.isInstance(s)?e.bucketEndpoint?(s.hostname=i,[3,6]):[3,1]:[3,7];case 1:return ki(i)?(a=function(e){var t=e.split(":");if(t.length<6||"arn"!==t[0])throw new Error("Malformed ARN");var n=Object(Qr.__read)(t);return{partition:n[1],service:n[2],region:n[3],accountId:n[4],resource:n.slice(5).join(":")}}(i),c=Ri,[4,e.region()]):[3,5];case 2:return u=c.apply(void 0,[_.sent()]),[4,e.regionInfoProvider(u)];case 3:return f=_.sent()||{},l=f.partition,d=f.signingRegion,h=void 0===d?u:d,[4,e.useArnRegion()];case 4:return p=_.sent(),v=Bi({bucketName:a,baseHostname:s.hostname,accelerateEndpoint:e.useAccelerateEndpoint,dualstackEndpoint:e.useDualstackEndpoint,pathStyleEndpoint:e.forcePathStyle,tlsCompatible:"https:"===s.protocol,useArnRegion:p,clientPartition:l,clientSigningRegion:h}),y=v.hostname,w=v.bucketEndpoint,g=v.signingRegion,m=v.signingService,g&&g!==h&&(n.signing_region=g),m&&"s3"!==m&&(n.signing_service=m),s.hostname=y,o=w,[3,6];case 5:b=Bi({bucketName:i,baseHostname:s.hostname,accelerateEndpoint:e.useAccelerateEndpoint,dualstackEndpoint:e.useDualstackEndpoint,pathStyleEndpoint:e.forcePathStyle,tlsCompatible:"https:"===s.protocol}),y=b.hostname,w=b.bucketEndpoint,s.hostname=y,o=w,_.label=6;case 6:o&&(s.path=s.path.replace(/^(\/)?[^\/]+/,""),""===s.path&&(s.path="/")),_.label=7;case 7:return[2,t(Object(Qr.__assign)(Object(Qr.__assign)({},r),{request:s}))]}}))}))}}},Ki={tags:["BUCKET_ENDPOINT"],name:"bucketEndpointMiddleware",relation:"before",toMiddleware:"hostHeaderMiddleware"},Hi=function(e){return{applyToStack:function(t){t.addRelativeTo(qi(e),Ki)}}};var Vi=n(10);var Gi={name:"ssecMiddleware",step:"initialize",tags:["SSE"]},Wi=function(e){return{applyToStack:function(t){t.add(function(e){var t=this;return function(n){return function(r){return Object(Qr.__awaiter)(t,void 0,void 0,(function(){var t,i,o,s,a,u,c,f,l,d,h,p,v,g,m,b,y;return Object(Qr.__generator)(this,(function(w){switch(w.label){case 0:t=Object(Qr.__assign)({},r.input),i=[{target:"SSECustomerKey",hash:"SSECustomerKeyMD5"},{target:"CopySourceSSECustomerKey",hash:"CopySourceSSECustomerKeyMD5"}],w.label=1;case 1:w.trys.push([1,6,7,8]),o=Object(Qr.__values)(i),s=o.next(),w.label=2;case 2:return s.done?[3,5]:(a=s.value,(u=t[a.target])?(c=ArrayBuffer.isView(u)?new Uint8Array(u.buffer,u.byteOffset,u.byteLength):"string"==typeof u?e.utf8Decoder(u):new Uint8Array(u),f=e.base64Encoder(c),(l=new e.md5).update(c),d=[Object(Qr.__assign)({},t)],(y={})[a.target]=f,h=a.hash,v=(p=e).base64Encoder,[4,l.digest()]):[3,4]);case 3:t=Qr.__assign.apply(void 0,d.concat([(y[h]=v.apply(p,[w.sent()]),y)])),w.label=4;case 4:return s=o.next(),[3,2];case 5:return[3,8];case 6:return g=w.sent(),m={error:g},[3,8];case 7:try{s&&!s.done&&(b=o.return)&&b.call(o)}finally{if(m)throw m.error}return[7];case 8:return[2,n(Object(Qr.__assign)(Object(Qr.__assign)({},r),{input:t}))]}}))}))}}}(e),Gi)}}},$i=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return c(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(Vi.a)(t,this.serialize,this.deserialize)),this.middlewareStack.use(Wi(t)),this.middlewareStack.use(Hi(t));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"S3Client",commandName:"GetObjectCommand",inputFilterSensitiveLog:Xt.filterSensitiveLog,outputFilterSensitiveLog:Zt.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"S3Client",commandName:"GetObjectCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l;return d(this,(function(d){switch(d.label){case 0:if(n=f(f(f(f(f(f(f(f(f(f({"Content-Type":""},Mi(e.SSECustomerKey)&&{"x-amz-server-side-encryption-customer-key":e.SSECustomerKey}),Mi(e.SSECustomerAlgorithm)&&{"x-amz-server-side-encryption-customer-algorithm":e.SSECustomerAlgorithm}),Mi(e.SSECustomerKeyMD5)&&{"x-amz-server-side-encryption-customer-key-MD5":e.SSECustomerKeyMD5}),Mi(e.RequestPayer)&&{"x-amz-request-payer":e.RequestPayer}),Mi(e.ExpectedBucketOwner)&&{"x-amz-expected-bucket-owner":e.ExpectedBucketOwner}),Mi(e.IfUnmodifiedSince)&&{"If-Unmodified-Since":Object(Zr.e)(e.IfUnmodifiedSince).toString()}),Mi(e.IfModifiedSince)&&{"If-Modified-Since":Object(Zr.e)(e.IfModifiedSince).toString()}),Mi(e.IfNoneMatch)&&{"If-None-Match":e.IfNoneMatch}),Mi(e.IfMatch)&&{"If-Match":e.IfMatch}),Mi(e.Range)&&{Range:e.Range}),r="/{Bucket}/{Key+}",void 0===e.Bucket)throw new Error("No value provided for input HTTP label: Bucket.");if((i=e.Bucket).length<=0)throw new Error("Empty value provided for input HTTP label: Bucket.");if(r=r.replace("{Bucket}",Object(Zr.f)(i)),void 0===e.Key)throw new Error("No value provided for input HTTP label: Key.");if((i=e.Key).length<=0)throw new Error("Empty value provided for input HTTP label: Key.");return r=r.replace("{Key+}",i.split("/").map((function(e){return Object(Zr.f)(e)})).join("/")),o=f(f(f(f(f(f(f(f({"x-id":"GetObject"},void 0!==e.ResponseContentEncoding&&{"response-content-encoding":e.ResponseContentEncoding}),void 0!==e.ResponseCacheControl&&{"response-cache-control":e.ResponseCacheControl}),void 0!==e.ResponseContentLanguage&&{"response-content-language":e.ResponseContentLanguage}),void 0!==e.ResponseContentDisposition&&{"response-content-disposition":e.ResponseContentDisposition}),void 0!==e.PartNumber&&{partNumber:e.PartNumber.toString()}),void 0!==e.VersionId&&{versionId:e.VersionId}),void 0!==e.ResponseExpires&&{"response-expires":(e.ResponseExpires.toISOString().split(".")[0]+"Z").toString()}),void 0!==e.ResponseContentType&&{"response-content-type":e.ResponseContentType}),[4,t.endpoint()];case 1:return s=d.sent(),a=s.hostname,u=s.protocol,c=void 0===u?"https":u,l=s.port,[2,new Xr.a({protocol:c,hostname:a,port:l,method:"GET",headers:n,path:r,query:o,body:void 0})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r;return d(this,(function(i){return 200!==e.statusCode&&e.statusCode>=300?[2,ai(e,t)]:(n={$metadata:Si(e),AcceptRanges:void 0,Body:void 0,CacheControl:void 0,ContentDisposition:void 0,ContentEncoding:void 0,ContentLanguage:void 0,ContentLength:void 0,ContentRange:void 0,ContentType:void 0,DeleteMarker:void 0,ETag:void 0,Expiration:void 0,Expires:void 0,LastModified:void 0,Metadata:void 0,MissingMeta:void 0,ObjectLockLegalHoldStatus:void 0,ObjectLockMode:void 0,ObjectLockRetainUntilDate:void 0,PartsCount:void 0,ReplicationStatus:void 0,RequestCharged:void 0,Restore:void 0,SSECustomerAlgorithm:void 0,SSECustomerKeyMD5:void 0,SSEKMSKeyId:void 0,ServerSideEncryption:void 0,StorageClass:void 0,TagCount:void 0,VersionId:void 0,WebsiteRedirectLocation:void 0},void 0!==e.headers["x-amz-object-lock-mode"]&&(n.ObjectLockMode=e.headers["x-amz-object-lock-mode"]),void 0!==e.headers["content-language"]&&(n.ContentLanguage=e.headers["content-language"]),void 0!==e.headers["content-disposition"]&&(n.ContentDisposition=e.headers["content-disposition"]),void 0!==e.headers["cache-control"]&&(n.CacheControl=e.headers["cache-control"]),void 0!==e.headers["content-type"]&&(n.ContentType=e.headers["content-type"]),void 0!==e.headers["content-range"]&&(n.ContentRange=e.headers["content-range"]),void 0!==e.headers["x-amz-server-side-encryption-aws-kms-key-id"]&&(n.SSEKMSKeyId=e.headers["x-amz-server-side-encryption-aws-kms-key-id"]),void 0!==e.headers["content-length"]&&(n.ContentLength=parseInt(e.headers["content-length"],10)),void 0!==e.headers["x-amz-object-lock-retain-until-date"]&&(n.ObjectLockRetainUntilDate=new Date(e.headers["x-amz-object-lock-retain-until-date"])),void 0!==e.headers["x-amz-object-lock-legal-hold"]&&(n.ObjectLockLegalHoldStatus=e.headers["x-amz-object-lock-legal-hold"]),void 0!==e.headers["x-amz-delete-marker"]&&(n.DeleteMarker="true"===e.headers["x-amz-delete-marker"]),void 0!==e.headers["x-amz-storage-class"]&&(n.StorageClass=e.headers["x-amz-storage-class"]),void 0!==e.headers["content-encoding"]&&(n.ContentEncoding=e.headers["content-encoding"]),void 0!==e.headers["x-amz-restore"]&&(n.Restore=e.headers["x-amz-restore"]),void 0!==e.headers["x-amz-website-redirect-location"]&&(n.WebsiteRedirectLocation=e.headers["x-amz-website-redirect-location"]),void 0!==e.headers["x-amz-server-side-encryption"]&&(n.ServerSideEncryption=e.headers["x-amz-server-side-encryption"]),void 0!==e.headers["x-amz-mp-parts-count"]&&(n.PartsCount=parseInt(e.headers["x-amz-mp-parts-count"],10)),void 0!==e.headers["x-amz-server-side-encryption-customer-algorithm"]&&(n.SSECustomerAlgorithm=e.headers["x-amz-server-side-encryption-customer-algorithm"]),void 0!==e.headers["accept-ranges"]&&(n.AcceptRanges=e.headers["accept-ranges"]),void 0!==e.headers["x-amz-version-id"]&&(n.VersionId=e.headers["x-amz-version-id"]),void 0!==e.headers.expires&&(n.Expires=new Date(e.headers.expires)),void 0!==e.headers["x-amz-expiration"]&&(n.Expiration=e.headers["x-amz-expiration"]),void 0!==e.headers["x-amz-missing-meta"]&&(n.MissingMeta=parseInt(e.headers["x-amz-missing-meta"],10)),void 0!==e.headers["x-amz-replication-status"]&&(n.ReplicationStatus=e.headers["x-amz-replication-status"]),void 0!==e.headers["x-amz-tagging-count"]&&(n.TagCount=parseInt(e.headers["x-amz-tagging-count"],10)),void 0!==e.headers["x-amz-server-side-encryption-customer-key-md5"]&&(n.SSECustomerKeyMD5=e.headers["x-amz-server-side-encryption-customer-key-md5"]),void 0!==e.headers["last-modified"]&&(n.LastModified=new Date(e.headers["last-modified"])),void 0!==e.headers.etag&&(n.ETag=e.headers.etag),void 0!==e.headers["x-amz-request-charged"]&&(n.RequestCharged=e.headers["x-amz-request-charged"]),Object.keys(e.headers).forEach((function(t){void 0===n.Metadata&&(n.Metadata={}),t.startsWith("x-amz-meta-")&&(n.Metadata[t.substring(11)]=e.headers[t])})),r=e.body,n.Body=r,[2,Promise.resolve(n)])}))}))}(e,t)},t}(Zr.b),Yi=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return c(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(Vi.a)(t,this.serialize,this.deserialize)),this.middlewareStack.use(Hi(t));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"S3Client",commandName:"DeleteObjectCommand",inputFilterSensitiveLog:Z.filterSensitiveLog,outputFilterSensitiveLog:J.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"S3Client",commandName:"DeleteObjectCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l;return d(this,(function(d){switch(d.label){case 0:if(n=f(f(f(f({"Content-Type":""},Mi(e.MFA)&&{"x-amz-mfa":e.MFA}),Mi(e.ExpectedBucketOwner)&&{"x-amz-expected-bucket-owner":e.ExpectedBucketOwner}),Mi(e.BypassGovernanceRetention)&&{"x-amz-bypass-governance-retention":e.BypassGovernanceRetention.toString()}),Mi(e.RequestPayer)&&{"x-amz-request-payer":e.RequestPayer}),r="/{Bucket}/{Key+}",void 0===e.Bucket)throw new Error("No value provided for input HTTP label: Bucket.");if((i=e.Bucket).length<=0)throw new Error("Empty value provided for input HTTP label: Bucket.");if(r=r.replace("{Bucket}",Object(Zr.f)(i)),void 0===e.Key)throw new Error("No value provided for input HTTP label: Key.");if((i=e.Key).length<=0)throw new Error("Empty value provided for input HTTP label: Key.");return r=r.replace("{Key+}",i.split("/").map((function(e){return Object(Zr.f)(e)})).join("/")),o=f({"x-id":"DeleteObject"},void 0!==e.VersionId&&{versionId:e.VersionId}),[4,t.endpoint()];case 1:return s=d.sent(),a=s.hostname,u=s.protocol,c=void 0===u?"https":u,l=s.port,[2,new Xr.a({protocol:c,hostname:a,port:l,method:"DELETE",headers:n,path:r,query:o,body:void 0})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n;return d(this,(function(r){switch(r.label){case 0:return 204!==e.statusCode&&e.statusCode>=300?[2,si(e,t)]:(n={$metadata:Si(e),DeleteMarker:void 0,RequestCharged:void 0,VersionId:void 0},void 0!==e.headers["x-amz-delete-marker"]&&(n.DeleteMarker="true"===e.headers["x-amz-delete-marker"]),void 0!==e.headers["x-amz-request-charged"]&&(n.RequestCharged=e.headers["x-amz-request-charged"]),void 0!==e.headers["x-amz-version-id"]&&(n.VersionId=e.headers["x-amz-version-id"]),[4,Ei(e.body,t)]);case 1:return r.sent(),[2,Promise.resolve(n)]}}))}))}(e,t)},t}(Zr.b),Ji=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return c(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(Vi.a)(t,this.serialize,this.deserialize)),this.middlewareStack.use(Hi(t));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"S3Client",commandName:"ListObjectsCommand",inputFilterSensitiveLog:Fn.filterSensitiveLog,outputFilterSensitiveLog:Bn.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"S3Client",commandName:"ListObjectsCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l;return d(this,(function(d){switch(d.label){case 0:if(n=f(f({"Content-Type":""},Mi(e.ExpectedBucketOwner)&&{"x-amz-expected-bucket-owner":e.ExpectedBucketOwner}),Mi(e.RequestPayer)&&{"x-amz-request-payer":e.RequestPayer}),r="/{Bucket}",void 0===e.Bucket)throw new Error("No value provided for input HTTP label: Bucket.");if((i=e.Bucket).length<=0)throw new Error("Empty value provided for input HTTP label: Bucket.");return r=r.replace("{Bucket}",Object(Zr.f)(i)),o=f(f(f(f(f({},void 0!==e.MaxKeys&&{"max-keys":e.MaxKeys.toString()}),void 0!==e.Marker&&{marker:e.Marker}),void 0!==e.Prefix&&{prefix:e.Prefix}),void 0!==e.Delimiter&&{delimiter:e.Delimiter}),void 0!==e.EncodingType&&{"encoding-type":e.EncodingType}),[4,t.endpoint()];case 1:return s=d.sent(),a=s.hostname,u=s.protocol,c=void 0===u?"https":u,l=s.port,[2,new Xr.a({protocol:c,hostname:a,port:l,method:"GET",headers:n,path:r,query:o,body:void 0})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r;return d(this,(function(i){switch(i.label){case 0:return 200!==e.statusCode&&e.statusCode>=300?[2,ui(e,t)]:(n={$metadata:Si(e),CommonPrefixes:void 0,Contents:void 0,Delimiter:void 0,EncodingType:void 0,IsTruncated:void 0,Marker:void 0,MaxKeys:void 0,Name:void 0,NextMarker:void 0,Prefix:void 0},[4,Ai(e.body,t)]);case 1:return""===(r=i.sent()).CommonPrefixes&&(n.CommonPrefixes=[]),void 0!==r.CommonPrefixes&&(n.CommonPrefixes=mi(Object(Zr.g)(r.CommonPrefixes),t)),""===r.Contents&&(n.Contents=[]),void 0!==r.Contents&&(n.Contents=yi(Object(Zr.g)(r.Contents),t)),void 0!==r.Delimiter&&(n.Delimiter=r.Delimiter),void 0!==r.EncodingType&&(n.EncodingType=r.EncodingType),void 0!==r.IsTruncated&&(n.IsTruncated="true"==r.IsTruncated),void 0!==r.Marker&&(n.Marker=r.Marker),void 0!==r.MaxKeys&&(n.MaxKeys=parseInt(r.MaxKeys)),void 0!==r.Name&&(n.Name=r.Name),void 0!==r.NextMarker&&(n.NextMarker=r.NextMarker),void 0!==r.Prefix&&(n.Prefix=r.Prefix),[2,Promise.resolve(n)]}}))}))}(e,t)},t}(Zr.b),Zi=n(153),Xi=n(38),Qi=n(110),eo=n(18);function to(e,t,n){return void 0===n&&(n=1048576),new Promise((function(r,i){var o=new FileReader;o.addEventListener("error",i),o.addEventListener("abort",i);var s=e.size,a=0;function u(){a>=s?r():o.readAsArrayBuffer(e.slice(a,Math.min(s,a+n)))}o.addEventListener("load",(function(e){var n=e.target.result;t(new Uint8Array(n)),a+=n.byteLength,u()})),u()}))}var no=n(24),ro=n(15),io=[1732584193,4023233417,2562383102,271733878],oo=function(){function e(){this.state=Uint32Array.from(io),this.buffer=new DataView(new ArrayBuffer(64)),this.bufferLength=0,this.bytesHashed=0,this.finished=!1}return e.prototype.update=function(e){if(!function(e){if("string"==typeof e)return 0===e.length;return 0===e.byteLength}(e)){if(this.finished)throw new Error("Attempted to update an already finished hash.");var t=function(e){if("string"==typeof e)return Object(ro.a)(e);if(ArrayBuffer.isView(e))return new Uint8Array(e.buffer,e.byteOffset,e.byteLength/Uint8Array.BYTES_PER_ELEMENT);return new Uint8Array(e)}(e),n=0,r=t.byteLength;for(this.bytesHashed+=r;r>0;)this.buffer.setUint8(this.bufferLength++,t[n++]),r--,64===this.bufferLength&&(this.hashBuffer(),this.bufferLength=0)}},e.prototype.digest=function(){return Object(Qr.__awaiter)(this,void 0,void 0,(function(){var e,t,n,r,i,o,s;return Object(Qr.__generator)(this,(function(a){if(!this.finished){if(t=(e=this).buffer,n=e.bufferLength,r=e.bytesHashed,i=8*r,t.setUint8(this.bufferLength++,128),n%64>=56){for(s=this.bufferLength;s<64;s++)t.setUint8(s,0);this.hashBuffer(),this.bufferLength=0}for(s=this.bufferLength;s<56;s++)t.setUint8(s,0);t.setUint32(56,i>>>0,!0),t.setUint32(60,Math.floor(i/4294967296),!0),this.hashBuffer(),this.finished=!0}for(o=new DataView(new ArrayBuffer(16)),s=0;s<4;s++)o.setUint32(4*s,this.state[s],!0);return[2,new Uint8Array(o.buffer,o.byteOffset,o.byteLength)]}))}))},e.prototype.hashBuffer=function(){var e=this.buffer,t=this.state,n=t[0],r=t[1],i=t[2],o=t[3];n=ao(n,r,i,o,e.getUint32(0,!0),7,3614090360),o=ao(o,n,r,i,e.getUint32(4,!0),12,3905402710),i=ao(i,o,n,r,e.getUint32(8,!0),17,606105819),r=ao(r,i,o,n,e.getUint32(12,!0),22,3250441966),n=ao(n,r,i,o,e.getUint32(16,!0),7,4118548399),o=ao(o,n,r,i,e.getUint32(20,!0),12,1200080426),i=ao(i,o,n,r,e.getUint32(24,!0),17,2821735955),r=ao(r,i,o,n,e.getUint32(28,!0),22,4249261313),n=ao(n,r,i,o,e.getUint32(32,!0),7,1770035416),o=ao(o,n,r,i,e.getUint32(36,!0),12,2336552879),i=ao(i,o,n,r,e.getUint32(40,!0),17,4294925233),r=ao(r,i,o,n,e.getUint32(44,!0),22,2304563134),n=ao(n,r,i,o,e.getUint32(48,!0),7,1804603682),o=ao(o,n,r,i,e.getUint32(52,!0),12,4254626195),i=ao(i,o,n,r,e.getUint32(56,!0),17,2792965006),n=uo(n,r=ao(r,i,o,n,e.getUint32(60,!0),22,1236535329),i,o,e.getUint32(4,!0),5,4129170786),o=uo(o,n,r,i,e.getUint32(24,!0),9,3225465664),i=uo(i,o,n,r,e.getUint32(44,!0),14,643717713),r=uo(r,i,o,n,e.getUint32(0,!0),20,3921069994),n=uo(n,r,i,o,e.getUint32(20,!0),5,3593408605),o=uo(o,n,r,i,e.getUint32(40,!0),9,38016083),i=uo(i,o,n,r,e.getUint32(60,!0),14,3634488961),r=uo(r,i,o,n,e.getUint32(16,!0),20,3889429448),n=uo(n,r,i,o,e.getUint32(36,!0),5,568446438),o=uo(o,n,r,i,e.getUint32(56,!0),9,3275163606),i=uo(i,o,n,r,e.getUint32(12,!0),14,4107603335),r=uo(r,i,o,n,e.getUint32(32,!0),20,1163531501),n=uo(n,r,i,o,e.getUint32(52,!0),5,2850285829),o=uo(o,n,r,i,e.getUint32(8,!0),9,4243563512),i=uo(i,o,n,r,e.getUint32(28,!0),14,1735328473),n=co(n,r=uo(r,i,o,n,e.getUint32(48,!0),20,2368359562),i,o,e.getUint32(20,!0),4,4294588738),o=co(o,n,r,i,e.getUint32(32,!0),11,2272392833),i=co(i,o,n,r,e.getUint32(44,!0),16,1839030562),r=co(r,i,o,n,e.getUint32(56,!0),23,4259657740),n=co(n,r,i,o,e.getUint32(4,!0),4,2763975236),o=co(o,n,r,i,e.getUint32(16,!0),11,1272893353),i=co(i,o,n,r,e.getUint32(28,!0),16,4139469664),r=co(r,i,o,n,e.getUint32(40,!0),23,3200236656),n=co(n,r,i,o,e.getUint32(52,!0),4,681279174),o=co(o,n,r,i,e.getUint32(0,!0),11,3936430074),i=co(i,o,n,r,e.getUint32(12,!0),16,3572445317),r=co(r,i,o,n,e.getUint32(24,!0),23,76029189),n=co(n,r,i,o,e.getUint32(36,!0),4,3654602809),o=co(o,n,r,i,e.getUint32(48,!0),11,3873151461),i=co(i,o,n,r,e.getUint32(60,!0),16,530742520),n=fo(n,r=co(r,i,o,n,e.getUint32(8,!0),23,3299628645),i,o,e.getUint32(0,!0),6,4096336452),o=fo(o,n,r,i,e.getUint32(28,!0),10,1126891415),i=fo(i,o,n,r,e.getUint32(56,!0),15,2878612391),r=fo(r,i,o,n,e.getUint32(20,!0),21,4237533241),n=fo(n,r,i,o,e.getUint32(48,!0),6,1700485571),o=fo(o,n,r,i,e.getUint32(12,!0),10,2399980690),i=fo(i,o,n,r,e.getUint32(40,!0),15,4293915773),r=fo(r,i,o,n,e.getUint32(4,!0),21,2240044497),n=fo(n,r,i,o,e.getUint32(32,!0),6,1873313359),o=fo(o,n,r,i,e.getUint32(60,!0),10,4264355552),i=fo(i,o,n,r,e.getUint32(24,!0),15,2734768916),r=fo(r,i,o,n,e.getUint32(52,!0),21,1309151649),n=fo(n,r,i,o,e.getUint32(16,!0),6,4149444226),o=fo(o,n,r,i,e.getUint32(44,!0),10,3174756917),i=fo(i,o,n,r,e.getUint32(8,!0),15,718787259),r=fo(r,i,o,n,e.getUint32(36,!0),21,3951481745),t[0]=n+t[0]&4294967295,t[1]=r+t[1]&4294967295,t[2]=i+t[2]&4294967295,t[3]=o+t[3]&4294967295},e}();function so(e,t,n,r,i,o){return((t=(t+e&4294967295)+(r+o&4294967295)&4294967295)<<i|t>>>32-i)+n&4294967295}function ao(e,t,n,r,i,o,s){return so(t&n|~t&r,e,t,i,o,s)}function uo(e,t,n,r,i,o,s){return so(t&r|n&~r,e,t,i,o,s)}function co(e,t,n,r,i,o,s){return so(t^n^r,e,t,i,o,s)}function fo(e,t,n,r,i,o,s){return so(n^(t|~r),e,t,i,o,s)}var lo=n(11),ho=n(39),po=n(17),vo=n(40),go=n(41),mo=new Set(["ap-east-1","ap-northeast-1","ap-northeast-2","ap-south-1","ap-southeast-1","ap-southeast-2","ca-central-1","eu-central-1","eu-north-1","eu-west-1","eu-west-2","eu-west-3","me-south-1","sa-east-1","us-east-1","us-east-2","us-west-1","us-west-2"]),bo=new Set(["cn-north-1","cn-northwest-1"]),yo=new Set(["us-iso-east-1"]),wo=new Set(["us-isob-east-1"]),_o=new Set(["us-gov-east-1","us-gov-west-1"]),So=f(f({},{apiVersion:"2006-03-01",disableHostPrefix:!1,logger:{},regionInfoProvider:function(e,t){var n=void 0;switch(e){case"ap-east-1":n={hostname:"s3.ap-east-1.amazonaws.com",partition:"aws"};break;case"ap-northeast-1":n={hostname:"s3.ap-northeast-1.amazonaws.com",partition:"aws"};break;case"ap-northeast-2":n={hostname:"s3.ap-northeast-2.amazonaws.com",partition:"aws"};break;case"ap-south-1":n={hostname:"s3.ap-south-1.amazonaws.com",partition:"aws"};break;case"ap-southeast-1":n={hostname:"s3.ap-southeast-1.amazonaws.com",partition:"aws"};break;case"ap-southeast-2":n={hostname:"s3.ap-southeast-2.amazonaws.com",partition:"aws"};break;case"ca-central-1":n={hostname:"s3.ca-central-1.amazonaws.com",partition:"aws"};break;case"cn-north-1":n={hostname:"s3.cn-north-1.amazonaws.com.cn",partition:"aws-cn"};break;case"cn-northwest-1":n={hostname:"s3.cn-northwest-1.amazonaws.com.cn",partition:"aws-cn"};break;case"eu-central-1":n={hostname:"s3.eu-central-1.amazonaws.com",partition:"aws"};break;case"eu-north-1":n={hostname:"s3.eu-north-1.amazonaws.com",partition:"aws"};break;case"eu-west-1":n={hostname:"s3.eu-west-1.amazonaws.com",partition:"aws"};break;case"eu-west-2":n={hostname:"s3.eu-west-2.amazonaws.com",partition:"aws"};break;case"eu-west-3":n={hostname:"s3.eu-west-3.amazonaws.com",partition:"aws"};break;case"fips-us-gov-west-1":n={hostname:"s3-fips-us-gov-west-1.amazonaws.com",partition:"aws-us-gov",signingRegion:"us-gov-west-1"};break;case"me-south-1":n={hostname:"s3.me-south-1.amazonaws.com",partition:"aws"};break;case"s3-external-1":n={hostname:"s3-external-1.amazonaws.com",partition:"aws",signingRegion:"us-east-1"};break;case"sa-east-1":n={hostname:"s3.sa-east-1.amazonaws.com",partition:"aws"};break;case"us-east-1":n={hostname:"s3.amazonaws.com",partition:"aws"};break;case"us-east-2":n={hostname:"s3.us-east-2.amazonaws.com",partition:"aws"};break;case"us-gov-east-1":n={hostname:"s3.us-gov-east-1.amazonaws.com",partition:"aws-us-gov"};break;case"us-gov-west-1":n={hostname:"s3.us-gov-west-1.amazonaws.com",partition:"aws-us-gov"};break;case"us-iso-east-1":n={hostname:"s3.us-iso-east-1.c2s.ic.gov",partition:"aws-iso"};break;case"us-isob-east-1":n={hostname:"s3.us-isob-east-1.sc2s.sgov.gov",partition:"aws-iso-b"};break;case"us-west-1":n={hostname:"s3.us-west-1.amazonaws.com",partition:"aws"};break;case"us-west-2":n={hostname:"s3.us-west-2.amazonaws.com",partition:"aws"};break;default:mo.has(e)&&(n={hostname:"s3.{region}.amazonaws.com".replace("{region}",e),partition:"aws"}),bo.has(e)&&(n={hostname:"s3.{region}.amazonaws.com.cn".replace("{region}",e),partition:"aws-cn"}),yo.has(e)&&(n={hostname:"s3.{region}.c2s.ic.gov".replace("{region}",e),partition:"aws-iso"}),wo.has(e)&&(n={hostname:"s3.{region}.sc2s.sgov.gov".replace("{region}",e),partition:"aws-iso-b"}),_o.has(e)&&(n={hostname:"s3.{region}.amazonaws.com".replace("{region}",e),partition:"aws-us-gov"}),void 0===n&&(n={hostname:"s3.{region}.amazonaws.com".replace("{region}",e),partition:"aws"})}return Promise.resolve(n)},signingEscapePath:!1,signingName:"s3",useArnRegion:!1}),{runtime:"browser",base64Decoder:po.a,base64Encoder:po.b,bodyLengthChecker:vo.a,credentialDefaultProvider:Object(no.a)("Credential is missing"),defaultUserAgent:Object(go.a)(Zi.name,Zi.version),eventStreamSerdeProvider:Qi.a,maxAttempts:lo.a,md5:oo,region:Object(no.a)("Region is missing"),requestHandler:new eo.a,sha256:Xi.Sha256,streamCollector:eo.b,streamHasher:function(e,t){return Object(Qr.__awaiter)(this,void 0,void 0,(function(){var n;return Object(Qr.__generator)(this,(function(r){switch(r.label){case 0:return n=new e,[4,to(t,(function(e){n.update(e)}))];case 1:return r.sent(),[2,n.digest()]}}))}))},urlParser:ho.a,utf8Decoder:ro.a,utf8Encoder:ro.b}),Eo=n(22),Mo=n(112),Ao=n(37);var Io={step:"build",tags:["SET_EXPECT_HEADER","EXPECT_HEADER"],name:"addExpectContinueMiddleware"},ko=function(e){return{applyToStack:function(t){t.add(function(e){var t=this;return function(n){return function(r){return Object(Qr.__awaiter)(t,void 0,void 0,(function(){var t;return Object(Qr.__generator)(this,(function(i){return t=r.request,Xr.a.isInstance(t)&&t.body&&"node"===e.runtime&&(t.headers=Object(Qr.__assign)(Object(Qr.__assign)({},t.headers),{Expect:"100-continue"})),[2,n(Object(Qr.__assign)(Object(Qr.__assign)({},r),{request:t}))]}))}))}}}(e),Io)}}},Oo=n(21),xo=n(43);var Co={step:"initialize",tags:["VALIDATE_BUCKET_NAME"],name:"validateBucketNameMiddleware"},To=function(e){return{applyToStack:function(e){e.add(function(){var e=this;return function(t){return function(n){return Object(Qr.__awaiter)(e,void 0,void 0,(function(){var e,r;return Object(Qr.__generator)(this,(function(i){if("string"==typeof(e=n.input.Bucket)&&!ki(e)&&e.indexOf("/")>=0)throw(r=new Error("Bucket name shouldn't contain '/', received '"+e+"'")).name="InvalidBucketName",r;return[2,t(Object(Qr.__assign)({},n))]}))}))}}}(),Co)}}},Po={step:"build",tags:["USE_REGIONAL_ENDPOINT","S3"],name:"useRegionalEndpointMiddleware"},No=function(e){return{applyToStack:function(t){t.add(function(e){return function(t){return function(n){return Object(Qr.__awaiter)(void 0,void 0,void 0,(function(){var r,i;return Object(Qr.__generator)(this,(function(o){switch(o.label){case 0:return r=n.request,!Xr.a.isInstance(r)||e.isCustomEndpoint?[2,t(Object(Qr.__assign)({},n))]:"s3.amazonaws.com"!==r.hostname?[3,1]:(r.hostname="s3.us-east-1.amazonaws.com",[3,3]);case 1:return i="aws-global",[4,e.region()];case 2:i===o.sent()&&(r.hostname="s3.amazonaws.com"),o.label=3;case 3:return[2,t(Object(Qr.__assign)({},n))]}}))}))}}}(e),Po)}}},Ro=n(25),Lo=n(23),jo=function(e){function t(t){var n,r,i,o,s,a,u,c,l,d,h,p=this,v=f(f({},So),t),g=Object(Eo.b)(v),m=Object(Eo.a)(g),b=Object(Ro.b)(m),y=Object(lo.c)(b),w=Object(Lo.b)(y),_=(r=(n=w).bucketEndpoint,i=void 0!==r&&r,o=n.forcePathStyle,s=void 0!==o&&o,a=n.useAccelerateEndpoint,u=void 0!==a&&a,c=n.useDualstackEndpoint,l=void 0!==c&&c,d=n.useArnRegion,h=void 0!==d&&d,Object(Qr.__assign)(Object(Qr.__assign)({},n),{bucketEndpoint:i,forcePathStyle:s,useAccelerateEndpoint:u,useDualstackEndpoint:l,useArnRegion:"function"==typeof h?h:function(){return Promise.resolve(h)}})),S=Object(Oo.b)(_),E=Object(Mo.a)(S);return(p=e.call(this,E)||this).config=E,p.middlewareStack.use(Object(Ro.a)(p.config)),p.middlewareStack.use(Object(lo.b)(p.config)),p.middlewareStack.use(Object(Lo.a)(p.config)),p.middlewareStack.use(Object(Ao.a)(p.config)),p.middlewareStack.use(To(p.config)),p.middlewareStack.use(No(p.config)),p.middlewareStack.use(ko(p.config)),p.middlewareStack.use(Object(Oo.a)(p.config)),p.middlewareStack.use(Object(xo.a)(p.config)),p}return c(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this)},t}(Zr.a),Do=n(74);function Uo(e){var t=e.port,n=e.query,r=e.protocol,i=e.path,o=e.hostname;r&&":"!==r.substr(-1)&&(r+=":"),t&&(o+=":"+t),i&&"/"!==i.charAt(0)&&(i="/"+i);var s=n?Object(Do.a)(n):"";return s&&"?"!==s[0]&&(s="?"+s),r+"//"+o+i+s}function Bo(e,t){return Object(Qr.__awaiter)(this,void 0,void 0,(function(){var n,r,i=this;return Object(Qr.__generator)(this,(function(o){switch(o.label){case 0:return n=function(e){return function(e){return Object(Qr.__awaiter)(i,void 0,void 0,(function(){return Object(Qr.__generator)(this,(function(t){return[2,{output:{request:e.request},response:void 0}]}))}))}},(r=e.middlewareStack.clone()).add(n,{step:"build",priority:"low"}),[4,t.resolveMiddleware(r,e.config,void 0)(t).then((function(e){return e.output.request}))];case 1:return[2,o.sent()]}}))}))}var Fo=n(111),zo=function(){function e(e){var t=Object(Qr.__assign)({service:e.signingName||e.service||"s3",uriEscapePath:e.uriEscapePath||!1},e);this.signer=new Fo.a(t)}return e.prototype.presign=function(e,t){void 0===t&&(t={});var n=t.unsignableHeaders,r=void 0===n?new Set:n,i=Object(Qr.__rest)(t,["unsignableHeaders"]);return Object(Qr.__awaiter)(this,void 0,void 0,(function(){return Object(Qr.__generator)(this,(function(t){return r.add("content-type"),e.headers["X-Amz-Content-Sha256"]="UNSIGNED-PAYLOAD",[2,this.signer.presign(e,Object(Qr.__assign)({expiresIn:900,unsignableHeaders:r},i))]}))}))},e}(),qo=n(64),Ko=n.n(qo),Ho=new r.a("axios-http-handler"),Vo=function(){function e(e,t){void 0===e&&(e={}),this.httpOptions=e,this.emitter=t}return e.prototype.destroy=function(){},e.prototype.handle=function(e,t){var n=this.httpOptions.requestTimeout,r=this.emitter,i=e.path;if(e.query){var o=Object(Do.a)(e.query);o&&(i+="?"+o)}var s=e.port,a=e.protocol+"//"+e.hostname+(s?":"+s:"")+i,u={};u.url=a,u.method=e.method,u.headers=e.headers,delete u.headers.host,e.body?u.data=e.body:u.headers["Content-Type"]&&(u.data=null),r&&(u.onUploadProgress=function(e){r.emit("sendProgress",e),Ho.debug(e)}),u.responseType="blob";var c=[Ko.a.request(u).then((function(e){return{response:new Xr.b({headers:e.headers,statusCode:e.status,body:e.data})}})).catch((function(e){throw Ho.error(e),e})),Go(n)];return Promise.race(c)},e}();function Go(e){return void 0===e&&(e=0),new Promise((function(t,n){e&&setTimeout((function(){var t=new Error("Request did not complete within "+e+" ms");t.name="TimeoutError",n(t)}),e)}))}var Wo,$o,Yo,Jo,Zo,Xo,Qo,es,ts,ns,rs,is,os,ss,as,us,cs,fs,ls,ds,hs=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return c(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(Vi.a)(t,this.serialize,this.deserialize)),this.middlewareStack.use(Wi(t)),this.middlewareStack.use(Hi(t));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"S3Client",commandName:"PutObjectCommand",inputFilterSensitiveLog:_r.filterSensitiveLog,outputFilterSensitiveLog:wr.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"S3Client",commandName:"PutObjectCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l,h,p;return d(this,(function(d){switch(d.label){case 0:if(n=f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f({"Content-Type":"application/octet-stream"},Mi(e.GrantFullControl)&&{"x-amz-grant-full-control":e.GrantFullControl}),Mi(e.ContentEncoding)&&{"Content-Encoding":e.ContentEncoding}),Mi(e.RequestPayer)&&{"x-amz-request-payer":e.RequestPayer}),Mi(e.GrantReadACP)&&{"x-amz-grant-read-acp":e.GrantReadACP}),Mi(e.SSECustomerKeyMD5)&&{"x-amz-server-side-encryption-customer-key-MD5":e.SSECustomerKeyMD5}),Mi(e.CacheControl)&&{"Cache-Control":e.CacheControl}),Mi(e.WebsiteRedirectLocation)&&{"x-amz-website-redirect-location":e.WebsiteRedirectLocation}),Mi(e.ObjectLockLegalHoldStatus)&&{"x-amz-object-lock-legal-hold":e.ObjectLockLegalHoldStatus}),Mi(e.GrantWriteACP)&&{"x-amz-grant-write-acp":e.GrantWriteACP}),Mi(e.ContentLength)&&{"Content-Length":e.ContentLength.toString()}),Mi(e.ObjectLockRetainUntilDate)&&{"x-amz-object-lock-retain-until-date":(e.ObjectLockRetainUntilDate.toISOString().split(".")[0]+"Z").toString()}),Mi(e.SSECustomerAlgorithm)&&{"x-amz-server-side-encryption-customer-algorithm":e.SSECustomerAlgorithm}),Mi(e.ContentDisposition)&&{"Content-Disposition":e.ContentDisposition}),Mi(e.SSECustomerKey)&&{"x-amz-server-side-encryption-customer-key":e.SSECustomerKey}),Mi(e.SSEKMSEncryptionContext)&&{"x-amz-server-side-encryption-context":e.SSEKMSEncryptionContext}),Mi(e.Tagging)&&{"x-amz-tagging":e.Tagging}),Mi(e.Expires)&&{Expires:Object(Zr.e)(e.Expires).toString()}),Mi(e.StorageClass)&&{"x-amz-storage-class":e.StorageClass}),Mi(e.ExpectedBucketOwner)&&{"x-amz-expected-bucket-owner":e.ExpectedBucketOwner}),Mi(e.ContentMD5)&&{"Content-MD5":e.ContentMD5}),Mi(e.ServerSideEncryption)&&{"x-amz-server-side-encryption":e.ServerSideEncryption}),Mi(e.ObjectLockMode)&&{"x-amz-object-lock-mode":e.ObjectLockMode}),Mi(e.SSEKMSKeyId)&&{"x-amz-server-side-encryption-aws-kms-key-id":e.SSEKMSKeyId}),Mi(e.ContentLanguage)&&{"Content-Language":e.ContentLanguage}),Mi(e.GrantRead)&&{"x-amz-grant-read":e.GrantRead}),Mi(e.ACL)&&{"x-amz-acl":e.ACL}),Mi(e.ContentType)&&{"Content-Type":e.ContentType}),void 0!==e.Metadata&&Object.keys(e.Metadata).reduce((function(t,n){return t["x-amz-meta-"+n]=e.Metadata[n],t}),{})),r="/{Bucket}/{Key+}",void 0===e.Bucket)throw new Error("No value provided for input HTTP label: Bucket.");if((i=e.Bucket).length<=0)throw new Error("Empty value provided for input HTTP label: Bucket.");if(r=r.replace("{Bucket}",Object(Zr.f)(i)),void 0===e.Key)throw new Error("No value provided for input HTTP label: Key.");if((i=e.Key).length<=0)throw new Error("Empty value provided for input HTTP label: Key.");return r=r.replace("{Key+}",i.split("/").map((function(e){return Object(Zr.f)(e)})).join("/")),o={"x-id":"PutObject"},void 0!==e.Body&&(a=e.Body,s=a),[4,t.endpoint()];case 1:return u=d.sent(),c=u.hostname,l=u.protocol,h=void 0===l?"https":l,p=u.port,[2,new Xr.a({protocol:h,hostname:c,port:p,method:"PUT",headers:n,path:r,query:o,body:s})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n;return d(this,(function(r){switch(r.label){case 0:return 200!==e.statusCode&&e.statusCode>=300?[2,fi(e,t)]:(n={$metadata:Si(e),ETag:void 0,Expiration:void 0,RequestCharged:void 0,SSECustomerAlgorithm:void 0,SSECustomerKeyMD5:void 0,SSEKMSEncryptionContext:void 0,SSEKMSKeyId:void 0,ServerSideEncryption:void 0,VersionId:void 0},void 0!==e.headers["x-amz-server-side-encryption-context"]&&(n.SSEKMSEncryptionContext=e.headers["x-amz-server-side-encryption-context"]),void 0!==e.headers["x-amz-expiration"]&&(n.Expiration=e.headers["x-amz-expiration"]),void 0!==e.headers["x-amz-server-side-encryption-customer-key-md5"]&&(n.SSECustomerKeyMD5=e.headers["x-amz-server-side-encryption-customer-key-md5"]),void 0!==e.headers.etag&&(n.ETag=e.headers.etag),void 0!==e.headers["x-amz-server-side-encryption-customer-algorithm"]&&(n.SSECustomerAlgorithm=e.headers["x-amz-server-side-encryption-customer-algorithm"]),void 0!==e.headers["x-amz-version-id"]&&(n.VersionId=e.headers["x-amz-version-id"]),void 0!==e.headers["x-amz-request-charged"]&&(n.RequestCharged=e.headers["x-amz-request-charged"]),void 0!==e.headers["x-amz-server-side-encryption-aws-kms-key-id"]&&(n.SSEKMSKeyId=e.headers["x-amz-server-side-encryption-aws-kms-key-id"]),void 0!==e.headers["x-amz-server-side-encryption"]&&(n.ServerSideEncryption=e.headers["x-amz-server-side-encryption"]),[4,Ei(e.body,t)]);case 1:return r.sent(),[2,Promise.resolve(n)]}}))}))}(e,t)},t}(Zr.b),ps=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return c(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(Vi.a)(t,this.serialize,this.deserialize)),this.middlewareStack.use(Wi(t)),this.middlewareStack.use(Hi(t));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"S3Client",commandName:"CreateMultipartUploadCommand",inputFilterSensitiveLog:D.filterSensitiveLog,outputFilterSensitiveLog:j.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"S3Client",commandName:"CreateMultipartUploadCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l;return d(this,(function(d){switch(d.label){case 0:if(n=f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f({"Content-Type":""},Mi(e.GrantFullControl)&&{"x-amz-grant-full-control":e.GrantFullControl}),Mi(e.SSECustomerKeyMD5)&&{"x-amz-server-side-encryption-customer-key-MD5":e.SSECustomerKeyMD5}),Mi(e.SSECustomerAlgorithm)&&{"x-amz-server-side-encryption-customer-algorithm":e.SSECustomerAlgorithm}),Mi(e.SSEKMSKeyId)&&{"x-amz-server-side-encryption-aws-kms-key-id":e.SSEKMSKeyId}),Mi(e.ObjectLockLegalHoldStatus)&&{"x-amz-object-lock-legal-hold":e.ObjectLockLegalHoldStatus}),Mi(e.RequestPayer)&&{"x-amz-request-payer":e.RequestPayer}),Mi(e.GrantRead)&&{"x-amz-grant-read":e.GrantRead}),Mi(e.GrantWriteACP)&&{"x-amz-grant-write-acp":e.GrantWriteACP}),Mi(e.WebsiteRedirectLocation)&&{"x-amz-website-redirect-location":e.WebsiteRedirectLocation}),Mi(e.ContentType)&&{"Content-Type":e.ContentType}),Mi(e.ContentLanguage)&&{"Content-Language":e.ContentLanguage}),Mi(e.CacheControl)&&{"Cache-Control":e.CacheControl}),Mi(e.GrantReadACP)&&{"x-amz-grant-read-acp":e.GrantReadACP}),Mi(e.Tagging)&&{"x-amz-tagging":e.Tagging}),Mi(e.SSEKMSEncryptionContext)&&{"x-amz-server-side-encryption-context":e.SSEKMSEncryptionContext}),Mi(e.ACL)&&{"x-amz-acl":e.ACL}),Mi(e.SSECustomerKey)&&{"x-amz-server-side-encryption-customer-key":e.SSECustomerKey}),Mi(e.ExpectedBucketOwner)&&{"x-amz-expected-bucket-owner":e.ExpectedBucketOwner}),Mi(e.Expires)&&{Expires:Object(Zr.e)(e.Expires).toString()}),Mi(e.ObjectLockRetainUntilDate)&&{"x-amz-object-lock-retain-until-date":(e.ObjectLockRetainUntilDate.toISOString().split(".")[0]+"Z").toString()}),Mi(e.ServerSideEncryption)&&{"x-amz-server-side-encryption":e.ServerSideEncryption}),Mi(e.ContentDisposition)&&{"Content-Disposition":e.ContentDisposition}),Mi(e.ObjectLockMode)&&{"x-amz-object-lock-mode":e.ObjectLockMode}),Mi(e.StorageClass)&&{"x-amz-storage-class":e.StorageClass}),Mi(e.ContentEncoding)&&{"Content-Encoding":e.ContentEncoding}),void 0!==e.Metadata&&Object.keys(e.Metadata).reduce((function(t,n){return t["x-amz-meta-"+n]=e.Metadata[n],t}),{})),r="/{Bucket}/{Key+}",void 0===e.Bucket)throw new Error("No value provided for input HTTP label: Bucket.");if((i=e.Bucket).length<=0)throw new Error("Empty value provided for input HTTP label: Bucket.");if(r=r.replace("{Bucket}",Object(Zr.f)(i)),void 0===e.Key)throw new Error("No value provided for input HTTP label: Key.");if((i=e.Key).length<=0)throw new Error("Empty value provided for input HTTP label: Key.");return r=r.replace("{Key+}",i.split("/").map((function(e){return Object(Zr.f)(e)})).join("/")),o={uploads:""},[4,t.endpoint()];case 1:return s=d.sent(),a=s.hostname,u=s.protocol,c=void 0===u?"https":u,l=s.port,[2,new Xr.a({protocol:c,hostname:a,port:l,method:"POST",headers:n,path:r,query:o,body:void 0})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r;return d(this,(function(i){switch(i.label){case 0:return 200!==e.statusCode&&e.statusCode>=300?[2,oi(e,t)]:(n={$metadata:Si(e),AbortDate:void 0,AbortRuleId:void 0,Bucket:void 0,Key:void 0,RequestCharged:void 0,SSECustomerAlgorithm:void 0,SSECustomerKeyMD5:void 0,SSEKMSEncryptionContext:void 0,SSEKMSKeyId:void 0,ServerSideEncryption:void 0,UploadId:void 0},void 0!==e.headers["x-amz-server-side-encryption-context"]&&(n.SSEKMSEncryptionContext=e.headers["x-amz-server-side-encryption-context"]),void 0!==e.headers["x-amz-server-side-encryption"]&&(n.ServerSideEncryption=e.headers["x-amz-server-side-encryption"]),void 0!==e.headers["x-amz-request-charged"]&&(n.RequestCharged=e.headers["x-amz-request-charged"]),void 0!==e.headers["x-amz-abort-date"]&&(n.AbortDate=new Date(e.headers["x-amz-abort-date"])),void 0!==e.headers["x-amz-server-side-encryption-customer-algorithm"]&&(n.SSECustomerAlgorithm=e.headers["x-amz-server-side-encryption-customer-algorithm"]),void 0!==e.headers["x-amz-server-side-encryption-aws-kms-key-id"]&&(n.SSEKMSKeyId=e.headers["x-amz-server-side-encryption-aws-kms-key-id"]),void 0!==e.headers["x-amz-abort-rule-id"]&&(n.AbortRuleId=e.headers["x-amz-abort-rule-id"]),void 0!==e.headers["x-amz-server-side-encryption-customer-key-md5"]&&(n.SSECustomerKeyMD5=e.headers["x-amz-server-side-encryption-customer-key-md5"]),[4,Ai(e.body,t)]);case 1:return void 0!==(r=i.sent()).Bucket&&(n.Bucket=r.Bucket),void 0!==r.Key&&(n.Key=r.Key),void 0!==r.UploadId&&(n.UploadId=r.UploadId),[2,Promise.resolve(n)]}}))}))}(e,t)},t}(Zr.b);!function(e){e.SELECT="SELECT"}(Wo||(Wo={})),($o||($o={})).filterSensitiveLog=function(e){return f(f({},e),e.OutputLocation&&{OutputLocation:Br.filterSensitiveLog(e.OutputLocation)})},(Yo||(Yo={})).filterSensitiveLog=function(e){return f(f({},e),e.RestoreRequest&&{RestoreRequest:$o.filterSensitiveLog(e.RestoreRequest)})},(Jo||(Jo={})).filterSensitiveLog=function(e){return f({},e)},(Zo||(Zo={})).filterSensitiveLog=function(e){return f({},e)},(Xo||(Xo={})).filterSensitiveLog=function(e){return f({},e)},(Qo||(Qo={})).filterSensitiveLog=function(e){return f({},e)},(es||(es={})).filterSensitiveLog=function(e){return f({},e)},(ts||(ts={})).filterSensitiveLog=function(e){return f({},e)},(ns||(ns={})).filterSensitiveLog=function(e){return f({},e)},function(e){e.visit=function(e,t){return void 0!==e.Cont?t.Cont(e.Cont):void 0!==e.Progress?t.Progress(e.Progress):void 0!==e.Stats?t.Stats(e.Stats):void 0!==e.End?t.End(e.End):void 0!==e.Records?t.Records(e.Records):t._(e.$unknown[0],e.$unknown[1])},e.filterSensitiveLog=function(e){var t;return void 0!==e.Cont?{Cont:Jo.filterSensitiveLog(e.Cont)}:void 0!==e.Progress?{Progress:Qo.filterSensitiveLog(e.Progress)}:void 0!==e.Stats?{Stats:ns.filterSensitiveLog(e.Stats)}:void 0!==e.End?{End:Zo.filterSensitiveLog(e.End)}:void 0!==e.Records?{Records:es.filterSensitiveLog(e.Records)}:void 0!==e.$unknown?((t={})[e.$unknown[0]]="UNKNOWN",t):void 0}}(rs||(rs={})),(is||(is={})).filterSensitiveLog=function(e){return f(f({},e),e.Payload&&{Payload:"STREAMING_CONTENT"})},(os||(os={})).filterSensitiveLog=function(e){return f({},e)},(ss||(ss={})).filterSensitiveLog=function(e){return f({},e)},(as||(as={})).filterSensitiveLog=function(e){return f(f({},e),e.SSECustomerKey&&{SSECustomerKey:Zr.d})},(us||(us={})).filterSensitiveLog=function(e){return f(f({},e),e.SSEKMSKeyId&&{SSEKMSKeyId:Zr.d})},(cs||(cs={})).filterSensitiveLog=function(e){return f(f({},e),e.SSECustomerKey&&{SSECustomerKey:Zr.d})},(fs||(fs={})).filterSensitiveLog=function(e){return f({},e)},(ls||(ls={})).filterSensitiveLog=function(e){return f(f({},e),e.SSEKMSKeyId&&{SSEKMSKeyId:Zr.d})},(ds||(ds={})).filterSensitiveLog=function(e){return f(f(f({},e),e.SSECustomerKey&&{SSECustomerKey:Zr.d}),e.CopySourceSSECustomerKey&&{CopySourceSSECustomerKey:Zr.d})};var vs=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return c(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(Vi.a)(t,this.serialize,this.deserialize)),this.middlewareStack.use(Wi(t)),this.middlewareStack.use(Hi(t));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"S3Client",commandName:"UploadPartCommand",inputFilterSensitiveLog:cs.filterSensitiveLog,outputFilterSensitiveLog:us.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"S3Client",commandName:"UploadPartCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l,h,p;return d(this,(function(d){switch(d.label){case 0:if(n=f(f(f(f(f(f(f({"Content-Type":"application/octet-stream"},Mi(e.ExpectedBucketOwner)&&{"x-amz-expected-bucket-owner":e.ExpectedBucketOwner}),Mi(e.RequestPayer)&&{"x-amz-request-payer":e.RequestPayer}),Mi(e.ContentLength)&&{"Content-Length":e.ContentLength.toString()}),Mi(e.SSECustomerKey)&&{"x-amz-server-side-encryption-customer-key":e.SSECustomerKey}),Mi(e.SSECustomerAlgorithm)&&{"x-amz-server-side-encryption-customer-algorithm":e.SSECustomerAlgorithm}),Mi(e.SSECustomerKeyMD5)&&{"x-amz-server-side-encryption-customer-key-MD5":e.SSECustomerKeyMD5}),Mi(e.ContentMD5)&&{"Content-MD5":e.ContentMD5}),r="/{Bucket}/{Key+}",void 0===e.Bucket)throw new Error("No value provided for input HTTP label: Bucket.");if((i=e.Bucket).length<=0)throw new Error("Empty value provided for input HTTP label: Bucket.");if(r=r.replace("{Bucket}",Object(Zr.f)(i)),void 0===e.Key)throw new Error("No value provided for input HTTP label: Key.");if((i=e.Key).length<=0)throw new Error("Empty value provided for input HTTP label: Key.");return r=r.replace("{Key+}",i.split("/").map((function(e){return Object(Zr.f)(e)})).join("/")),o=f(f({"x-id":"UploadPart"},void 0!==e.PartNumber&&{partNumber:e.PartNumber.toString()}),void 0!==e.UploadId&&{uploadId:e.UploadId}),void 0!==e.Body&&(a=e.Body,s=a),[4,t.endpoint()];case 1:return u=d.sent(),c=u.hostname,l=u.protocol,h=void 0===l?"https":l,p=u.port,[2,new Xr.a({protocol:h,hostname:c,port:p,method:"PUT",headers:n,path:r,query:o,body:s})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n;return d(this,(function(r){switch(r.label){case 0:return 200!==e.statusCode&&e.statusCode>=300?[2,li(e,t)]:(n={$metadata:Si(e),ETag:void 0,RequestCharged:void 0,SSECustomerAlgorithm:void 0,SSECustomerKeyMD5:void 0,SSEKMSKeyId:void 0,ServerSideEncryption:void 0},void 0!==e.headers["x-amz-server-side-encryption-customer-key-md5"]&&(n.SSECustomerKeyMD5=e.headers["x-amz-server-side-encryption-customer-key-md5"]),void 0!==e.headers["x-amz-server-side-encryption"]&&(n.ServerSideEncryption=e.headers["x-amz-server-side-encryption"]),void 0!==e.headers["x-amz-server-side-encryption-aws-kms-key-id"]&&(n.SSEKMSKeyId=e.headers["x-amz-server-side-encryption-aws-kms-key-id"]),void 0!==e.headers["x-amz-server-side-encryption-customer-algorithm"]&&(n.SSECustomerAlgorithm=e.headers["x-amz-server-side-encryption-customer-algorithm"]),void 0!==e.headers["x-amz-request-charged"]&&(n.RequestCharged=e.headers["x-amz-request-charged"]),void 0!==e.headers.etag&&(n.ETag=e.headers.etag),[4,Ei(e.body,t)]);case 1:return r.sent(),[2,Promise.resolve(n)]}}))}))}(e,t)},t}(Zr.b),gs=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return c(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(Vi.a)(t,this.serialize,this.deserialize)),this.middlewareStack.use(Hi(t));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"S3Client",commandName:"CompleteMultipartUploadCommand",inputFilterSensitiveLog:I.filterSensitiveLog,outputFilterSensitiveLog:E.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"S3Client",commandName:"CompleteMultipartUploadCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l,h,p;return d(this,(function(d){switch(d.label){case 0:if(n=f(f({"Content-Type":"application/xml"},Mi(e.ExpectedBucketOwner)&&{"x-amz-expected-bucket-owner":e.ExpectedBucketOwner}),Mi(e.RequestPayer)&&{"x-amz-request-payer":e.RequestPayer}),r="/{Bucket}/{Key+}",void 0===e.Key)throw new Error("No value provided for input HTTP label: Key.");if((i=e.Key).length<=0)throw new Error("Empty value provided for input HTTP label: Key.");if(r=r.replace("{Key+}",i.split("/").map((function(e){return Object(Zr.f)(e)})).join("/")),void 0===e.Bucket)throw new Error("No value provided for input HTTP label: Bucket.");if((i=e.Bucket).length<=0)throw new Error("Empty value provided for input HTTP label: Bucket.");return r=r.replace("{Bucket}",Object(Zr.f)(i)),o=f({},void 0!==e.UploadId&&{uploadId:e.UploadId}),void 0!==e.MultipartUpload&&(a=vi(e.MultipartUpload,t),s='<?xml version="1.0" encoding="UTF-8"?>',a.addAttribute("xmlns","http://s3.amazonaws.com/doc/2006-03-01/"),s+=a.toString()),[4,t.endpoint()];case 1:return u=d.sent(),c=u.hostname,l=u.protocol,h=void 0===l?"https":l,p=u.port,[2,new Xr.a({protocol:h,hostname:c,port:p,method:"POST",headers:n,path:r,query:o,body:s})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r;return d(this,(function(i){switch(i.label){case 0:return 200!==e.statusCode&&e.statusCode>=300?[2,ii(e,t)]:(n={$metadata:Si(e),Bucket:void 0,ETag:void 0,Expiration:void 0,Key:void 0,Location:void 0,RequestCharged:void 0,SSEKMSKeyId:void 0,ServerSideEncryption:void 0,VersionId:void 0},void 0!==e.headers["x-amz-expiration"]&&(n.Expiration=e.headers["x-amz-expiration"]),void 0!==e.headers["x-amz-server-side-encryption"]&&(n.ServerSideEncryption=e.headers["x-amz-server-side-encryption"]),void 0!==e.headers["x-amz-server-side-encryption-aws-kms-key-id"]&&(n.SSEKMSKeyId=e.headers["x-amz-server-side-encryption-aws-kms-key-id"]),void 0!==e.headers["x-amz-version-id"]&&(n.VersionId=e.headers["x-amz-version-id"]),void 0!==e.headers["x-amz-request-charged"]&&(n.RequestCharged=e.headers["x-amz-request-charged"]),[4,Ai(e.body,t)]);case 1:return void 0!==(r=i.sent()).Bucket&&(n.Bucket=r.Bucket),void 0!==r.ETag&&(n.ETag=r.ETag),void 0!==r.Key&&(n.Key=r.Key),void 0!==r.Location&&(n.Location=r.Location),[2,Promise.resolve(n)]}}))}))}(e,t)},t}(Zr.b),ms=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return c(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(Vi.a)(t,this.serialize,this.deserialize)),this.middlewareStack.use(Hi(t));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"S3Client",commandName:"AbortMultipartUploadCommand",inputFilterSensitiveLog:v.filterSensitiveLog,outputFilterSensitiveLog:p.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"S3Client",commandName:"AbortMultipartUploadCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l;return d(this,(function(d){switch(d.label){case 0:if(n=f(f({"Content-Type":""},Mi(e.RequestPayer)&&{"x-amz-request-payer":e.RequestPayer}),Mi(e.ExpectedBucketOwner)&&{"x-amz-expected-bucket-owner":e.ExpectedBucketOwner}),r="/{Bucket}/{Key+}",void 0===e.Key)throw new Error("No value provided for input HTTP label: Key.");if((i=e.Key).length<=0)throw new Error("Empty value provided for input HTTP label: Key.");if(r=r.replace("{Key+}",i.split("/").map((function(e){return Object(Zr.f)(e)})).join("/")),void 0===e.Bucket)throw new Error("No value provided for input HTTP label: Bucket.");if((i=e.Bucket).length<=0)throw new Error("Empty value provided for input HTTP label: Bucket.");return r=r.replace("{Bucket}",Object(Zr.f)(i)),o=f({"x-id":"AbortMultipartUpload"},void 0!==e.UploadId&&{uploadId:e.UploadId}),[4,t.endpoint()];case 1:return s=d.sent(),a=s.hostname,u=s.protocol,c=void 0===u?"https":u,l=s.port,[2,new Xr.a({protocol:c,hostname:a,port:l,method:"DELETE",headers:n,path:r,query:o,body:void 0})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n;return d(this,(function(r){switch(r.label){case 0:return 204!==e.statusCode&&e.statusCode>=300?[2,ri(e,t)]:(n={$metadata:Si(e),RequestCharged:void 0},void 0!==e.headers["x-amz-request-charged"]&&(n.RequestCharged=e.headers["x-amz-request-charged"]),[4,Ei(e.body,t)]);case 1:return r.sent(),[2,Promise.resolve(n)]}}))}))}(e,t)},t}(Zr.b),bs=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return c(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(Vi.a)(t,this.serialize,this.deserialize)),this.middlewareStack.use(Hi(t));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"S3Client",commandName:"ListPartsCommand",inputFilterSensitiveLog:Yn.filterSensitiveLog,outputFilterSensitiveLog:$n.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"S3Client",commandName:"ListPartsCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l;return d(this,(function(d){switch(d.label){case 0:if(n=f(f({"Content-Type":""},Mi(e.RequestPayer)&&{"x-amz-request-payer":e.RequestPayer}),Mi(e.ExpectedBucketOwner)&&{"x-amz-expected-bucket-owner":e.ExpectedBucketOwner}),r="/{Bucket}/{Key+}",void 0===e.Bucket)throw new Error("No value provided for input HTTP label: Bucket.");if((i=e.Bucket).length<=0)throw new Error("Empty value provided for input HTTP label: Bucket.");if(r=r.replace("{Bucket}",Object(Zr.f)(i)),void 0===e.Key)throw new Error("No value provided for input HTTP label: Key.");if((i=e.Key).length<=0)throw new Error("Empty value provided for input HTTP label: Key.");return r=r.replace("{Key+}",i.split("/").map((function(e){return Object(Zr.f)(e)})).join("/")),o=f(f(f({"x-id":"ListParts"},void 0!==e.UploadId&&{uploadId:e.UploadId}),void 0!==e.MaxParts&&{"max-parts":e.MaxParts.toString()}),void 0!==e.PartNumberMarker&&{"part-number-marker":e.PartNumberMarker.toString()}),[4,t.endpoint()];case 1:return s=d.sent(),a=s.hostname,u=s.protocol,c=void 0===u?"https":u,l=s.port,[2,new Xr.a({protocol:c,hostname:a,port:l,method:"GET",headers:n,path:r,query:o,body:void 0})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r;return d(this,(function(i){switch(i.label){case 0:return 200!==e.statusCode&&e.statusCode>=300?[2,ci(e,t)]:(n={$metadata:Si(e),AbortDate:void 0,AbortRuleId:void 0,Bucket:void 0,Initiator:void 0,IsTruncated:void 0,Key:void 0,MaxParts:void 0,NextPartNumberMarker:void 0,Owner:void 0,PartNumberMarker:void 0,Parts:void 0,RequestCharged:void 0,StorageClass:void 0,UploadId:void 0},void 0!==e.headers["x-amz-abort-rule-id"]&&(n.AbortRuleId=e.headers["x-amz-abort-rule-id"]),void 0!==e.headers["x-amz-request-charged"]&&(n.RequestCharged=e.headers["x-amz-request-charged"]),void 0!==e.headers["x-amz-abort-date"]&&(n.AbortDate=new Date(e.headers["x-amz-abort-date"])),[4,Ai(e.body,t)]);case 1:return void 0!==(r=i.sent()).Bucket&&(n.Bucket=r.Bucket),void 0!==r.Initiator&&(n.Initiator=bi(r.Initiator,t)),void 0!==r.IsTruncated&&(n.IsTruncated="true"==r.IsTruncated),void 0!==r.Key&&(n.Key=r.Key),void 0!==r.MaxParts&&(n.MaxParts=parseInt(r.MaxParts)),void 0!==r.NextPartNumberMarker&&(n.NextPartNumberMarker=parseInt(r.NextPartNumberMarker)),void 0!==r.Owner&&(n.Owner=wi(r.Owner,t)),void 0!==r.PartNumberMarker&&(n.PartNumberMarker=parseInt(r.PartNumberMarker)),""===r.Part&&(n.Parts=[]),void 0!==r.Part&&(n.Parts=_i(Object(Zr.g)(r.Part),t)),void 0!==r.StorageClass&&(n.StorageClass=r.StorageClass),void 0!==r.UploadId&&(n.UploadId=r.UploadId),[2,Promise.resolve(n)]}}))}))}(e,t)},t}(Zr.b),ys=n(49),ws=n(106),_s=n(16),Ss=function(e){var t,n=Object(_s.parse)(e),r=n.hostname,i=void 0===r?"localhost":r,o=n.pathname,s=void 0===o?"/":o,a=n.port,u=n.protocol,c=void 0===u?"https:":u,f=n.search;return f&&(t=Object(ws.a)(f)),{hostname:i,port:a?parseInt(a):void 0,protocol:c,path:s,query:t}};function Es(e){return(Es="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var Ms=function(){return(Ms=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},As=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},Is=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},ks=function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},Os=new r.a("AWSS3ProviderManagedUpload"),xs=function(){function e(e,t,n){this.minPartSize=5242880,this.queueSize=4,this.body=null,this.params=null,this.opts=null,this.multiPartMap=[],this.cancel=!1,this.bytesUploaded=0,this.totalBytesToUpload=0,this.emitter=null,this.params=e,this.opts=t,this.emitter=n}return e.prototype.upload=function(){return As(this,void 0,void 0,(function(){var e,t,n,r,i,o;return Is(this,(function(s){switch(s.label){case 0:return e=this,[4,this.validateAndSanitizeBody(this.params.Body)];case 1:return e.body=s.sent(),this.totalBytesToUpload=this.byteLength(this.body),this.totalBytesToUpload<=this.minPartSize?(this.params.Body=this.body,t=new hs(this.params),[4,this._createNewS3Client(this.opts,this.emitter)]):[3,3];case 2:return[2,s.sent().send(t)];case 3:return[4,this.createMultiPartUpload()];case 4:n=s.sent(),r=Math.ceil(this.totalBytesToUpload/this.minPartSize),i=0,s.label=5;case 5:return i<r?[4,this.checkIfUploadCancelled(n)]:[3,10];case 6:return s.sent(),o=this.createParts(i),[4,this.uploadParts(n,o)];case 7:return s.sent(),[4,this.checkIfUploadCancelled(n)];case 8:s.sent(),s.label=9;case 9:return i+=this.queueSize,[3,5];case 10:return[4,this.finishMultiPartUpload(n)];case 11:return[2,s.sent()]}}))}))},e.prototype.createParts=function(e){for(var t=[],n=e,r=e*this.minPartSize;r<this.totalBytesToUpload&&t.length<this.queueSize;){var i=Math.min(r+this.minPartSize,this.totalBytesToUpload);t.push({bodyPart:this.body.slice(r,i),partNumber:++n,emitter:new ys.EventEmitter,_lastUploadedBytes:0}),r+=this.minPartSize}return t},e.prototype.createMultiPartUpload=function(){return As(this,void 0,void 0,(function(){var e,t;return Is(this,(function(n){switch(n.label){case 0:return e=new ps(this.params),[4,this._createNewS3Client(this.opts)];case 1:return[4,n.sent().send(e)];case 2:return t=n.sent(),Os.debug(t.UploadId),[2,t.UploadId]}}))}))},e.prototype.uploadParts=function(e,t){return As(this,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,l,d,h,p;return Is(this,(function(v){switch(v.label){case 0:n=[],v.label=1;case 1:v.trys.push([1,6,7,8]),r=ks(t),i=r.next(),v.label=2;case 2:return i.done?[3,5]:(o=i.value,this.setupEventListener(o),s={PartNumber:o.partNumber,Body:o.bodyPart,UploadId:e,Key:this.params.Key,Bucket:this.params.Bucket},a=new vs(s),[4,this._createNewS3Client(this.opts,o.emitter)]);case 3:u=v.sent(),n.push(u.send(a)),v.label=4;case 4:return i=r.next(),[3,2];case 5:return[3,8];case 6:return c=v.sent(),h={error:c},[3,8];case 7:try{i&&!i.done&&(p=r.return)&&p.call(r)}finally{if(h)throw h.error}return[7];case 8:return v.trys.push([8,10,,11]),[4,Promise.all(n)];case 9:for(f=v.sent(),l=0;l<f.length;l++)this.multiPartMap.push({PartNumber:t[l].partNumber,ETag:f[l].ETag});return[3,11];case 10:return d=v.sent(),Os.error("error happened while uploading a part. Cancelling the multipart upload",d),this.cancelUpload(),[2];case 11:return[2]}}))}))},e.prototype.finishMultiPartUpload=function(e){return As(this,void 0,void 0,(function(){var t,n,r,i;return Is(this,(function(o){switch(o.label){case 0:return t={Bucket:this.params.Bucket,Key:this.params.Key,UploadId:e,MultipartUpload:{Parts:this.multiPartMap}},n=new gs(t),[4,this._createNewS3Client(this.opts)];case 1:r=o.sent(),o.label=2;case 2:return o.trys.push([2,4,,5]),[4,r.send(n)];case 3:return[2,o.sent().Key];case 4:return i=o.sent(),Os.error("error happened while finishing the upload. Cancelling the multipart upload",i),this.cancelUpload(),[2];case 5:return[2]}}))}))},e.prototype.checkIfUploadCancelled=function(e){return As(this,void 0,void 0,(function(){var t,n;return Is(this,(function(r){switch(r.label){case 0:if(!this.cancel)return[3,5];t="Upload was cancelled.",r.label=1;case 1:return r.trys.push([1,3,,4]),[4,this.cleanup(e)];case 2:return r.sent(),[3,4];case 3:return n=r.sent(),t+=n.errorMessage,[3,4];case 4:throw new Error(t);case 5:return[2]}}))}))},e.prototype.cancelUpload=function(){this.cancel=!0},e.prototype.cleanup=function(e){return As(this,void 0,void 0,(function(){var t,n,r;return Is(this,(function(i){switch(i.label){case 0:return this.body=null,this.multiPartMap=[],this.bytesUploaded=0,this.totalBytesToUpload=0,t={Bucket:this.params.Bucket,Key:this.params.Key,UploadId:e},[4,this._createNewS3Client(this.opts)];case 1:return[4,(n=i.sent()).send(new ms(t))];case 2:return i.sent(),[4,n.send(new bs(t))];case 3:if((r=i.sent())&&r.Parts&&r.Parts.length>0)throw new Error("Multi Part upload clean up failed");return[2]}}))}))},e.prototype.setupEventListener=function(e){var t=this;e.emitter.on("sendProgress",(function(n){t.progressChanged(e.partNumber,n.loaded-e._lastUploadedBytes),e._lastUploadedBytes=n.loaded}))},e.prototype.progressChanged=function(e,t){this.bytesUploaded+=t,this.emitter.emit("sendProgress",{loaded:this.bytesUploaded,total:this.totalBytesToUpload,part:e,key:this.params.Key})},e.prototype.byteLength=function(e){if(null==e)return 0;if("number"==typeof e.byteLength)return e.byteLength;if("number"==typeof e.length)return e.length;if("number"==typeof e.size)return e.size;if("string"!=typeof e.path)throw new Error("Cannot determine length of "+e)},e.prototype.validateAndSanitizeBody=function(e){return As(this,void 0,void 0,(function(){return Is(this,(function(t){switch(t.label){case 0:return this.isGenericObject(e)?[2,JSON.stringify(e)]:[3,1];case 1:return this.isBlob(e)?a.a.isReactNative?[4,Object(eo.b)(e)]:[3,3]:[3,4];case 2:return[2,t.sent()];case 3:case 4:return[2,e]}}))}))},e.prototype.isBlob=function(e){return"undefined"!=typeof Blob&&e instanceof Blob},e.prototype.isGenericObject=function(e){if(null!==e&&"object"===Es(e))try{return!(this.byteLength(e)>=0)}catch(e){return!0}return!1},e.prototype._createNewS3Client=function(e,t){return As(this,void 0,void 0,(function(){var n,r,i,o,s;return Is(this,(function(u){switch(u.label){case 0:return[4,this._getCredentials()];case 1:return n=u.sent(),r=e.region,i=e.dangerouslyConnectToHttpEndpointForTesting,o={},i&&(o={endpoint:"http://localhost:20005",tls:!1,bucketEndpoint:!1,forcePathStyle:!0}),(s=new jo(Ms(Ms({region:r,credentials:n},o),{requestHandler:new Vo({},t),customUserAgent:Object(a.b)(),urlParser:Ss}))).middlewareStack.remove("contentLengthMiddleware"),[2,s]}}))}))},e.prototype._getCredentials=function(){return s.a.get().then((function(e){if(!e)return!1;var t=s.a.shear(e);return Os.debug("set credentials for storage",t),t})).catch((function(e){return Os.warn("ensure credentials error",e),!1}))},e}();function Cs(e){return(Cs="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var Ts=function(){return(Ts=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},Ps=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},Ns=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},Rs=new r.a("AWSS3Provider"),Ls="undefined"!=typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("amplify_default"):"@@amplify_default",js=function(e,t,n,r,i){if(e){var s={attrs:n};r&&(s.metrics=r),o.a.dispatch("storage",{event:t,data:s,message:i},"Storage",Ls)}},Ds=function(){function e(e){this._config=e||{},Rs.debug("Storage Options",this._config)}return e.prototype.getCategory=function(){return e.CATEGORY},e.prototype.getProviderName=function(){return e.PROVIDER_NAME},e.prototype.configure=function(e){if(Rs.debug("configure Storage",e),!e)return this._config;var t=i.a.parseMobilehubConfig(e);return this._config=Object.assign({},this._config,t.Storage),this._config.bucket||Rs.debug("Do not have bucket yet"),this._config},e.prototype.get=function(e,t){return Ps(this,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,l,d,h,p,v,g,m,b,y,w,_,S,E;return Ns(this,(function(M){switch(M.label){case 0:return[4,this._ensureCredentials()];case 1:if(!M.sent())return[2,Promise.reject("No credentials")];if(n=Object.assign({},this._config,t),r=n.bucket,i=n.download,o=n.cacheControl,s=n.contentDisposition,a=n.contentEncoding,u=n.contentLanguage,c=n.contentType,f=n.expires,l=n.track,d=this._prefix(n),h=d+e,p=this._createNewS3Client(n),Rs.debug("get "+e+" from "+h),v={Bucket:r,Key:h},o&&(v.ResponseCacheControl=o),s&&(v.ResponseContentDisposition=s),a&&(v.ResponseContentEncoding=a),u&&(v.ResponseContentLanguage=u),c&&(v.ResponseContentType=c),!0!==i)return[3,5];g=new $i(v),M.label=2;case 2:return M.trys.push([2,4,,5]),[4,p.send(g)];case 3:return m=M.sent(),js(l,"download",{method:"get",result:"success"},{fileSize:Number(m.Body.size||m.Body.length)},"Download success for "+e),[2,m];case 4:throw b=M.sent(),js(l,"download",{method:"get",result:"failed"},null,"Download failed with "+b.message),b;case 5:v.Expires=f||900,M.label=6;case 6:return M.trys.push([6,9,,10]),y=new zo(Ts({},p.config)),[4,Bo(p,new $i(v))];case 7:return w=M.sent(),S=Uo,[4,y.presign(w,{expiresIn:v.Expires})];case 8:return _=S.apply(void 0,[M.sent()]),js(l,"getSignedUrl",{method:"get",result:"success"},null,"Signed URL: "+_),[2,_];case 9:throw E=M.sent(),Rs.warn("get signed url error",E),js(l,"getSignedUrl",{method:"get",result:"failed"},null,"Could not get a signed URL for "+e),E;case 10:return[2]}}))}))},e.prototype.put=function(e,t,n){return Ps(this,void 0,void 0,(function(){var r,i,o,s,a,u,c,f,l,d,h,p,v,g,m,b,y,w,_,S,E,M,A,I;return Ns(this,(function(k){switch(k.label){case 0:return[4,this._ensureCredentials()];case 1:if(!k.sent())return[2,Promise.reject("No credentials")];r=Object.assign({},this._config,n),i=r.bucket,o=r.track,s=r.progressCallback,a=r.contentType,u=r.contentDisposition,c=r.cacheControl,f=r.expires,l=r.metadata,d=r.tagging,h=r.acl,p=r.serverSideEncryption,v=r.SSECustomerAlgorithm,g=r.SSECustomerKey,m=r.SSECustomerKeyMD5,b=r.SSEKMSKeyId,y=a||"binary/octet-stream",w=this._prefix(r),_=w+e,Rs.debug("put "+e+" to "+_),S={Bucket:i,Key:_,Body:t,ContentType:y},c&&(S.CacheControl=c),u&&(S.ContentDisposition=u),f&&(S.Expires=f),l&&(S.Metadata=l),d&&(S.Tagging=d),p&&(S.ServerSideEncryption=p,v&&(S.SSECustomerAlgorithm=v),g&&(S.SSECustomerKey=g),m&&(S.SSECustomerKeyMD5=m),b&&(S.SSEKMSKeyId=b)),E=new ys.EventEmitter,M=new xs(S,r,E),h&&(S.ACL=h),k.label=2;case 2:return k.trys.push([2,4,,5]),E.on("sendProgress",(function(e){s&&("function"==typeof s?s(e):Rs.warn("progressCallback should be a function, not a "+Cs(s)))})),[4,M.upload()];case 3:return A=k.sent(),Rs.debug("upload result",A),js(o,"upload",{method:"put",result:"success"},null,"Upload success for "+e),[2,{key:e}];case 4:throw I=k.sent(),Rs.warn("error uploading",I),js(o,"upload",{method:"put",result:"failed"},null,"Error uploading "+e),I;case 5:return[2]}}))}))},e.prototype.remove=function(e,t){return Ps(this,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f;return Ns(this,(function(l){switch(l.label){case 0:return[4,this._ensureCredentials()];case 1:if(!l.sent())return[2,Promise.reject("No credentials")];n=Object.assign({},this._config,t),r=n.bucket,i=n.track,o=this._prefix(n),s=o+e,a=this._createNewS3Client(n),Rs.debug("remove "+e+" from "+s),u=new Yi({Bucket:r,Key:s}),l.label=2;case 2:return l.trys.push([2,4,,5]),[4,a.send(u)];case 3:return c=l.sent(),js(i,"delete",{method:"remove",result:"success"},null,"Deleted "+e+" successfully"),[2,c];case 4:throw f=l.sent(),js(i,"delete",{method:"remove",result:"failed"},null,"Deletion of "+e+" failed with "+f),f;case 5:return[2]}}))}))},e.prototype.list=function(e,t){return Ps(this,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,l,d;return Ns(this,(function(h){switch(h.label){case 0:return[4,this._ensureCredentials()];case 1:if(!h.sent())return[2,Promise.reject("No credentials")];n=Object.assign({},this._config,t),r=n.bucket,i=n.track,o=n.maxKeys,s=this._prefix(n),a=s+e,u=this._createNewS3Client(n),Rs.debug("list "+e+" from "+a),c=new Ji({Bucket:r,Prefix:a,MaxKeys:o}),h.label=2;case 2:return h.trys.push([2,4,,5]),[4,u.send(c)];case 3:return f=h.sent(),l=[],f&&f.Contents&&(l=f.Contents.map((function(e){return{key:e.Key.substr(s.length),eTag:e.ETag,lastModified:e.LastModified,size:e.Size}}))),js(i,"list",{method:"list",result:"success"},null,l.length+" items returned from list operation"),Rs.debug("list",l),[2,l];case 4:throw d=h.sent(),Rs.warn("list error",d),js(i,"list",{method:"list",result:"failed"},null,"Listing items failed: "+d.message),d;case 5:return[2]}}))}))},e.prototype._ensureCredentials=function(){var e=this;return s.a.get().then((function(t){if(!t)return!1;var n=s.a.shear(t);return Rs.debug("set credentials for storage",n),e._config.credentials=n,!0})).catch((function(e){return Rs.warn("ensure credentials error",e),!1}))},e.prototype._prefix=function(e){var t=e.credentials,n=e.level,r=e.customPrefix||{},i=e.identityId||t.identityId,o=(void 0!==r.private?r.private:"private/")+i+"/",s=(void 0!==r.protected?r.protected:"protected/")+i+"/",a=void 0!==r.public?r.public:"public/";switch(n){case"private":return o;case"protected":return s;default:return a}},e.prototype._createNewS3Client=function(e,t){var n=e.region,r=e.credentials,i={};return e.dangerouslyConnectToHttpEndpointForTesting&&(i={endpoint:"http://localhost:20005",tls:!1,bucketEndpoint:!1,forcePathStyle:!0}),new jo(Ts(Ts({region:n,credentials:r,customUserAgent:Object(a.b)()},i),{requestHandler:new Vo({},t)}))},e.CATEGORY="Storage",e.PROVIDER_NAME="AWSS3",e}(),Us=function(){return(Us=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},Bs=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},Fs=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},zs=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},qs=new r.a("StorageClass"),Ks=function(){function e(){this._config={},this._pluggables=[],qs.debug("Storage Options",this._config),this.get=this.get.bind(this),this.put=this.put.bind(this),this.remove=this.remove.bind(this),this.list=this.list.bind(this)}return e.prototype.getModuleName=function(){return"Storage"},e.prototype.addPluggable=function(e){if(e&&"Storage"===e.getCategory()){this._pluggables.push(e);return e.configure(this._config[e.getProviderName()])}},e.prototype.getPluggable=function(e){var t=this._pluggables.find((function(t){return t.getProviderName()===e}));return void 0===t?(qs.debug("No plugin found with providerName",e),null):t},e.prototype.removePluggable=function(e){this._pluggables=this._pluggables.filter((function(t){return t.getProviderName()!==e}))},e.prototype.configure=function(e){var t=this;if(qs.debug("configure Storage"),!e)return this._config;var n=i.a.parseMobilehubConfig(e),r=Object.keys(n.Storage),o=["bucket","region","level","track","customPrefix","serverSideEncryption","SSECustomerAlgorithm","SSECustomerKey","SSECustomerKeyMD5","SSEKMSKeyId"],s=function(e){return o.some((function(t){return t===e}))};return r&&r.find((function(e){return s(e)}))&&!n.Storage.AWSS3&&(n.Storage.AWSS3={}),Object.entries(n.Storage).map((function(e){var t=zs(e,2),r=t[0],i=t[1];r&&s(r)&&void 0!==i&&(n.Storage.AWSS3[r]=i,delete n.Storage[r])})),Object.keys(n.Storage).forEach((function(e){"string"!=typeof n.Storage[e]&&(t._config[e]=Us(Us({},t._config[e]),n.Storage[e]))})),this._pluggables.forEach((function(e){e.configure(t._config[e.getProviderName()])})),0===this._pluggables.length&&this.addPluggable(new Ds),this._config},e.prototype.get=function(e,t){return Bs(this,void 0,void 0,(function(){var n,r,i;return Fs(this,(function(o){return n=(t||{}).provider,r=void 0===n?"AWSS3":n,void 0===(i=this._pluggables.find((function(e){return e.getProviderName()===r})))&&(qs.debug("No plugin found with providerName",r),Promise.reject("No plugin found in Storage for the provider")),[2,i.get(e,t)]}))}))},e.prototype.put=function(e,t,n){return Bs(this,void 0,void 0,(function(){var r,i,o;return Fs(this,(function(s){return r=(n||{}).provider,i=void 0===r?"AWSS3":r,void 0===(o=this._pluggables.find((function(e){return e.getProviderName()===i})))&&(qs.debug("No plugin found with providerName",i),Promise.reject("No plugin found in Storage for the provider")),[2,o.put(e,t,n)]}))}))},e.prototype.remove=function(e,t){return Bs(this,void 0,void 0,(function(){var n,r,i;return Fs(this,(function(o){return n=(t||{}).provider,r=void 0===n?"AWSS3":n,void 0===(i=this._pluggables.find((function(e){return e.getProviderName()===r})))&&(qs.debug("No plugin found with providerName",r),Promise.reject("No plugin found in Storage for the provider")),[2,i.remove(e,t)]}))}))},e.prototype.list=function(e,t){return Bs(this,void 0,void 0,(function(){var n,r,i;return Fs(this,(function(o){return n=(t||{}).provider,r=void 0===n?"AWSS3":n,void 0===(i=this._pluggables.find((function(e){return e.getProviderName()===r})))&&(qs.debug("No plugin found with providerName",r),Promise.reject("No plugin found in Storage for the provider")),[2,i.list(e,t)]}))}))},e}(),Hs=n(19),Vs=function(){return(Vs=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},Gs=new r.a("Storage"),Ws=null,$s=function(){if(Ws)return Ws;Gs.debug("Create Storage Instance, debug"),(Ws=new Ks).vault=new Ks;var e=Ws.configure;return Ws.configure=function(t){Gs.debug("storage configure called");var n=Vs({},e.call(Ws,t));Object.keys(n).forEach((function(e){"string"!=typeof n[e]&&(n[e]=Vs(Vs({},n[e]),{level:"private"}))})),Gs.debug("storage vault configure called"),Ws.vault.configure(n)},Ws}();Hs.a.register($s)},function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));var r=new(n(44).a)("ClientDevice_Browser");function i(){return"undefined"==typeof window?{}:function(){if("undefined"==typeof window)return r.warn("No window object available to get browser client info"),{};var e=window.navigator;if(!e)return r.warn("No navigator object available to get browser client info"),{};var t=e.platform,n=e.product,i=e.vendor,o=e.userAgent,s=e.language,a=function(e){var t=/.+(Opera[\s[A-Z]*|OPR[\sA-Z]*)\/([0-9\.]+).*/i.exec(e);if(t)return{type:t[1],version:t[2]};var n=/.+(Trident|Edge)\/([0-9\.]+).*/i.exec(e);if(n)return{type:n[1],version:n[2]};var r=/.+(Chrome|Firefox|FxiOS)\/([0-9\.]+).*/i.exec(e);if(r)return{type:r[1],version:r[2]};var i=/.+(Safari)\/([0-9\.]+).*/i.exec(e);if(i)return{type:i[1],version:i[2]};var o=/.+(AppleWebKit)\/([0-9\.]+).*/i.exec(e);if(o)return{type:o[1],version:o[2]};var s=/.*([A-Z]+)\/([0-9\.]+).*/i.exec(e);if(s)return{type:s[1],version:s[2]};return{type:"",version:""}}(o),u=(c=/\(([A-Za-z\s].*)\)/.exec((new Date).toString()),c&&c[1]||"");var c;return{platform:t,make:n||i,model:a.type,version:a.version,appVersion:[a.type,a.version].join("/"),language:s,timezone:u}}()}var o=function(){function e(){}return e.clientInfo=function(){return i()},e.dimension=function(){return"undefined"==typeof window?(r.warn("No window object available to get browser client info"),{width:320,height:320}):{width:window.innerWidth,height:window.innerHeight}},e}()},function(e,t,n){"use strict";n.d(t,"a",(function(){return f}));var r=n(44),i=new r.a("I18n"),o=function(){function e(e){this._options=null,this._lang=null,this._dict={},this._options=Object.assign({},e),this._lang=this._options.language,!this._lang&&"undefined"!=typeof window&&window&&window.navigator&&(this._lang=window.navigator.language),i.debug(this._lang)}return e.prototype.setLanguage=function(e){this._lang=e},e.prototype.get=function(e,t){if(void 0===t&&(t=void 0),!this._lang)return void 0!==t?t:e;var n=this._lang,r=this.getByLanguage(e,n);return r||(n.indexOf("-")>0&&(r=this.getByLanguage(e,n.split("-")[0])),r||(void 0!==t?t:e))},e.prototype.getByLanguage=function(e,t,n){if(void 0===n&&(n=null),!t)return n;var r=this._dict[t];return r?r[e]:n},e.prototype.putVocabulariesForLanguage=function(e,t){var n=this._dict[e];n||(n=this._dict[e]={}),Object.assign(n,t)},e.prototype.putVocabularies=function(e){var t=this;Object.keys(e).map((function(n){t.putVocabulariesForLanguage(n,e[n])}))},e}(),s=n(19),a=new r.a("I18n"),u=null,c=null,f=function(){function e(){}return e.configure=function(t){return a.debug("configure I18n"),t?(u=Object.assign({},u,t.I18n||t),e.createInstance(),u):u},e.getModuleName=function(){return"I18n"},e.createInstance=function(){a.debug("create I18n instance"),c||(c=new o(u))},e.setLanguage=function(t){return e.checkConfig(),c.setLanguage(t)},e.get=function(t,n){return e.checkConfig()?c.get(t,n):void 0===n?t:n},e.putVocabulariesForLanguage=function(t,n){return e.checkConfig(),c.putVocabulariesForLanguage(t,n)},e.putVocabularies=function(t){return e.checkConfig(),c.putVocabularies(t)},e.checkConfig=function(){return c||(c=new o(u)),!0},e}();s.a.register(f)},function(e,t,n){"use strict";n.d(t,"a",(function(){return a}));var r=n(44),i=n(33),o=n(19);function s(e){return(s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var a=function(){function e(){this._logger=new r.a("ServiceWorker")}return Object.defineProperty(e.prototype,"serviceWorker",{get:function(){return this._serviceWorker},enumerable:!0,configurable:!0}),e.prototype.register=function(e,t){var n=this;return void 0===e&&(e="/service-worker.js"),void 0===t&&(t="/"),this._logger.debug("registering "+e),this._logger.debug("registering service worker with scope "+t),new Promise((function(r,i){if(!navigator||!("serviceWorker"in navigator))return i(new Error("Service Worker not available"));navigator.serviceWorker.register(e,{scope:t}).then((function(e){return e.installing?n._serviceWorker=e.installing:e.waiting?n._serviceWorker=e.waiting:e.active&&(n._serviceWorker=e.active),n._registration=e,n._setupListeners(),n._logger.debug("Service Worker Registration Success: "+e),r(e)})).catch((function(e){return n._logger.debug("Service Worker Registration Failed "+e),i(e)}))}))},e.prototype.enablePush=function(e){var t=this;if(!this._registration)throw new Error("Service Worker not registered");return this._publicKey=e,new Promise((function(n,r){if(!Object(i.b)().isBrowser)return r(new Error("Service Worker not available"));t._registration.pushManager.getSubscription().then((function(r){if(!r)return t._logger.debug("User is NOT subscribed to push"),t._registration.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:t._urlB64ToUint8Array(e)}).then((function(e){t._subscription=e,t._logger.debug("User subscribed: "+JSON.stringify(e)),n(e)})).catch((function(e){t._logger.error(e)}));t._subscription=r,t._logger.debug("User is subscribed to push: "+JSON.stringify(r)),n(r)}))}))},e.prototype._urlB64ToUint8Array=function(e){for(var t=(e+"=".repeat((4-e.length%4)%4)).replace(/\-/g,"+").replace(/_/g,"/"),n=window.atob(t),r=new Uint8Array(n.length),i=0;i<n.length;++i)r[i]=n.charCodeAt(i);return r},e.prototype.send=function(e){this._serviceWorker&&this._serviceWorker.postMessage("object"===s(e)?JSON.stringify(e):e)},e.prototype._setupListeners=function(){var e=this;this._serviceWorker.addEventListener("statechange",(function(t){var n=e._serviceWorker.state;e._logger.debug("ServiceWorker statechange: "+n),o.a.Analytics&&"function"==typeof o.a.Analytics.record&&o.a.Analytics.record({name:"ServiceWorker",attributes:{state:n}})})),this._serviceWorker.addEventListener("message",(function(t){e._logger.debug("ServiceWorker message event: "+t)}))},e}()},function(e,t,n){"use strict";n.d(t,"a",(function(){return dr}));var r=n(88),i=n(44),o=n(141),s=n(104),a=n(33),u=n(50),c=n(89),f=function(e,t){return(f=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};function l(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}f(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var d=function(){return(d=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};function h(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))}function p(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}}Object.create;function v(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s}var g,m,b,y,w,_,S,E,M,A,I,k,O,x,C,T,P,N,R,L,j,D,U,B,F,z,q,K,H,V,G,W,$,Y,J,Z,X,Q,ee,te,ne,re,ie,oe,se,ae,ue,ce,fe,le,de,he,pe,ve,ge,me,be,ye,we,_e,Se,Ee,Me,Ae,Ie,ke,Oe,xe,Ce,Te,Pe,Ne,Re,Le,je,De,Ue,Be,Fe,ze,qe,Ke,He,Ve,Ge,We,$e,Ye,Je,Ze,Xe,Qe,et,tt,nt,rt,it,ot,st,at,ut,ct,ft,lt,dt,ht,pt,vt,gt,mt,bt,yt,wt,_t,St,Et,Mt,At,It,kt,Ot,xt,Ct,Tt,Pt,Nt,Rt,Lt,jt,Dt,Ut;Object.create;(g||(g={})).filterSensitiveLog=function(e){return d({},e)},(m||(m={})).filterSensitiveLog=function(e){return d({},e)},(b||(b={})).filterSensitiveLog=function(e){return d({},e)},(y||(y={})).filterSensitiveLog=function(e){return d({},e)},(w||(w={})).filterSensitiveLog=function(e){return d({},e)},(_||(_={})).filterSensitiveLog=function(e){return d({},e)},(S||(S={})).filterSensitiveLog=function(e){return d({},e)},(E||(E={})).filterSensitiveLog=function(e){return d({},e)},(M||(M={})).filterSensitiveLog=function(e){return d({},e)},(A||(A={})).filterSensitiveLog=function(e){return d({},e)},(I||(I={})).filterSensitiveLog=function(e){return d({},e)},(k||(k={})).filterSensitiveLog=function(e){return d({},e)},(O||(O={})).filterSensitiveLog=function(e){return d({},e)},(x||(x={})).filterSensitiveLog=function(e){return d({},e)},(C||(C={})).filterSensitiveLog=function(e){return d({},e)},(T||(T={})).filterSensitiveLog=function(e){return d({},e)},(P||(P={})).filterSensitiveLog=function(e){return d({},e)},(N||(N={})).filterSensitiveLog=function(e){return d({},e)},(R||(R={})).filterSensitiveLog=function(e){return d({},e)},(L||(L={})).filterSensitiveLog=function(e){return d({},e)},(j||(j={})).filterSensitiveLog=function(e){return d({},e)},(D||(D={})).filterSensitiveLog=function(e){return d({},e)},(U||(U={})).filterSensitiveLog=function(e){return d({},e)},(B||(B={})).filterSensitiveLog=function(e){return d({},e)},(F||(F={})).filterSensitiveLog=function(e){return d({},e)},(z||(z={})).filterSensitiveLog=function(e){return d({},e)},(q||(q={})).filterSensitiveLog=function(e){return d({},e)},(K||(K={})).filterSensitiveLog=function(e){return d({},e)},(H||(H={})).filterSensitiveLog=function(e){return d({},e)},(V||(V={})).filterSensitiveLog=function(e){return d({},e)},(G||(G={})).filterSensitiveLog=function(e){return d({},e)},(W||(W={})).filterSensitiveLog=function(e){return d({},e)},($||($={})).filterSensitiveLog=function(e){return d({},e)},(Y||(Y={})).filterSensitiveLog=function(e){return d({},e)},(J||(J={})).filterSensitiveLog=function(e){return d({},e)},(Z||(Z={})).filterSensitiveLog=function(e){return d({},e)},(X||(X={})).filterSensitiveLog=function(e){return d({},e)},(Q||(Q={})).filterSensitiveLog=function(e){return d({},e)},(ee||(ee={})).filterSensitiveLog=function(e){return d({},e)},(te||(te={})).filterSensitiveLog=function(e){return d({},e)},(ne||(ne={})).filterSensitiveLog=function(e){return d({},e)},(re||(re={})).filterSensitiveLog=function(e){return d({},e)},(ie||(ie={})).filterSensitiveLog=function(e){return d({},e)},(oe||(oe={})).filterSensitiveLog=function(e){return d({},e)},(se||(se={})).filterSensitiveLog=function(e){return d({},e)},(ae||(ae={})).filterSensitiveLog=function(e){return d({},e)},(ue||(ue={})).filterSensitiveLog=function(e){return d({},e)},(ce||(ce={})).filterSensitiveLog=function(e){return d({},e)},(fe||(fe={})).filterSensitiveLog=function(e){return d({},e)},(le||(le={})).filterSensitiveLog=function(e){return d({},e)},(de||(de={})).filterSensitiveLog=function(e){return d({},e)},(he||(he={})).filterSensitiveLog=function(e){return d({},e)},(pe||(pe={})).filterSensitiveLog=function(e){return d({},e)},(ve||(ve={})).filterSensitiveLog=function(e){return d({},e)},(ge||(ge={})).filterSensitiveLog=function(e){return d({},e)},(me||(me={})).filterSensitiveLog=function(e){return d({},e)},(be||(be={})).filterSensitiveLog=function(e){return d({},e)},(ye||(ye={})).filterSensitiveLog=function(e){return d({},e)},(we||(we={})).filterSensitiveLog=function(e){return d({},e)},(_e||(_e={})).filterSensitiveLog=function(e){return d({},e)},(Se||(Se={})).filterSensitiveLog=function(e){return d({},e)},(Ee||(Ee={})).filterSensitiveLog=function(e){return d({},e)},(Me||(Me={})).filterSensitiveLog=function(e){return d({},e)},(Ae||(Ae={})).filterSensitiveLog=function(e){return d({},e)},(Ie||(Ie={})).filterSensitiveLog=function(e){return d({},e)},(ke||(ke={})).filterSensitiveLog=function(e){return d({},e)},(Oe||(Oe={})).filterSensitiveLog=function(e){return d({},e)},(xe||(xe={})).filterSensitiveLog=function(e){return d({},e)},(Ce||(Ce={})).filterSensitiveLog=function(e){return d({},e)},(Te||(Te={})).filterSensitiveLog=function(e){return d({},e)},(Pe||(Pe={})).filterSensitiveLog=function(e){return d({},e)},(Ne||(Ne={})).filterSensitiveLog=function(e){return d({},e)},(Re||(Re={})).filterSensitiveLog=function(e){return d({},e)},(Le||(Le={})).filterSensitiveLog=function(e){return d({},e)},(je||(je={})).filterSensitiveLog=function(e){return d({},e)},(De||(De={})).filterSensitiveLog=function(e){return d({},e)},(Ue||(Ue={})).filterSensitiveLog=function(e){return d({},e)},(Be||(Be={})).filterSensitiveLog=function(e){return d({},e)},(Fe||(Fe={})).filterSensitiveLog=function(e){return d({},e)},(ze||(ze={})).filterSensitiveLog=function(e){return d({},e)},(qe||(qe={})).filterSensitiveLog=function(e){return d({},e)},(Ke||(Ke={})).filterSensitiveLog=function(e){return d({},e)},(He||(He={})).filterSensitiveLog=function(e){return d({},e)},(Ve||(Ve={})).filterSensitiveLog=function(e){return d({},e)},(Ge||(Ge={})).filterSensitiveLog=function(e){return d({},e)},(We||(We={})).filterSensitiveLog=function(e){return d({},e)},($e||($e={})).filterSensitiveLog=function(e){return d({},e)},(Ye||(Ye={})).filterSensitiveLog=function(e){return d({},e)},(Je||(Je={})).filterSensitiveLog=function(e){return d({},e)},(Ze||(Ze={})).filterSensitiveLog=function(e){return d({},e)},(Xe||(Xe={})).filterSensitiveLog=function(e){return d({},e)},(Qe||(Qe={})).filterSensitiveLog=function(e){return d({},e)},(et||(et={})).filterSensitiveLog=function(e){return d({},e)},(tt||(tt={})).filterSensitiveLog=function(e){return d({},e)},(nt||(nt={})).filterSensitiveLog=function(e){return d({},e)},(rt||(rt={})).filterSensitiveLog=function(e){return d({},e)},(it||(it={})).filterSensitiveLog=function(e){return d({},e)},(ot||(ot={})).filterSensitiveLog=function(e){return d({},e)},(st||(st={})).filterSensitiveLog=function(e){return d({},e)},(at||(at={})).filterSensitiveLog=function(e){return d({},e)},(ut||(ut={})).filterSensitiveLog=function(e){return d({},e)},(ct||(ct={})).filterSensitiveLog=function(e){return d({},e)},(ft||(ft={})).filterSensitiveLog=function(e){return d({},e)},(lt||(lt={})).filterSensitiveLog=function(e){return d({},e)},(dt||(dt={})).filterSensitiveLog=function(e){return d({},e)},(ht||(ht={})).filterSensitiveLog=function(e){return d({},e)},(pt||(pt={})).filterSensitiveLog=function(e){return d({},e)},(vt||(vt={})).filterSensitiveLog=function(e){return d({},e)},(gt||(gt={})).filterSensitiveLog=function(e){return d({},e)},(mt||(mt={})).filterSensitiveLog=function(e){return d({},e)},(bt||(bt={})).filterSensitiveLog=function(e){return d({},e)},(yt||(yt={})).filterSensitiveLog=function(e){return d({},e)},(wt||(wt={})).filterSensitiveLog=function(e){return d({},e)},(_t||(_t={})).filterSensitiveLog=function(e){return d({},e)},(St||(St={})).filterSensitiveLog=function(e){return d({},e)},(Et||(Et={})).filterSensitiveLog=function(e){return d({},e)},(Mt||(Mt={})).filterSensitiveLog=function(e){return d({},e)},(At||(At={})).filterSensitiveLog=function(e){return d({},e)},(It||(It={})).filterSensitiveLog=function(e){return d({},e)},(kt||(kt={})).filterSensitiveLog=function(e){return d({},e)},(Ot||(Ot={})).filterSensitiveLog=function(e){return d({},e)},(xt||(xt={})).filterSensitiveLog=function(e){return d({},e)},(Ct||(Ct={})).filterSensitiveLog=function(e){return d({},e)},(Tt||(Tt={})).filterSensitiveLog=function(e){return d({},e)},(Pt||(Pt={})).filterSensitiveLog=function(e){return d({},e)},(Nt||(Nt={})).filterSensitiveLog=function(e){return d({},e)},(Rt||(Rt={})).filterSensitiveLog=function(e){return d({},e)},(Lt||(Lt={})).filterSensitiveLog=function(e){return d({},e)},(jt||(jt={})).filterSensitiveLog=function(e){return d({},e)},(Dt||(Dt={})).filterSensitiveLog=function(e){return d({},e)},(Ut||(Ut={})).filterSensitiveLog=function(e){return d({},e)};var Bt=n(2),Ft=n(0),zt=function(e,t){return h(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,l,h,v,g,m;return p(this,(function(p){switch(p.label){case 0:return r=[d({},e)],m={},[4,mn(e.body,t)];case 1:switch(n=d.apply(void 0,r.concat([(m.body=p.sent(),m)])),o="UnknownError",o=bn(e,n.body),o){case"BadRequestException":case"com.amazonaws.pinpoint#BadRequestException":return[3,2];case"ForbiddenException":case"com.amazonaws.pinpoint#ForbiddenException":return[3,4];case"InternalServerErrorException":case"com.amazonaws.pinpoint#InternalServerErrorException":return[3,6];case"MethodNotAllowedException":case"com.amazonaws.pinpoint#MethodNotAllowedException":return[3,8];case"NotFoundException":case"com.amazonaws.pinpoint#NotFoundException":return[3,10];case"PayloadTooLargeException":case"com.amazonaws.pinpoint#PayloadTooLargeException":return[3,12];case"TooManyRequestsException":case"com.amazonaws.pinpoint#TooManyRequestsException":return[3,14]}return[3,16];case 2:return s=[{}],[4,Kt(n,t)];case 3:return i=d.apply(void 0,[d.apply(void 0,s.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 4:return a=[{}],[4,Ht(n,t)];case 5:return i=d.apply(void 0,[d.apply(void 0,a.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 6:return u=[{}],[4,Vt(n,t)];case 7:return i=d.apply(void 0,[d.apply(void 0,u.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 8:return c=[{}],[4,Gt(n,t)];case 9:return i=d.apply(void 0,[d.apply(void 0,c.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 10:return f=[{}],[4,Wt(n,t)];case 11:return i=d.apply(void 0,[d.apply(void 0,f.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 12:return l=[{}],[4,$t(n,t)];case 13:return i=d.apply(void 0,[d.apply(void 0,l.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 14:return h=[{}],[4,Yt(n,t)];case 15:return i=d.apply(void 0,[d.apply(void 0,h.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 16:v=n.body,o=v.code||v.Code||o,i=d(d({},v),{name:""+o,message:v.message||v.Message||o,$fault:"client",$metadata:vn(e)}),p.label=17;case 17:return g=i.message||i.Message||o,i.message=g,delete i.Message,[2,Promise.reject(Object.assign(new Error(g),i))]}}))}))},qt=function(e,t){return h(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,l,h,v,g,m;return p(this,(function(p){switch(p.label){case 0:return r=[d({},e)],m={},[4,mn(e.body,t)];case 1:switch(n=d.apply(void 0,r.concat([(m.body=p.sent(),m)])),o="UnknownError",o=bn(e,n.body),o){case"BadRequestException":case"com.amazonaws.pinpoint#BadRequestException":return[3,2];case"ForbiddenException":case"com.amazonaws.pinpoint#ForbiddenException":return[3,4];case"InternalServerErrorException":case"com.amazonaws.pinpoint#InternalServerErrorException":return[3,6];case"MethodNotAllowedException":case"com.amazonaws.pinpoint#MethodNotAllowedException":return[3,8];case"NotFoundException":case"com.amazonaws.pinpoint#NotFoundException":return[3,10];case"PayloadTooLargeException":case"com.amazonaws.pinpoint#PayloadTooLargeException":return[3,12];case"TooManyRequestsException":case"com.amazonaws.pinpoint#TooManyRequestsException":return[3,14]}return[3,16];case 2:return s=[{}],[4,Kt(n,t)];case 3:return i=d.apply(void 0,[d.apply(void 0,s.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 4:return a=[{}],[4,Ht(n,t)];case 5:return i=d.apply(void 0,[d.apply(void 0,a.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 6:return u=[{}],[4,Vt(n,t)];case 7:return i=d.apply(void 0,[d.apply(void 0,u.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 8:return c=[{}],[4,Gt(n,t)];case 9:return i=d.apply(void 0,[d.apply(void 0,c.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 10:return f=[{}],[4,Wt(n,t)];case 11:return i=d.apply(void 0,[d.apply(void 0,f.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 12:return l=[{}],[4,$t(n,t)];case 13:return i=d.apply(void 0,[d.apply(void 0,l.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 14:return h=[{}],[4,Yt(n,t)];case 15:return i=d.apply(void 0,[d.apply(void 0,h.concat([p.sent()])),{name:o,$metadata:vn(e)}]),[3,17];case 16:v=n.body,o=v.code||v.Code||o,i=d(d({},v),{name:""+o,message:v.message||v.Message||o,$fault:"client",$metadata:vn(e)}),p.label=17;case 17:return g=i.message||i.Message||o,i.message=g,delete i.Message,[2,Promise.reject(Object.assign(new Error(g),i))]}}))}))},Kt=function(e,t){return h(void 0,void 0,void 0,(function(){var t,n;return p(this,(function(r){return t={name:"BadRequestException",$fault:"client",$metadata:vn(e),Message:void 0,RequestID:void 0},void 0!==(n=e.body).Message&&null!==n.Message&&(t.Message=n.Message),void 0!==n.RequestID&&null!==n.RequestID&&(t.RequestID=n.RequestID),[2,t]}))}))},Ht=function(e,t){return h(void 0,void 0,void 0,(function(){var t,n;return p(this,(function(r){return t={name:"ForbiddenException",$fault:"client",$metadata:vn(e),Message:void 0,RequestID:void 0},void 0!==(n=e.body).Message&&null!==n.Message&&(t.Message=n.Message),void 0!==n.RequestID&&null!==n.RequestID&&(t.RequestID=n.RequestID),[2,t]}))}))},Vt=function(e,t){return h(void 0,void 0,void 0,(function(){var t,n;return p(this,(function(r){return t={name:"InternalServerErrorException",$fault:"server",$metadata:vn(e),Message:void 0,RequestID:void 0},void 0!==(n=e.body).Message&&null!==n.Message&&(t.Message=n.Message),void 0!==n.RequestID&&null!==n.RequestID&&(t.RequestID=n.RequestID),[2,t]}))}))},Gt=function(e,t){return h(void 0,void 0,void 0,(function(){var t,n;return p(this,(function(r){return t={name:"MethodNotAllowedException",$fault:"client",$metadata:vn(e),Message:void 0,RequestID:void 0},void 0!==(n=e.body).Message&&null!==n.Message&&(t.Message=n.Message),void 0!==n.RequestID&&null!==n.RequestID&&(t.RequestID=n.RequestID),[2,t]}))}))},Wt=function(e,t){return h(void 0,void 0,void 0,(function(){var t,n;return p(this,(function(r){return t={name:"NotFoundException",$fault:"client",$metadata:vn(e),Message:void 0,RequestID:void 0},void 0!==(n=e.body).Message&&null!==n.Message&&(t.Message=n.Message),void 0!==n.RequestID&&null!==n.RequestID&&(t.RequestID=n.RequestID),[2,t]}))}))},$t=function(e,t){return h(void 0,void 0,void 0,(function(){var t,n;return p(this,(function(r){return t={name:"PayloadTooLargeException",$fault:"client",$metadata:vn(e),Message:void 0,RequestID:void 0},void 0!==(n=e.body).Message&&null!==n.Message&&(t.Message=n.Message),void 0!==n.RequestID&&null!==n.RequestID&&(t.RequestID=n.RequestID),[2,t]}))}))},Yt=function(e,t){return h(void 0,void 0,void 0,(function(){var t,n;return p(this,(function(r){return t={name:"TooManyRequestsException",$fault:"client",$metadata:vn(e),Message:void 0,RequestID:void 0},void 0!==(n=e.body).Message&&null!==n.Message&&(t.Message=n.Message),void 0!==n.RequestID&&null!==n.RequestID&&(t.RequestID=n.RequestID),[2,t]}))}))},Jt=function(e,t){return d(d(d(d(d(d(d(d({},void 0!==e.AppVersion&&{AppVersion:e.AppVersion}),void 0!==e.Locale&&{Locale:e.Locale}),void 0!==e.Make&&{Make:e.Make}),void 0!==e.Model&&{Model:e.Model}),void 0!==e.ModelVersion&&{ModelVersion:e.ModelVersion}),void 0!==e.Platform&&{Platform:e.Platform}),void 0!==e.PlatformVersion&&{PlatformVersion:e.PlatformVersion}),void 0!==e.Timezone&&{Timezone:e.Timezone})},Zt=function(e,t){return d(d(d(d(d(d({},void 0!==e.City&&{City:e.City}),void 0!==e.Country&&{Country:e.Country}),void 0!==e.Latitude&&{Latitude:e.Latitude}),void 0!==e.Longitude&&{Longitude:e.Longitude}),void 0!==e.PostalCode&&{PostalCode:e.PostalCode}),void 0!==e.Region&&{Region:e.Region})},Xt=function(e,t){return d(d(d(d(d(d(d(d(d(d(d({},void 0!==e.Address&&{Address:e.Address}),void 0!==e.Attributes&&{Attributes:an(e.Attributes,t)}),void 0!==e.ChannelType&&{ChannelType:e.ChannelType}),void 0!==e.Demographic&&{Demographic:Jt(e.Demographic,t)}),void 0!==e.EffectiveDate&&{EffectiveDate:e.EffectiveDate}),void 0!==e.EndpointStatus&&{EndpointStatus:e.EndpointStatus}),void 0!==e.Location&&{Location:Zt(e.Location,t)}),void 0!==e.Metrics&&{Metrics:nn(e.Metrics,t)}),void 0!==e.OptOut&&{OptOut:e.OptOut}),void 0!==e.RequestId&&{RequestId:e.RequestId}),void 0!==e.User&&{User:Qt(e.User,t)})},Qt=function(e,t){return d(d({},void 0!==e.UserAttributes&&{UserAttributes:an(e.UserAttributes,t)}),void 0!==e.UserId&&{UserId:e.UserId})},en=function(e,t){return d({},void 0!==e.BatchItem&&{BatchItem:sn(e.BatchItem,t)})},tn=function(e,t){return e.map((function(e){return e}))},nn=function(e,t){return Object.entries(e).reduce((function(e,t){var n,r=v(t,2),i=r[0],o=r[1];return d(d({},e),((n={})[i]=o,n))}),{})},rn=function(e,t){return Object.entries(e).reduce((function(e,t){var n,r=v(t,2),i=r[0],o=r[1];return d(d({},e),((n={})[i]=o,n))}),{})},on=function(e,t){return Object.entries(e).reduce((function(e,n){var r,i=v(n,2),o=i[0],s=i[1];return d(d({},e),((r={})[o]=function(e,t){return d(d(d(d(d(d(d(d(d(d({},void 0!==e.AppPackageName&&{AppPackageName:e.AppPackageName}),void 0!==e.AppTitle&&{AppTitle:e.AppTitle}),void 0!==e.AppVersionCode&&{AppVersionCode:e.AppVersionCode}),void 0!==e.Attributes&&{Attributes:rn(e.Attributes,t)}),void 0!==e.ClientSdkVersion&&{ClientSdkVersion:e.ClientSdkVersion}),void 0!==e.EventType&&{EventType:e.EventType}),void 0!==e.Metrics&&{Metrics:nn(e.Metrics,t)}),void 0!==e.SdkName&&{SdkName:e.SdkName}),void 0!==e.Session&&{Session:cn(e.Session,t)}),void 0!==e.Timestamp&&{Timestamp:e.Timestamp})}(s,t),r))}),{})},sn=function(e,t){return Object.entries(e).reduce((function(e,n){var r,i=v(n,2),o=i[0],s=i[1];return d(d({},e),((r={})[o]=function(e,t){return d(d({},void 0!==e.Endpoint&&{Endpoint:un(e.Endpoint,t)}),void 0!==e.Events&&{Events:on(e.Events,t)})}(s,t),r))}),{})},an=function(e,t){return Object.entries(e).reduce((function(e,n){var r,i=v(n,2),o=i[0],s=i[1];return d(d({},e),((r={})[o]=tn(s,t),r))}),{})},un=function(e,t){return d(d(d(d(d(d(d(d(d(d(d({},void 0!==e.Address&&{Address:e.Address}),void 0!==e.Attributes&&{Attributes:an(e.Attributes,t)}),void 0!==e.ChannelType&&{ChannelType:e.ChannelType}),void 0!==e.Demographic&&{Demographic:Jt(e.Demographic,t)}),void 0!==e.EffectiveDate&&{EffectiveDate:e.EffectiveDate}),void 0!==e.EndpointStatus&&{EndpointStatus:e.EndpointStatus}),void 0!==e.Location&&{Location:Zt(e.Location,t)}),void 0!==e.Metrics&&{Metrics:nn(e.Metrics,t)}),void 0!==e.OptOut&&{OptOut:e.OptOut}),void 0!==e.RequestId&&{RequestId:e.RequestId}),void 0!==e.User&&{User:Qt(e.User,t)})},cn=function(e,t){return d(d(d(d({},void 0!==e.Duration&&{Duration:e.Duration}),void 0!==e.Id&&{Id:e.Id}),void 0!==e.StartTimestamp&&{StartTimestamp:e.StartTimestamp}),void 0!==e.StopTimestamp&&{StopTimestamp:e.StopTimestamp})},fn=function(e,t){return{Message:void 0!==e.Message&&null!==e.Message?e.Message:void 0,StatusCode:void 0!==e.StatusCode&&null!==e.StatusCode?e.StatusCode:void 0}},ln=function(e,t){return{Results:void 0!==e.Results&&null!==e.Results?hn(e.Results,t):void 0}},dn=function(e,t){return Object.entries(e).reduce((function(e,t){var n,r=v(t,2),i=r[0],o=r[1];return d(d({},e),((n={})[i]=function(e,t){return{Message:void 0!==e.Message&&null!==e.Message?e.Message:void 0,StatusCode:void 0!==e.StatusCode&&null!==e.StatusCode?e.StatusCode:void 0}}(o),n))}),{})},hn=function(e,t){return Object.entries(e).reduce((function(e,n){var r,i=v(n,2),o=i[0],s=i[1];return d(d({},e),((r={})[o]=function(e,t){return{EndpointItemResponse:void 0!==e.EndpointItemResponse&&null!==e.EndpointItemResponse?fn(e.EndpointItemResponse):void 0,EventsItemResponse:void 0!==e.EventsItemResponse&&null!==e.EventsItemResponse?dn(e.EventsItemResponse,t):void 0}}(s,t),r))}),{})},pn=function(e,t){return{Message:void 0!==e.Message&&null!==e.Message?e.Message:void 0,RequestID:void 0!==e.RequestID&&null!==e.RequestID?e.RequestID:void 0}},vn=function(e){return{httpStatusCode:e.statusCode,httpHeaders:e.headers,requestId:e.headers["x-amzn-requestid"]}},gn=function(e,t){return void 0===e&&(e=new Uint8Array),e instanceof Uint8Array?Promise.resolve(e):t.streamCollector(e)||Promise.resolve(new Uint8Array)},mn=function(e,t){return function(e,t){return gn(e,t).then((function(e){return t.utf8Encoder(e)}))}(e,t).then((function(e){return e.length?JSON.parse(e):{}}))},bn=function(e,t){var n,r,i=function(e){var t=e;return t.indexOf(":")>=0&&(t=t.split(":")[0]),t.indexOf("#")>=0&&(t=t.split("#")[1]),t},o=(n=e.headers,r="x-amzn-errortype",Object.keys(n).find((function(e){return e.toLowerCase()===r.toLowerCase()})));return void 0!==o?i(e.headers[o]):void 0!==t.code?i(t.code):void 0!==t.__type?i(t.__type):""},yn=n(10),wn=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return l(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(yn.a)(t,this.serialize,this.deserialize));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"PinpointClient",commandName:"PutEventsCommand",inputFilterSensitiveLog:Ie.filterSensitiveLog,outputFilterSensitiveLog:ke.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"PinpointClient",commandName:"PutEventsCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return h(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f;return p(this,(function(l){switch(l.label){case 0:if(n={"Content-Type":"application/json"},r="/v1/apps/{ApplicationId}/events",void 0===e.ApplicationId)throw new Error("No value provided for input HTTP label: ApplicationId.");if((i=e.ApplicationId).length<=0)throw new Error("Empty value provided for input HTTP label: ApplicationId.");return r=r.replace("{ApplicationId}",Object(Ft.f)(i)),void 0!==e.EventsRequest&&(o=en(e.EventsRequest,t)),void 0===o&&(o={}),o=JSON.stringify(o),[4,t.endpoint()];case 1:return s=l.sent(),a=s.hostname,u=s.protocol,c=void 0===u?"https":u,f=s.port,[2,new Bt.a({protocol:c,hostname:a,port:f,method:"POST",headers:n,path:r,body:o})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return h(void 0,void 0,void 0,(function(){var n,r;return p(this,(function(i){switch(i.label){case 0:return 202!==e.statusCode&&e.statusCode>=300?[2,zt(e,t)]:(n={$metadata:vn(e),EventsResponse:void 0},[4,mn(e.body,t)]);case 1:return r=i.sent(),n.EventsResponse=ln(r,t),[2,Promise.resolve(n)]}}))}))}(e,t)},t}(Ft.b),_n=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return l(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(yn.a)(t,this.serialize,this.deserialize));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"PinpointClient",commandName:"UpdateEndpointCommand",inputFilterSensitiveLog:ct.filterSensitiveLog,outputFilterSensitiveLog:ft.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"PinpointClient",commandName:"UpdateEndpointCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return h(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f;return p(this,(function(l){switch(l.label){case 0:if(n={"Content-Type":"application/json"},r="/v1/apps/{ApplicationId}/endpoints/{EndpointId}",void 0===e.ApplicationId)throw new Error("No value provided for input HTTP label: ApplicationId.");if((i=e.ApplicationId).length<=0)throw new Error("Empty value provided for input HTTP label: ApplicationId.");if(r=r.replace("{ApplicationId}",Object(Ft.f)(i)),void 0===e.EndpointId)throw new Error("No value provided for input HTTP label: EndpointId.");if((i=e.EndpointId).length<=0)throw new Error("Empty value provided for input HTTP label: EndpointId.");return r=r.replace("{EndpointId}",Object(Ft.f)(i)),void 0!==e.EndpointRequest&&(o=Xt(e.EndpointRequest,t)),void 0===o&&(o={}),o=JSON.stringify(o),[4,t.endpoint()];case 1:return s=l.sent(),a=s.hostname,u=s.protocol,c=void 0===u?"https":u,f=s.port,[2,new Bt.a({protocol:c,hostname:a,port:f,method:"PUT",headers:n,path:r,body:o})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return h(void 0,void 0,void 0,(function(){var n,r;return p(this,(function(i){switch(i.label){case 0:return 202!==e.statusCode&&e.statusCode>=300?[2,qt(e,t)]:(n={$metadata:vn(e),MessageBody:void 0},[4,mn(e.body,t)]);case 1:return r=i.sent(),n.MessageBody=pn(r,t),[2,Promise.resolve(n)]}}))}))}(e,t)},t}(Ft.b),Sn=n(149),En=n(38),Mn=n(18),An=n(24),In=n(11),kn=n(39),On=n(17),xn=n(40),Cn=n(41),Tn=n(15),Pn=new Set(["ap-east-1","ap-northeast-1","ap-northeast-2","ap-south-1","ap-southeast-1","ap-southeast-2","ca-central-1","eu-central-1","eu-north-1","eu-west-1","eu-west-2","eu-west-3","me-south-1","sa-east-1","us-east-1","us-east-2","us-west-1","us-west-2"]),Nn=new Set(["cn-north-1","cn-northwest-1"]),Rn=new Set(["us-iso-east-1"]),Ln=new Set(["us-isob-east-1"]),jn=new Set(["us-gov-east-1","us-gov-west-1"]),Dn=d(d({},{apiVersion:"2016-12-01",disableHostPrefix:!1,logger:{},regionInfoProvider:function(e,t){var n=void 0;switch(e){case"ap-south-1":n={hostname:"pinpoint.ap-south-1.amazonaws.com",partition:"aws",signingService:"mobiletargeting"};break;case"ap-southeast-2":n={hostname:"pinpoint.ap-southeast-2.amazonaws.com",partition:"aws",signingService:"mobiletargeting"};break;case"eu-central-1":n={hostname:"pinpoint.eu-central-1.amazonaws.com",partition:"aws",signingService:"mobiletargeting"};break;case"eu-west-1":n={hostname:"pinpoint.eu-west-1.amazonaws.com",partition:"aws",signingService:"mobiletargeting"};break;case"us-east-1":n={hostname:"pinpoint.us-east-1.amazonaws.com",partition:"aws",signingService:"mobiletargeting"};break;case"us-west-2":n={hostname:"pinpoint.us-west-2.amazonaws.com",partition:"aws",signingService:"mobiletargeting"};break;default:Pn.has(e)&&(n={hostname:"pinpoint.{region}.amazonaws.com".replace("{region}",e),partition:"aws",signingService:"mobiletargeting"}),Nn.has(e)&&(n={hostname:"pinpoint.{region}.amazonaws.com.cn".replace("{region}",e),partition:"aws-cn"}),Rn.has(e)&&(n={hostname:"pinpoint.{region}.c2s.ic.gov".replace("{region}",e),partition:"aws-iso"}),Ln.has(e)&&(n={hostname:"pinpoint.{region}.sc2s.sgov.gov".replace("{region}",e),partition:"aws-iso-b"}),jn.has(e)&&(n={hostname:"pinpoint.{region}.amazonaws.com".replace("{region}",e),partition:"aws-us-gov"}),void 0===n&&(n={hostname:"pinpoint.{region}.amazonaws.com".replace("{region}",e),partition:"aws",signingService:"mobiletargeting"})}return Promise.resolve(n)},signingName:"mobiletargeting"}),{runtime:"browser",base64Decoder:On.a,base64Encoder:On.b,bodyLengthChecker:xn.a,credentialDefaultProvider:Object(An.a)("Credential is missing"),defaultUserAgent:Object(Cn.a)(Sn.name,Sn.version),maxAttempts:In.a,region:Object(An.a)("Region is missing"),requestHandler:new Mn.a,sha256:En.Sha256,streamCollector:Mn.b,urlParser:kn.a,utf8Decoder:Tn.a,utf8Encoder:Tn.b}),Un=n(22),Bn=n(37),Fn=n(21),zn=n(43),qn=n(25),Kn=n(23),Hn=function(e){function t(t){var n=this,r=d(d({},Dn),t),i=Object(Un.b)(r),o=Object(Un.a)(i),s=Object(qn.b)(o),a=Object(In.c)(s),u=Object(Kn.b)(a),c=Object(Fn.b)(u);return(n=e.call(this,c)||this).config=c,n.middlewareStack.use(Object(qn.a)(n.config)),n.middlewareStack.use(Object(In.b)(n.config)),n.middlewareStack.use(Object(Kn.a)(n.config)),n.middlewareStack.use(Object(Bn.a)(n.config)),n.middlewareStack.use(Object(Fn.a)(n.config)),n.middlewareStack.use(Object(zn.a)(n.config)),n}return l(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this)},t}(Ft.a),Vn=n(26),Gn=n(27),Wn=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},$n=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},Yn=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},Jn=function(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(Yn(arguments[t]));return e},Zn=new i.a("EventsBuffer"),Xn=[429,500],Qn=[202],er=function(){function e(e,t){this._pause=!1,this._flush=!1,Zn.debug("Instantiating buffer with config:",t),this._buffer=[],this._client=e,this._config=t,this._sendBatch=this._sendBatch.bind(this),this._startLoop()}return e.prototype.push=function(e){var t;if(this._buffer>this._config.bufferSize)return Zn.debug("Exceeded analytics events buffer size"),e.handlers.reject(new Error("Exceeded the size of analytics events buffer"));var n=((t={})[e.params.event.eventId]=e,t);this._buffer.push(n)},e.prototype.pause=function(){this._pause=!0},e.prototype.resume=function(){this._pause=!1},e.prototype.updateClient=function(e){this._client=e},e.prototype.flush=function(){this._flush=!0},e.prototype._startLoop=function(){this._interval&&clearInterval(this._interval);var e=this._config.flushInterval;this._interval=setInterval(this._sendBatch,e)},e.prototype._sendBatch=function(){var e=this._buffer.length;if(this._flush&&!e&&clearInterval(this._interval),!this._pause&&e){var t=this._config.flushSize,n=Math.min(t,e),r=this._buffer.splice(0,n);this._putEvents(r)}},e.prototype._putEvents=function(e){return Wn(this,void 0,void 0,(function(){var t,n,r,i,o;return $n(this,(function(s){switch(s.label){case 0:t=this._bufferToMap(e),n=this._generateBatchEventParams(t),s.label=1;case 1:return s.trys.push([1,3,,4]),r=new wn(n),[4,this._client.send(r)];case 2:return i=s.sent(),this._processPutEventsSuccessResponse(i,t),[3,4];case 3:return o=s.sent(),[2,this._handlePutEventsFailure(o,t)];case 4:return[2]}}))}))},e.prototype._generateBatchEventParams=function(e){var t={ApplicationId:"",EventsRequest:{BatchItem:{}}};return Object.values(e).forEach((function(e){var n=e.params,r=n.event,i=n.timestamp,o=n.config,s=r.name,a=r.attributes,u=r.metrics,c=r.eventId,f=r.session,l=o.appId,d=o.endpointId,h=t.EventsRequest.BatchItem;t.ApplicationId=t.ApplicationId||l,h[d]||(h[d]={Endpoint:{},Events:{}}),h[d].Events[c]={EventType:s,Timestamp:new Date(i).toISOString(),Attributes:a,Metrics:u,Session:f}})),t},e.prototype._handlePutEventsFailure=function(e,t){Zn.debug("_putEvents Failed: ",e);var n=e.$metadata&&e.$metadata.httpStatusCode;if(Xn.includes(n)){var r=Object.values(t);this._retry(r)}else;},e.prototype._processPutEventsSuccessResponse=function(e,t){var n=e.EventsResponse.Results,r=[];Object.entries(n).forEach((function(e){var n=Yn(e,2),i=n[0],o=n[1].EventsItemResponse;Object.entries(o).forEach((function(e){var n,o,s=Yn(e,2),a=s[0],u=s[1],c=u.StatusCode,f=u.Message,l=t[a],d={EventsResponse:{Results:(n={},n[i]={EventsItemResponse:(o={},o[a]={StatusCode:c,Message:f},o)},n)}};if(Qn.includes(c))l.handlers.resolve(d);else{if(!Xn.includes(c)){var h=l.params.event.name;return Zn.error("event "+a+" : "+h+" failed with error: "+f),l.handlers.reject(d)}r.push(l)}}))})),r.length&&this._retry(r)},e.prototype._retry=function(e){var t,n=[];e.forEach((function(e){var t,r=e.params,i=r.event,o=i.eventId,s=i.name;if(r.resendLimit-- >0)return Zn.debug("resending event "+o+" : "+s+" with "+r.resendLimit+" retry attempts remaining"),void n.push((t={},t[o]=e,t));Zn.debug("no retry attempts remaining for event "+o+" : "+s)})),(t=this._buffer).unshift.apply(t,Jn(n))},e.prototype._bufferToMap=function(e){return e.reduce((function(e,t){var n=Yn(Object.entries(t),1),r=Yn(n[0],2),i=r[0],o=r[1];return e[i]=o,e}),{})},e}();function tr(e){return(tr="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var nr=function(){return(nr=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},rr=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},ir=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},or=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]])}return n},sr="undefined"!=typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("amplify_default"):"@@amplify_default",ar=function(e,t){r.a.dispatch("analytics",{event:e,data:t},"Analytics",sr)},ur=new i.a("AWSPinpointProvider"),cr=[429,500],fr=[202],lr="undefined"!=typeof navigator&&navigator&&"function"==typeof navigator.sendBeacon,dr=function(){function e(e){this._endpointGenerating=!0,this._endpointUpdateInProgress=!1,this._buffer=null,this._endpointBuffer=[],this._config=e||{},this._config.bufferSize=this._config.bufferSize||1e3,this._config.flushSize=this._config.flushSize||100,this._config.flushInterval=this._config.flushInterval||5e3,this._config.resendLimit=this._config.resendLimit||5,this._clientInfo=o.a.clientInfo()}return e.prototype.getCategory=function(){return e.category},e.prototype.getProviderName=function(){return e.providerName},e.prototype.configure=function(e){var t=this;ur.debug("configure Analytics",e);var n=e||{};if(this._config=Object.assign({},this._config,n),this._endpointGenerating=!!e.autoSessionRecord,this._config.appId&&!this._config.disabled)if(this._config.endpointId)ar("pinpointProvider_configured",null);else{var r=this.getProviderName()+"_"+this._config.appId;this._getEndpointId(r).then((function(e){ur.debug("setting endpoint id from the cache",e),t._config.endpointId=e,ar("pinpointProvider_configured",null)})).catch((function(e){ur.debug("Failed to generate endpointId",e)}))}else this._flushBuffer();return this._config},e.prototype.record=function(e,t){return rr(this,void 0,void 0,(function(){var n,r;return ir(this,(function(i){switch(i.label){case 0:return ur.debug("_public record",e),[4,this._getCredentials()];case 1:return(n=i.sent())&&this._config.appId&&this._config.region?(this._initClients(n),r=(new Date).getTime(),this._generateSession(e),e.event.eventId=Object(Gn.v1)(),Object.assign(e,{timestamp:r,config:this._config}),e.event.immediate?[2,this._send(e,t)]:(this._putToBuffer(e,t),[2])):(ur.debug("cannot send events without credentials, applicationId or region"),[2,t.reject(new Error("No credentials, applicationId or region"))])}}))}))},e.prototype._sendEndpointUpdate=function(e){return rr(this,void 0,void 0,(function(){var t;return ir(this,(function(n){switch(n.label){case 0:return this._endpointUpdateInProgress?(this._endpointBuffer.push(e),[2]):(this._endpointUpdateInProgress=!0,[4,this._updateEndpoint(e)]);case 1:return n.sent(),t=this._endpointBuffer.shift(),this._endpointUpdateInProgress=!1,t&&this._sendEndpointUpdate(t),[2]}}))}))},e.prototype._putToBuffer=function(e,t){"_update_endpoint"!==e.event.name?this._buffer&&this._buffer.push({params:e,handlers:t}):this._sendEndpointUpdate({params:e,handlers:t})},e.prototype._generateSession=function(e){this._sessionId=this._sessionId||Object(Gn.v1)();var t=e.event;switch(t.name){case"_session.start":this._sessionStartTimestamp=(new Date).getTime(),this._sessionId=Object(Gn.v1)(),t.session={Id:this._sessionId,StartTimestamp:new Date(this._sessionStartTimestamp).toISOString()};break;case"_session.stop":var n=(new Date).getTime();this._sessionStartTimestamp=this._sessionStartTimestamp||(new Date).getTime(),this._sessionId=this._sessionId||Object(Gn.v1)(),t.session={Id:this._sessionId,Duration:n-this._sessionStartTimestamp,StartTimestamp:new Date(this._sessionStartTimestamp).toISOString(),StopTimestamp:new Date(n).toISOString()},this._sessionId=void 0,this._sessionStartTimestamp=void 0;break;default:this._sessionStartTimestamp=this._sessionStartTimestamp||(new Date).getTime(),this._sessionId=this._sessionId||Object(Gn.v1)(),t.session={Id:this._sessionId,StartTimestamp:new Date(this._sessionStartTimestamp).toISOString()}}},e.prototype._send=function(e,t){return rr(this,void 0,void 0,(function(){return ir(this,(function(n){switch(e.event.name){case"_update_endpoint":return[2,this._updateEndpoint({params:e,handlers:t})];case"_session.stop":return[2,this._pinpointSendStopSession(e,t)];default:return[2,this._pinpointPutEvents(e,t)]}return[2]}))}))},e.prototype._generateBatchItemContext=function(e){var t,n=e.event,r=e.timestamp,i=e.config,o=n.name,s=n.attributes,a=n.metrics,u=n.eventId,c=n.session,f=i.appId,l=i.endpointId,d={ApplicationId:f,EventsRequest:{BatchItem:{}}},h={Endpoint:{}};return h.Events=((t={})[u]={EventType:o,Timestamp:new Date(r).toISOString(),Attributes:s,Metrics:a,Session:c},t),d.EventsRequest.BatchItem[l]=h,d},e.prototype._pinpointPutEvents=function(e,t){return rr(this,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,l,d;return ir(this,(function(h){switch(h.label){case 0:n=e.event.eventId,r=e.config.endpointId,i=this._generateBatchItemContext(e),o=new wn(i),h.label=1;case 1:return h.trys.push([1,3,,4]),[4,this.pinpointClient.send(o)];case 2:return s=h.sent(),a=r,u=n,c=s.EventsResponse.Results[a].EventsItemResponse[u],f=c.StatusCode,l=c.Message,fr.includes(f)?(ur.debug("record event success. ",s),[2,t.resolve(s)]):cr.includes(f)?(this._retry(e,t),[3,4]):(ur.error("Event "+n+" is not accepted, the error is "+l),[2,t.reject(s)]);case 3:return d=h.sent(),this._eventError(d),[2,t.reject(d)];case 4:return[2]}}))}))},e.prototype._pinpointSendStopSession=function(e,t){if(lr){var n=this._generateBatchItemContext(e),r=this._config.region,i=n.ApplicationId,o=n.EventsRequest,a={secret_key:this._config.credentials.secretAccessKey,access_key:this._config.credentials.accessKeyId,session_token:this._config.credentials.sessionToken},u="https://pinpoint."+r+".amazonaws.com/v1/apps/"+i+"/events/legacy",c=JSON.stringify(o),f={url:u,body:c,method:"POST"},l={region:r,service:"mobiletargeting"},d=s.a.signUrl(f,a,l,null);return navigator.sendBeacon(d,c)?t.resolve("sendBeacon success"):t.reject("sendBeacon failure")}this._pinpointPutEvents(e,t)},e.prototype._retry=function(e,t){var n=e.config.resendLimit;e.resendLimit="number"==typeof e.resendLimit?e.resendLimit:n,e.resendLimit-- >0?(ur.debug("resending event "+e.eventName+" with "+e.resendLimit+" retry times left"),this._pinpointPutEvents(e,t)):ur.debug("retry times used up for event "+e.eventName)},e.prototype._updateEndpoint=function(e){return rr(this,void 0,void 0,(function(){var t,n,r,i,o,s,u,c,f,l,d,h;return ir(this,(function(p){switch(p.label){case 0:t=e.params,n=e.handlers,r=t.config,i=t.event,o=r.appId,s=r.endpointId,u=this._endpointRequest(r,a.a.transferKeyToLowerCase(i,[],["attributes","userAttributes","Attributes","UserAttributes"])),c={ApplicationId:o,EndpointId:s,EndpointRequest:u},p.label=1;case 1:return p.trys.push([1,3,,4]),f=new _n(c),[4,this.pinpointClient.send(f)];case 2:return l=p.sent(),ur.debug("updateEndpoint success",l),this._endpointGenerating=!1,this._resumeBuffer(),n.resolve(l),[2];case 3:return d=p.sent(),h={err:d,update_params:c,endpointObject:e},[2,this._handleEndpointUpdateFailure(h)];case 4:return[2]}}))}))},e.prototype._handleEndpointUpdateFailure=function(e){return rr(this,void 0,void 0,(function(){var t,n,r;return ir(this,(function(i){switch(t=e.err,n=e.endpointObject,r=t.$metadata&&t.$metadata.httpStatusCode,ur.debug("updateEndpoint error",t),r){case 403:return[2,this._handleEndpointUpdateForbidden(e)];default:if(cr.includes(r))return!0,[2,this._retryEndpointUpdate(n,!0)];ur.error("updateEndpoint failed",t),n.handlers.reject(t)}return[2]}))}))},e.prototype._handleEndpointUpdateForbidden=function(e){var t=e.err,n=e.endpointObject,r=t.code,i=t.retryable;if("ExpiredTokenException"!==r&&!i)return n.handlers.reject(t);this._retryEndpointUpdate(n)},e.prototype._retryEndpointUpdate=function(e,t){void 0===t&&(t=!1),ur.debug("_retryEndpointUpdate",e);var n=e.params,r=n.config.resendLimit;if(n.resendLimit="number"==typeof n.resendLimit?n.resendLimit:r,n.resendLimit-- >0)return ur.debug("resending endpoint update "+n.event.eventId+" with "+n.resendLimit+" retry attempts remaining"),void(this._endpointBuffer.length?this._endpointBuffer.unshift(e):this._updateEndpoint(e));ur.warn("resending endpoint update "+n.event.eventId+" failed after "+n.config.resendLimit+" attempts"),this._endpointGenerating&&ur.error("Initial endpoint update failed. ")},e.prototype._initClients=function(e){return rr(this,void 0,void 0,(function(){var t,n;return ir(this,(function(r){return ur.debug("init clients"),this.pinpointClient&&this._config.credentials&&this._config.credentials.sessionToken===e.sessionToken&&this._config.credentials.identityId===e.identityId?(ur.debug("no change for aws credentials, directly return from init"),[2]):(t=this._config.credentials?this._config.credentials.identityId:null,this._config.credentials=e,n=this._config.region,ur.debug("init clients with credentials",e),this.pinpointClient=new Hn({region:n,credentials:e,customUserAgent:Object(u.b)()}),this.pinpointClient.middlewareStack.addRelativeTo((function(e){return function(t){return delete t.request.headers["amz-sdk-invocation-id"],delete t.request.headers["amz-sdk-request"],e(t)}}),{step:"finalizeRequest",relation:"after",toMiddleware:"retryMiddleware"}),this._bufferExists()&&t===e.identityId?this._updateBufferClient():this._initBuffer(),this._customizePinpointClientReq(),[2])}))}))},e.prototype._bufferExists=function(){return this._buffer&&this._buffer instanceof er},e.prototype._initBuffer=function(){this._bufferExists()&&this._flushBuffer(),this._buffer=new er(this.pinpointClient,this._config),this._endpointGenerating&&this._buffer.pause()},e.prototype._updateBufferClient=function(){this._bufferExists()&&this._buffer.updateClient(this.pinpointClient)},e.prototype._flushBuffer=function(){this._bufferExists()&&(this._buffer.flush(),this._buffer=null)},e.prototype._resumeBuffer=function(){this._bufferExists()&&this._buffer.resume()},e.prototype._customizePinpointClientReq=function(){},e.prototype._getEndpointId=function(e){return rr(this,void 0,void 0,(function(){var t;return ir(this,(function(n){switch(n.label){case 0:return[4,Vn.a.getItem(e)];case 1:return t=n.sent(),ur.debug("endpointId from cache",t,"type",tr(t)),t||(t=Object(Gn.v1)(),Vn.a.setItem(e,t)),[2,t]}}))}))},e.prototype._endpointRequest=function(e,t){var n=e.credentials,r=this._clientInfo||{},i=e.clientContext||{},o=e.endpoint||{},s={appVersion:r.appVersion,make:r.make,model:r.model,modelVersion:r.version,platform:r.platform},u=(i.clientId,i.appTitle,i.appVersionName,i.appVersionCode,i.appPackageName,or(i,["clientId","appTitle","appVersionName","appVersionCode","appPackageName"])),c=t.address?"android"===r.platform?"GCM":"APNS":void 0,f=nr(nr(nr({channelType:c,requestId:Object(Gn.v1)(),effectiveDate:(new Date).toISOString()},o),t),{attributes:nr(nr({},o.attributes),t.attributes),demographic:nr(nr(nr(nr({},s),u),o.demographic),t.demographic),location:nr(nr({},o.location),t.location),metrics:nr(nr({},o.metrics),t.metrics),user:{userId:t.userId||o.userId||n.identityId,userAttributes:nr(nr({},o.userAttributes),t.userAttributes)}}),l=(f.userId,f.userAttributes,f.name,f.session,f.eventId,f.immediate,or(f,["userId","userAttributes","name","session","eventId","immediate"]));return a.a.transferKeyToUpperCase(l,[],["metrics","userAttributes","attributes"])},e.prototype._eventError=function(e){ur.error("record event failed.",e),ur.warn('Please ensure you have updated your Pinpoint IAM Policy with the Action: "mobiletargeting:PutEvents" in order to record events')},e.prototype._getCredentials=function(){return rr(this,void 0,void 0,(function(){var e,t;return ir(this,(function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,c.a.get()];case 1:return(e=n.sent())?(ur.debug("set credentials for analytics",e),[2,c.a.shear(e)]):[2,null];case 2:return t=n.sent(),ur.debug("ensure credentials error",t),[2,null];case 3:return[2]}}))}))},e.category="Analytics",e.providerName="AWSPinpoint",e}()},function(e,t,n){"use strict";n.d(t,"a",(function(){return Ht}));var r=n(44),i=n(50),o=n(89),s=function(e,t){return(s=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}s(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var u=function(){return(u=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};function c(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))}function f(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}}Object.create;var l,d,h,p,v,g,m,b,y,w,_,S,E,M,A,I,k,O,x,C,T,P,N,R,L,j,D,U,B,F,z,q,K,H,V,G,W,$,Y,J,Z,X,Q,ee,te,ne,re,ie,oe,se,ae,ue,ce,fe,le,de,he,pe,ve,ge,me,be,ye,we,_e,Se,Ee,Me,Ae,Ie,ke,Oe,xe,Ce,Te,Pe,Ne,Re,Le,je,De,Ue;Object.create;(l||(l={})).filterSensitiveLog=function(e){return u({},e)},(d||(d={})).filterSensitiveLog=function(e){return u({},e)},(h||(h={})).filterSensitiveLog=function(e){return u({},e)},(p||(p={})).filterSensitiveLog=function(e){return u({},e)},(v||(v={})).filterSensitiveLog=function(e){return u({},e)},(g||(g={})).filterSensitiveLog=function(e){return u({},e)},(m||(m={})).filterSensitiveLog=function(e){return u({},e)},function(e){e.ACTIVE="ACTIVE",e.CREATING="CREATING",e.DELETING="DELETING"}(b||(b={})),(y||(y={})).filterSensitiveLog=function(e){return u({},e)},(w||(w={})).filterSensitiveLog=function(e){return u({},e)},(_||(_={})).filterSensitiveLog=function(e){return u({},e)},(S||(S={})).filterSensitiveLog=function(e){return u({},e)},(E||(E={})).filterSensitiveLog=function(e){return u({},e)},(M||(M={})).filterSensitiveLog=function(e){return u({},e)},(A||(A={})).filterSensitiveLog=function(e){return u({},e)},(I||(I={})).filterSensitiveLog=function(e){return u({},e)},(k||(k={})).filterSensitiveLog=function(e){return u({},e)},function(e){e.KMS="KMS",e.NONE="NONE"}(O||(O={})),function(e){e.ALL="ALL",e.INCOMING_BYTES="IncomingBytes",e.INCOMING_RECORDS="IncomingRecords",e.ITERATOR_AGE_MILLISECONDS="IteratorAgeMilliseconds",e.OUTGOING_BYTES="OutgoingBytes",e.OUTGOING_RECORDS="OutgoingRecords",e.READ_PROVISIONED_THROUGHPUT_EXCEEDED="ReadProvisionedThroughputExceeded",e.WRITE_PROVISIONED_THROUGHPUT_EXCEEDED="WriteProvisionedThroughputExceeded"}(x||(x={})),(C||(C={})).filterSensitiveLog=function(e){return u({},e)},(T||(T={})).filterSensitiveLog=function(e){return u({},e)},(P||(P={})).filterSensitiveLog=function(e){return u({},e)},function(e){e.ACTIVE="ACTIVE",e.CREATING="CREATING",e.DELETING="DELETING",e.UPDATING="UPDATING"}(N||(N={})),(R||(R={})).filterSensitiveLog=function(e){return u({},e)},(L||(L={})).filterSensitiveLog=function(e){return u({},e)},(j||(j={})).filterSensitiveLog=function(e){return u({},e)},(D||(D={})).filterSensitiveLog=function(e){return u({},e)},(U||(U={})).filterSensitiveLog=function(e){return u({},e)},(B||(B={})).filterSensitiveLog=function(e){return u({},e)},(F||(F={})).filterSensitiveLog=function(e){return u({},e)},(z||(z={})).filterSensitiveLog=function(e){return u({},e)},(q||(q={})).filterSensitiveLog=function(e){return u({},e)},(K||(K={})).filterSensitiveLog=function(e){return u({},e)},(H||(H={})).filterSensitiveLog=function(e){return u({},e)},(V||(V={})).filterSensitiveLog=function(e){return u({},e)},(G||(G={})).filterSensitiveLog=function(e){return u({},e)},(W||(W={})).filterSensitiveLog=function(e){return u({},e)},($||($={})).filterSensitiveLog=function(e){return u({},e)},(Y||(Y={})).filterSensitiveLog=function(e){return u({},e)},(J||(J={})).filterSensitiveLog=function(e){return u({},e)},(Z||(Z={})).filterSensitiveLog=function(e){return u({},e)},(X||(X={})).filterSensitiveLog=function(e){return u({},e)},(Q||(Q={})).filterSensitiveLog=function(e){return u({},e)},(ee||(ee={})).filterSensitiveLog=function(e){return u({},e)},(te||(te={})).filterSensitiveLog=function(e){return u({},e)},function(e){e.AFTER_SEQUENCE_NUMBER="AFTER_SEQUENCE_NUMBER",e.AT_SEQUENCE_NUMBER="AT_SEQUENCE_NUMBER",e.AT_TIMESTAMP="AT_TIMESTAMP",e.LATEST="LATEST",e.TRIM_HORIZON="TRIM_HORIZON"}(ne||(ne={})),(re||(re={})).filterSensitiveLog=function(e){return u({},e)},(ie||(ie={})).filterSensitiveLog=function(e){return u({},e)},(oe||(oe={})).filterSensitiveLog=function(e){return u({},e)},(se||(se={})).filterSensitiveLog=function(e){return u({},e)},function(e){e.AFTER_SHARD_ID="AFTER_SHARD_ID",e.AT_LATEST="AT_LATEST",e.AT_TIMESTAMP="AT_TIMESTAMP",e.AT_TRIM_HORIZON="AT_TRIM_HORIZON",e.FROM_TIMESTAMP="FROM_TIMESTAMP",e.FROM_TRIM_HORIZON="FROM_TRIM_HORIZON"}(ae||(ae={})),(ue||(ue={})).filterSensitiveLog=function(e){return u({},e)},(ce||(ce={})).filterSensitiveLog=function(e){return u({},e)},(fe||(fe={})).filterSensitiveLog=function(e){return u({},e)},(le||(le={})).filterSensitiveLog=function(e){return u({},e)},(de||(de={})).filterSensitiveLog=function(e){return u({},e)},(he||(he={})).filterSensitiveLog=function(e){return u({},e)},(pe||(pe={})).filterSensitiveLog=function(e){return u({},e)},(ve||(ve={})).filterSensitiveLog=function(e){return u({},e)},(ge||(ge={})).filterSensitiveLog=function(e){return u({},e)},(me||(me={})).filterSensitiveLog=function(e){return u({},e)},(be||(be={})).filterSensitiveLog=function(e){return u({},e)},(ye||(ye={})).filterSensitiveLog=function(e){return u({},e)},(we||(we={})).filterSensitiveLog=function(e){return u({},e)},(_e||(_e={})).filterSensitiveLog=function(e){return u({},e)},(Se||(Se={})).filterSensitiveLog=function(e){return u({},e)},(Ee||(Ee={})).filterSensitiveLog=function(e){return u({},e)},(Me||(Me={})).filterSensitiveLog=function(e){return u({},e)},(Ae||(Ae={})).filterSensitiveLog=function(e){return u({},e)},(Ie||(Ie={})).filterSensitiveLog=function(e){return u({},e)},(ke||(ke={})).filterSensitiveLog=function(e){return u({},e)},(Oe||(Oe={})).filterSensitiveLog=function(e){return u({},e)},(xe||(xe={})).filterSensitiveLog=function(e){return u({},e)},(Ce||(Ce={})).filterSensitiveLog=function(e){return u({},e)},(Te||(Te={})).filterSensitiveLog=function(e){return u({},e)},(Pe||(Pe={})).filterSensitiveLog=function(e){return u({},e)},(Ne||(Ne={})).filterSensitiveLog=function(e){return u({},e)},function(e){e.visit=function(e,t){return void 0!==e.KMSThrottlingException?t.KMSThrottlingException(e.KMSThrottlingException):void 0!==e.InternalFailureException?t.InternalFailureException(e.InternalFailureException):void 0!==e.ResourceInUseException?t.ResourceInUseException(e.ResourceInUseException):void 0!==e.KMSOptInRequired?t.KMSOptInRequired(e.KMSOptInRequired):void 0!==e.KMSDisabledException?t.KMSDisabledException(e.KMSDisabledException):void 0!==e.KMSAccessDeniedException?t.KMSAccessDeniedException(e.KMSAccessDeniedException):void 0!==e.KMSInvalidStateException?t.KMSInvalidStateException(e.KMSInvalidStateException):void 0!==e.KMSNotFoundException?t.KMSNotFoundException(e.KMSNotFoundException):void 0!==e.ResourceNotFoundException?t.ResourceNotFoundException(e.ResourceNotFoundException):void 0!==e.SubscribeToShardEvent?t.SubscribeToShardEvent(e.SubscribeToShardEvent):t._(e.$unknown[0],e.$unknown[1])},e.filterSensitiveLog=function(e){var t;return void 0!==e.KMSThrottlingException?{KMSThrottlingException:ee.filterSensitiveLog(e.KMSThrottlingException)}:void 0!==e.InternalFailureException?{InternalFailureException:se.filterSensitiveLog(e.InternalFailureException)}:void 0!==e.ResourceInUseException?{ResourceInUseException:p.filterSensitiveLog(e.ResourceInUseException)}:void 0!==e.KMSOptInRequired?{KMSOptInRequired:Q.filterSensitiveLog(e.KMSOptInRequired)}:void 0!==e.KMSDisabledException?{KMSDisabledException:J.filterSensitiveLog(e.KMSDisabledException)}:void 0!==e.KMSAccessDeniedException?{KMSAccessDeniedException:Y.filterSensitiveLog(e.KMSAccessDeniedException)}:void 0!==e.KMSInvalidStateException?{KMSInvalidStateException:Z.filterSensitiveLog(e.KMSInvalidStateException)}:void 0!==e.KMSNotFoundException?{KMSNotFoundException:X.filterSensitiveLog(e.KMSNotFoundException)}:void 0!==e.ResourceNotFoundException?{ResourceNotFoundException:v.filterSensitiveLog(e.ResourceNotFoundException)}:void 0!==e.SubscribeToShardEvent?{SubscribeToShardEvent:Ne.filterSensitiveLog(e.SubscribeToShardEvent)}:void 0!==e.$unknown?((t={})[e.$unknown[0]]="UNKNOWN",t):void 0}}(Re||(Re={})),(Le||(Le={})).filterSensitiveLog=function(e){return u(u({},e),e.EventStream&&{EventStream:"STREAMING_CONTENT"})},function(e){e.UNIFORM_SCALING="UNIFORM_SCALING"}(je||(je={})),(De||(De={})).filterSensitiveLog=function(e){return u({},e)},(Ue||(Ue={})).filterSensitiveLog=function(e){return u({},e)};var Be=n(2),Fe=function(e,t){return c(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,c,l,d,h,p,v,g,m,b,y,w;return f(this,(function(f){switch(f.label){case 0:return r=[u({},e)],w={},[4,dt(e.body,t)];case 1:switch(n=u.apply(void 0,r.concat([(w.body=f.sent(),w)])),o="UnknownError",s=n.body.__type.split("#"),o=void 0===s[1]?s[0]:s[1],o){case"InvalidArgumentException":case"com.amazonaws.kinesis#InvalidArgumentException":return[3,2];case"KMSAccessDeniedException":case"com.amazonaws.kinesis#KMSAccessDeniedException":return[3,4];case"KMSDisabledException":case"com.amazonaws.kinesis#KMSDisabledException":return[3,6];case"KMSInvalidStateException":case"com.amazonaws.kinesis#KMSInvalidStateException":return[3,8];case"KMSNotFoundException":case"com.amazonaws.kinesis#KMSNotFoundException":return[3,10];case"KMSOptInRequired":case"com.amazonaws.kinesis#KMSOptInRequired":return[3,12];case"KMSThrottlingException":case"com.amazonaws.kinesis#KMSThrottlingException":return[3,14];case"ProvisionedThroughputExceededException":case"com.amazonaws.kinesis#ProvisionedThroughputExceededException":return[3,16];case"ResourceNotFoundException":case"com.amazonaws.kinesis#ResourceNotFoundException":return[3,18]}return[3,20];case 2:return a=[{}],[4,ze(n,t)];case 3:return i=u.apply(void 0,[u.apply(void 0,a.concat([f.sent()])),{name:o,$metadata:ct(e)}]),[3,21];case 4:return c=[{}],[4,qe(n,t)];case 5:return i=u.apply(void 0,[u.apply(void 0,c.concat([f.sent()])),{name:o,$metadata:ct(e)}]),[3,21];case 6:return l=[{}],[4,Ke(n,t)];case 7:return i=u.apply(void 0,[u.apply(void 0,l.concat([f.sent()])),{name:o,$metadata:ct(e)}]),[3,21];case 8:return d=[{}],[4,He(n,t)];case 9:return i=u.apply(void 0,[u.apply(void 0,d.concat([f.sent()])),{name:o,$metadata:ct(e)}]),[3,21];case 10:return h=[{}],[4,Ve(n,t)];case 11:return i=u.apply(void 0,[u.apply(void 0,h.concat([f.sent()])),{name:o,$metadata:ct(e)}]),[3,21];case 12:return p=[{}],[4,Ge(n,t)];case 13:return i=u.apply(void 0,[u.apply(void 0,p.concat([f.sent()])),{name:o,$metadata:ct(e)}]),[3,21];case 14:return v=[{}],[4,We(n,t)];case 15:return i=u.apply(void 0,[u.apply(void 0,v.concat([f.sent()])),{name:o,$metadata:ct(e)}]),[3,21];case 16:return g=[{}],[4,$e(n,t)];case 17:return i=u.apply(void 0,[u.apply(void 0,g.concat([f.sent()])),{name:o,$metadata:ct(e)}]),[3,21];case 18:return m=[{}],[4,Ye(n,t)];case 19:return i=u.apply(void 0,[u.apply(void 0,m.concat([f.sent()])),{name:o,$metadata:ct(e)}]),[3,21];case 20:b=n.body,o=b.code||b.Code||o,i=u(u({},b),{name:""+o,message:b.message||b.Message||o,$fault:"client",$metadata:ct(e)}),f.label=21;case 21:return y=i.message||i.Message||o,i.message=y,delete i.Message,[2,Promise.reject(Object.assign(new Error(y),i))]}}))}))},ze=function(e,t){return c(void 0,void 0,void 0,(function(){var n,r;return f(this,(function(i){return n=e.body,r=Xe(n,t),[2,u({name:"InvalidArgumentException",$fault:"client",$metadata:ct(e)},r)]}))}))},qe=function(e,t){return c(void 0,void 0,void 0,(function(){var n,r;return f(this,(function(i){return n=e.body,r=Qe(n,t),[2,u({name:"KMSAccessDeniedException",$fault:"client",$metadata:ct(e)},r)]}))}))},Ke=function(e,t){return c(void 0,void 0,void 0,(function(){var n,r;return f(this,(function(i){return n=e.body,r=et(n,t),[2,u({name:"KMSDisabledException",$fault:"client",$metadata:ct(e)},r)]}))}))},He=function(e,t){return c(void 0,void 0,void 0,(function(){var n,r;return f(this,(function(i){return n=e.body,r=tt(n,t),[2,u({name:"KMSInvalidStateException",$fault:"client",$metadata:ct(e)},r)]}))}))},Ve=function(e,t){return c(void 0,void 0,void 0,(function(){var n,r;return f(this,(function(i){return n=e.body,r=nt(n,t),[2,u({name:"KMSNotFoundException",$fault:"client",$metadata:ct(e)},r)]}))}))},Ge=function(e,t){return c(void 0,void 0,void 0,(function(){var n,r;return f(this,(function(i){return n=e.body,r=rt(n,t),[2,u({name:"KMSOptInRequired",$fault:"client",$metadata:ct(e)},r)]}))}))},We=function(e,t){return c(void 0,void 0,void 0,(function(){var n,r;return f(this,(function(i){return n=e.body,r=it(n,t),[2,u({name:"KMSThrottlingException",$fault:"client",$metadata:ct(e)},r)]}))}))},$e=function(e,t){return c(void 0,void 0,void 0,(function(){var n,r;return f(this,(function(i){return n=e.body,r=ot(n,t),[2,u({name:"ProvisionedThroughputExceededException",$fault:"client",$metadata:ct(e)},r)]}))}))},Ye=function(e,t){return c(void 0,void 0,void 0,(function(){var n,r;return f(this,(function(i){return n=e.body,r=ut(n,t),[2,u({name:"ResourceNotFoundException",$fault:"client",$metadata:ct(e)},r)]}))}))},Je=function(e,t){return u(u({},void 0!==e.Records&&{Records:Ze(e.Records,t)}),void 0!==e.StreamName&&{StreamName:e.StreamName})},Ze=function(e,t){return e.map((function(e){return function(e,t){return u(u(u({},void 0!==e.Data&&{Data:t.base64Encoder(e.Data)}),void 0!==e.ExplicitHashKey&&{ExplicitHashKey:e.ExplicitHashKey}),void 0!==e.PartitionKey&&{PartitionKey:e.PartitionKey})}(e,t)}))},Xe=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},Qe=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},et=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},tt=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},nt=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},rt=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},it=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},ot=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},st=function(e,t){return{EncryptionType:void 0!==e.EncryptionType&&null!==e.EncryptionType?e.EncryptionType:void 0,FailedRecordCount:void 0!==e.FailedRecordCount&&null!==e.FailedRecordCount?e.FailedRecordCount:void 0,Records:void 0!==e.Records&&null!==e.Records?at(e.Records,t):void 0}},at=function(e,t){return(e||[]).map((function(e){return function(e,t){return{ErrorCode:void 0!==e.ErrorCode&&null!==e.ErrorCode?e.ErrorCode:void 0,ErrorMessage:void 0!==e.ErrorMessage&&null!==e.ErrorMessage?e.ErrorMessage:void 0,SequenceNumber:void 0!==e.SequenceNumber&&null!==e.SequenceNumber?e.SequenceNumber:void 0,ShardId:void 0!==e.ShardId&&null!==e.ShardId?e.ShardId:void 0}}(e)}))},ut=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},ct=function(e){return{httpStatusCode:e.statusCode,httpHeaders:e.headers,requestId:e.headers["x-amzn-requestid"]}},ft=function(e,t){return void 0===e&&(e=new Uint8Array),e instanceof Uint8Array?Promise.resolve(e):t.streamCollector(e)||Promise.resolve(new Uint8Array)},lt=function(e,t,n,r,i){return c(void 0,void 0,void 0,(function(){var o,s,a,u,c,l;return f(this,(function(f){switch(f.label){case 0:return[4,e.endpoint()];case 1:return o=f.sent(),s=o.hostname,a=o.protocol,u=void 0===a?"https":a,c=o.port,l={protocol:u,hostname:s,port:c,method:"POST",path:n,headers:t},void 0!==r&&(l.hostname=r),void 0!==i&&(l.body=i),[2,new Be.a(l)]}}))}))},dt=function(e,t){return function(e,t){return ft(e,t).then((function(e){return t.utf8Encoder(e)}))}(e,t).then((function(e){return e.length?JSON.parse(e):{}}))},ht=n(10),pt=n(0),vt=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return a(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(ht.a)(t,this.serialize,this.deserialize));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"KinesisClient",commandName:"PutRecordsCommand",inputFilterSensitiveLog:Se.filterSensitiveLog,outputFilterSensitiveLog:Me.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"KinesisClient",commandName:"PutRecordsCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return c(void 0,void 0,void 0,(function(){var n,r;return f(this,(function(i){return n={"Content-Type":"application/x-amz-json-1.1","X-Amz-Target":"Kinesis_20131202.PutRecords"},r=JSON.stringify(Je(e,t)),[2,lt(t,n,"/",void 0,r)]}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return c(void 0,void 0,void 0,(function(){var n,r,i;return f(this,(function(o){switch(o.label){case 0:return e.statusCode>=300?[2,Fe(e,t)]:[4,dt(e.body,t)];case 1:return n=o.sent(),{},r=st(n,t),i=u({$metadata:ct(e)},r),[2,Promise.resolve(i)]}}))}))}(e,t)},t}(pt.b),gt=n(150),mt=n(38),bt=n(110),yt=n(18),wt=n(24),_t=n(11),St=n(39),Et=n(17),Mt=n(40),At=n(41),It=n(15),kt=new Set(["ap-east-1","ap-northeast-1","ap-northeast-2","ap-south-1","ap-southeast-1","ap-southeast-2","ca-central-1","eu-central-1","eu-north-1","eu-west-1","eu-west-2","eu-west-3","me-south-1","sa-east-1","us-east-1","us-east-2","us-west-1","us-west-2"]),Ot=new Set(["cn-north-1","cn-northwest-1"]),xt=new Set(["us-iso-east-1"]),Ct=new Set(["us-isob-east-1"]),Tt=new Set(["us-gov-east-1","us-gov-west-1"]),Pt=u(u({},{apiVersion:"2013-12-02",disableHostPrefix:!1,logger:{},regionInfoProvider:function(e,t){var n=void 0;switch(e){case"ap-east-1":n={hostname:"kinesis.ap-east-1.amazonaws.com",partition:"aws"};break;case"ap-northeast-1":n={hostname:"kinesis.ap-northeast-1.amazonaws.com",partition:"aws"};break;case"ap-northeast-2":n={hostname:"kinesis.ap-northeast-2.amazonaws.com",partition:"aws"};break;case"ap-south-1":n={hostname:"kinesis.ap-south-1.amazonaws.com",partition:"aws"};break;case"ap-southeast-1":n={hostname:"kinesis.ap-southeast-1.amazonaws.com",partition:"aws"};break;case"ap-southeast-2":n={hostname:"kinesis.ap-southeast-2.amazonaws.com",partition:"aws"};break;case"ca-central-1":n={hostname:"kinesis.ca-central-1.amazonaws.com",partition:"aws"};break;case"cn-north-1":n={hostname:"kinesis.cn-north-1.amazonaws.com.cn",partition:"aws-cn"};break;case"cn-northwest-1":n={hostname:"kinesis.cn-northwest-1.amazonaws.com.cn",partition:"aws-cn"};break;case"eu-central-1":n={hostname:"kinesis.eu-central-1.amazonaws.com",partition:"aws"};break;case"eu-north-1":n={hostname:"kinesis.eu-north-1.amazonaws.com",partition:"aws"};break;case"eu-west-1":n={hostname:"kinesis.eu-west-1.amazonaws.com",partition:"aws"};break;case"eu-west-2":n={hostname:"kinesis.eu-west-2.amazonaws.com",partition:"aws"};break;case"eu-west-3":n={hostname:"kinesis.eu-west-3.amazonaws.com",partition:"aws"};break;case"me-south-1":n={hostname:"kinesis.me-south-1.amazonaws.com",partition:"aws"};break;case"sa-east-1":n={hostname:"kinesis.sa-east-1.amazonaws.com",partition:"aws"};break;case"us-east-1":n={hostname:"kinesis.us-east-1.amazonaws.com",partition:"aws"};break;case"us-east-2":n={hostname:"kinesis.us-east-2.amazonaws.com",partition:"aws"};break;case"us-gov-east-1":n={hostname:"kinesis.us-gov-east-1.amazonaws.com",partition:"aws-us-gov"};break;case"us-gov-west-1":n={hostname:"kinesis.us-gov-west-1.amazonaws.com",partition:"aws-us-gov"};break;case"us-iso-east-1":n={hostname:"kinesis.us-iso-east-1.c2s.ic.gov",partition:"aws-iso"};break;case"us-isob-east-1":n={hostname:"kinesis.us-isob-east-1.sc2s.sgov.gov",partition:"aws-iso-b"};break;case"us-west-1":n={hostname:"kinesis.us-west-1.amazonaws.com",partition:"aws"};break;case"us-west-2":n={hostname:"kinesis.us-west-2.amazonaws.com",partition:"aws"};break;default:kt.has(e)&&(n={hostname:"kinesis.{region}.amazonaws.com".replace("{region}",e),partition:"aws"}),Ot.has(e)&&(n={hostname:"kinesis.{region}.amazonaws.com.cn".replace("{region}",e),partition:"aws-cn"}),xt.has(e)&&(n={hostname:"kinesis.{region}.c2s.ic.gov".replace("{region}",e),partition:"aws-iso"}),Ct.has(e)&&(n={hostname:"kinesis.{region}.sc2s.sgov.gov".replace("{region}",e),partition:"aws-iso-b"}),Tt.has(e)&&(n={hostname:"kinesis.{region}.amazonaws.com".replace("{region}",e),partition:"aws-us-gov"}),void 0===n&&(n={hostname:"kinesis.{region}.amazonaws.com".replace("{region}",e),partition:"aws"})}return Promise.resolve(n)},signingName:"kinesis"}),{runtime:"browser",base64Decoder:Et.a,base64Encoder:Et.b,bodyLengthChecker:Mt.a,credentialDefaultProvider:Object(wt.a)("Credential is missing"),defaultUserAgent:Object(At.a)(gt.name,gt.version),eventStreamSerdeProvider:bt.a,maxAttempts:_t.a,region:Object(wt.a)("Region is missing"),requestHandler:new yt.a,sha256:mt.Sha256,streamCollector:yt.b,urlParser:St.a,utf8Decoder:It.a,utf8Encoder:It.b}),Nt=n(22),Rt=n(112),Lt=n(37),jt=n(21),Dt=n(43),Ut=n(25),Bt=n(23),Ft=function(e){function t(t){var n=this,r=u(u({},Pt),t),i=Object(Nt.b)(r),o=Object(Nt.a)(i),s=Object(Ut.b)(o),a=Object(_t.c)(s),c=Object(Bt.b)(a),f=Object(jt.b)(c),l=Object(Rt.a)(f);return(n=e.call(this,l)||this).config=l,n.middlewareStack.use(Object(Ut.a)(n.config)),n.middlewareStack.use(Object(_t.b)(n.config)),n.middlewareStack.use(Object(Bt.a)(n.config)),n.middlewareStack.use(Object(Lt.a)(n.config)),n.middlewareStack.use(Object(jt.a)(n.config)),n.middlewareStack.use(Object(Dt.a)(n.config)),n}return a(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this)},t}(pt.a),zt=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},qt=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},Kt=new r.a("AWSKinesisProvider"),Ht=function(){function e(e){this._buffer=[],this._config=e||{},this._config.bufferSize=this._config.bufferSize||1e3,this._config.flushSize=this._config.flushSize||100,this._config.flushInterval=this._config.flushInterval||5e3,this._config.resendLimit=this._config.resendLimit||5,this._setupTimer()}return e.prototype._setupTimer=function(){var e=this;this._timer&&clearInterval(this._timer);var t=this._config,n=t.flushSize,r=t.flushInterval;this._timer=setInterval((function(){for(var t=e._buffer.length<n?e._buffer.length:n,r=[],i=0;i<t;i+=1){var o=e._buffer.shift();r.push(o)}e._sendFromBuffer(r)}),r)},e.prototype.getCategory=function(){return"Analytics"},e.prototype.getProviderName=function(){return"AWSKinesis"},e.prototype.configure=function(e){Kt.debug("configure Analytics",e);var t=e||{};return this._config=Object.assign({},this._config,t),this._setupTimer(),this._config},e.prototype.record=function(e){return zt(this,void 0,void 0,(function(){var t;return qt(this,(function(n){switch(n.label){case 0:return[4,this._getCredentials()];case 1:return(t=n.sent())?(Object.assign(e,{config:this._config,credentials:t}),[2,this._putToBuffer(e)]):[2,Promise.resolve(!1)]}}))}))},e.prototype.updateEndpoint=function(){return Kt.debug("updateEndpoint is not implemented in Kinesis provider"),Promise.resolve(!0)},e.prototype._putToBuffer=function(e){return this._buffer.length<1e3?(this._buffer.push(e),Promise.resolve(!0)):(Kt.debug("exceed analytics events buffer size"),Promise.reject(!1))},e.prototype._sendFromBuffer=function(e){for(var t=this,n=[],r=null,i=[],o=0;o<e.length;o+=1){var s=e[o].credentials;0===o?(i.push(e[o]),r=s):s.sessionToken===r.sessionToken&&s.identityId===r.identityId?(Kt.debug("no change for cred, put event in the same group"),i.push(e[o])):(n.push(i),(i=[]).push(e[o]),r=s)}n.push(i),n.map((function(e){t._sendEvents(e)}))},e.prototype._sendEvents=function(e){var t=this;if(0!==e.length){var n=e[0],r=n.config,i=n.credentials;if(!this._init(r,i))return!1;var o={};e.map((function(e){var t=e.event,n=t.streamName;void 0===o[n]&&(o[n]=[]);var r=t.data&&"string"!=typeof t.data?JSON.stringify(t.data):t.data,s={Data:Object(It.a)(r),PartitionKey:t.partitionKey||"partition-"+i.identityId};o[n].push(s)})),Object.keys(o).map((function(e){return zt(t,void 0,void 0,(function(){var t,n;return qt(this,(function(r){switch(r.label){case 0:Kt.debug("putting records to kinesis with records",o[e]),r.label=1;case 1:return r.trys.push([1,3,,4]),t=new vt({Records:o[e],StreamName:e}),[4,this._kinesis.send(t)];case 2:return r.sent(),Kt.debug("Upload records to stream",e),[3,4];case 3:return n=r.sent(),Kt.debug("Failed to upload records to Kinesis",n),[3,4];case 4:return[2]}}))}))}))}},e.prototype._init=function(e,t){if(Kt.debug("init clients"),this._kinesis&&this._config.credentials&&this._config.credentials.sessionToken===t.sessionToken&&this._config.credentials.identityId===t.identityId)return Kt.debug("no change for analytics config, directly return from init"),!0;this._config.credentials=t;var n=e.region,r=e.endpoint;return this._initKinesis(n,r,t)},e.prototype._initKinesis=function(e,t,n){return Kt.debug("initialize kinesis with credentials",n),this._kinesis=new Ft({region:e,credentials:n,customUserAgent:Object(i.b)(),endpoint:t}),!0},e.prototype._getCredentials=function(){var e=this;return o.a.get().then((function(t){return t?(Kt.debug("set credentials for analytics",e._config.credentials),o.a.shear(t)):null})).catch((function(e){return Kt.debug("ensure credentials error",e),null}))},e}()},function(e,t,n){"use strict";n.d(t,"a",(function(){return c}));var r=n(76);function i(e,t){void 0===t&&(t={});var n=function(e){if(e&&"j"===e[0]&&":"===e[1])return e.substr(2);return e}(e);if(function(e,t){return void 0===t&&(t=!e||"{"!==e[0]&&"["!==e[0]&&'"'!==e[0]),!t}(n,t.doNotParse))try{return JSON.parse(n)}catch(e){}return e}var o=function(){return(o=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},s=function(){function e(e,t){var n=this;this.changeListeners=[],this.HAS_DOCUMENT_COOKIE=!1,this.cookies=function(e,t){return"string"==typeof e?r.parse(e,t):"object"==typeof e&&null!==e?e:{}}(e,t),new Promise((function(){n.HAS_DOCUMENT_COOKIE="object"==typeof document&&"string"==typeof document.cookie})).catch((function(){}))}return e.prototype._updateBrowserValues=function(e){this.HAS_DOCUMENT_COOKIE&&(this.cookies=r.parse(document.cookie,e))},e.prototype._emitChange=function(e){for(var t=0;t<this.changeListeners.length;++t)this.changeListeners[t](e)},e.prototype.get=function(e,t,n){return void 0===t&&(t={}),this._updateBrowserValues(n),i(this.cookies[e],t)},e.prototype.getAll=function(e,t){void 0===e&&(e={}),this._updateBrowserValues(t);var n={};for(var r in this.cookies)n[r]=i(this.cookies[r],e);return n},e.prototype.set=function(e,t,n){var i;"object"==typeof t&&(t=JSON.stringify(t)),this.cookies=o(o({},this.cookies),((i={})[e]=t,i)),this.HAS_DOCUMENT_COOKIE&&(document.cookie=r.serialize(e,t,n)),this._emitChange({name:e,value:t,options:n})},e.prototype.remove=function(e,t){var n=t=o(o({},t),{expires:new Date(1970,1,1,0,0,1),maxAge:0});this.cookies=o({},this.cookies),delete this.cookies[e],this.HAS_DOCUMENT_COOKIE&&(document.cookie=r.serialize(e,"",n)),this._emitChange({name:e,value:void 0,options:t})},e.prototype.addChangeListener=function(e){this.changeListeners.push(e)},e.prototype.removeChangeListener=function(e){var t=this.changeListeners.indexOf(e);t>=0&&this.changeListeners.splice(t,1)},e}(),a=n(33),u=Object(a.b)().isBrowser,c=function(){function e(e){void 0===e&&(e={}),this.cookies=new s,this.store=u?window.localStorage:Object.create(null),this.cookies=e.req?new s(e.req.headers.cookie):new s,Object.assign(this.store,this.cookies.getAll())}return Object.defineProperty(e.prototype,"length",{get:function(){return Object.entries(this.store).length},enumerable:!0,configurable:!0}),e.prototype.clear=function(){var e=this;Array.from(new Array(this.length)).map((function(t,n){return e.key(n)})).forEach((function(t){return e.removeItem(t)}))},e.prototype.getItem=function(e){return this.getLocalItem(e)},e.prototype.getLocalItem=function(e){return Object.prototype.hasOwnProperty.call(this.store,e)?this.store[e]:null},e.prototype.getUniversalItem=function(e){return this.cookies.get(e)},e.prototype.key=function(e){return Object.keys(this.store)[e]},e.prototype.removeItem=function(e){this.removeLocalItem(e),this.removeUniversalItem(e)},e.prototype.removeLocalItem=function(e){delete this.store[e]},e.prototype.removeUniversalItem=function(e){this.cookies.remove(e,{path:"/"})},e.prototype.setItem=function(e,t){switch(this.setLocalItem(e,t),e.split(".").pop()){case"LastAuthUser":case"accessToken":case"idToken":this.setUniversalItem(e,t)}},e.prototype.setLocalItem=function(e,t){this.store[e]=t},e.prototype.setUniversalItem=function(e,t){this.cookies.set(e,t,{path:"/",sameSite:!0,secure:"localhost"!==window.location.hostname})},e}()},function(e){e.exports=JSON.parse('{"name":"@aws-sdk/client-cognito-identity","description":"AWS SDK for JavaScript Cognito Identity Client for Node.js, Browser and React Native","version":"1.0.0-rc.4","scripts":{"clean":"npm run remove-definitions && npm run remove-dist","build-documentation":"npm run clean && typedoc ./","prepublishOnly":"yarn build","pretest":"yarn build:cjs","remove-definitions":"rimraf ./types","remove-dist":"rimraf ./dist","remove-documentation":"rimraf ./docs","test:unit":"mocha **/cjs/**/*.spec.js","test:e2e":"mocha **/cjs/**/*.ispec.js && karma start karma.conf.js","test":"yarn test:unit","build:cjs":"tsc -p tsconfig.json","build:es":"tsc -p tsconfig.es.json","build":"yarn build:cjs && yarn build:es"},"main":"./dist/cjs/index.js","types":"./types/index.d.ts","module":"./dist/es/index.js","browser":{"./runtimeConfig":"./runtimeConfig.browser"},"react-native":{"./runtimeConfig":"./runtimeConfig.native"},"sideEffects":false,"dependencies":{"@aws-crypto/sha256-browser":"^1.0.0","@aws-crypto/sha256-js":"^1.0.0","@aws-sdk/config-resolver":"1.0.0-rc.3","@aws-sdk/credential-provider-node":"1.0.0-rc.3","@aws-sdk/fetch-http-handler":"1.0.0-rc.3","@aws-sdk/hash-node":"1.0.0-rc.3","@aws-sdk/invalid-dependency":"1.0.0-rc.3","@aws-sdk/middleware-content-length":"1.0.0-rc.3","@aws-sdk/middleware-host-header":"1.0.0-rc.3","@aws-sdk/middleware-logger":"1.0.0-rc.4","@aws-sdk/middleware-retry":"1.0.0-rc.4","@aws-sdk/middleware-serde":"1.0.0-rc.3","@aws-sdk/middleware-signing":"1.0.0-rc.3","@aws-sdk/middleware-stack":"1.0.0-rc.4","@aws-sdk/middleware-user-agent":"1.0.0-rc.3","@aws-sdk/node-config-provider":"1.0.0-rc.3","@aws-sdk/node-http-handler":"1.0.0-rc.3","@aws-sdk/protocol-http":"1.0.0-rc.3","@aws-sdk/smithy-client":"1.0.0-rc.4","@aws-sdk/types":"1.0.0-rc.3","@aws-sdk/url-parser-browser":"1.0.0-rc.3","@aws-sdk/url-parser-node":"1.0.0-rc.3","@aws-sdk/util-base64-browser":"1.0.0-rc.3","@aws-sdk/util-base64-node":"1.0.0-rc.3","@aws-sdk/util-body-length-browser":"1.0.0-rc.3","@aws-sdk/util-body-length-node":"1.0.0-rc.3","@aws-sdk/util-user-agent-browser":"1.0.0-rc.3","@aws-sdk/util-user-agent-node":"1.0.0-rc.3","@aws-sdk/util-utf8-browser":"1.0.0-rc.3","@aws-sdk/util-utf8-node":"1.0.0-rc.3","tslib":"^2.0.0"},"devDependencies":{"@aws-sdk/client-documentation-generator":"1.0.0-rc.3","@aws-sdk/client-iam":"1.0.0-rc.4","@types/chai":"^4.2.11","@types/mocha":"^7.0.2","@types/node":"^12.7.5","jest":"^26.1.0","rimraf":"^3.0.0","typedoc":"^0.17.8","typescript":"~4.0.2"},"engines":{"node":">=10.0.0"},"author":{"name":"AWS SDK for JavaScript Team","url":"https://aws.amazon.com/javascript/"},"license":"Apache-2.0","homepage":"https://github.com/aws/aws-sdk-js-v3/tree/master/clients/client-cognito-identity","repository":{"type":"git","url":"https://github.com/aws/aws-sdk-js-v3.git","directory":"clients/client-cognito-identity"}}')},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),n(1).__exportStar(n(385),t)},function(e){e.exports=JSON.parse('{"name":"@aws-sdk/client-pinpoint","description":"AWS SDK for JavaScript Pinpoint Client for Node.js, Browser and React Native","version":"1.0.0-rc.4","scripts":{"clean":"npm run remove-definitions && npm run remove-dist","build-documentation":"npm run clean && typedoc ./","prepublishOnly":"yarn build","pretest":"yarn build:cjs","remove-definitions":"rimraf ./types","remove-dist":"rimraf ./dist","remove-documentation":"rimraf ./docs","test":"exit 0","build:cjs":"tsc -p tsconfig.json","build:es":"tsc -p tsconfig.es.json","build":"yarn build:cjs && yarn build:es"},"main":"./dist/cjs/index.js","types":"./types/index.d.ts","module":"./dist/es/index.js","browser":{"./runtimeConfig":"./runtimeConfig.browser"},"react-native":{"./runtimeConfig":"./runtimeConfig.native"},"sideEffects":false,"dependencies":{"@aws-crypto/sha256-browser":"^1.0.0","@aws-crypto/sha256-js":"^1.0.0","@aws-sdk/config-resolver":"1.0.0-rc.3","@aws-sdk/credential-provider-node":"1.0.0-rc.3","@aws-sdk/fetch-http-handler":"1.0.0-rc.3","@aws-sdk/hash-node":"1.0.0-rc.3","@aws-sdk/invalid-dependency":"1.0.0-rc.3","@aws-sdk/middleware-content-length":"1.0.0-rc.3","@aws-sdk/middleware-host-header":"1.0.0-rc.3","@aws-sdk/middleware-logger":"1.0.0-rc.4","@aws-sdk/middleware-retry":"1.0.0-rc.4","@aws-sdk/middleware-serde":"1.0.0-rc.3","@aws-sdk/middleware-signing":"1.0.0-rc.3","@aws-sdk/middleware-stack":"1.0.0-rc.4","@aws-sdk/middleware-user-agent":"1.0.0-rc.3","@aws-sdk/node-config-provider":"1.0.0-rc.3","@aws-sdk/node-http-handler":"1.0.0-rc.3","@aws-sdk/protocol-http":"1.0.0-rc.3","@aws-sdk/smithy-client":"1.0.0-rc.4","@aws-sdk/types":"1.0.0-rc.3","@aws-sdk/url-parser-browser":"1.0.0-rc.3","@aws-sdk/url-parser-node":"1.0.0-rc.3","@aws-sdk/util-base64-browser":"1.0.0-rc.3","@aws-sdk/util-base64-node":"1.0.0-rc.3","@aws-sdk/util-body-length-browser":"1.0.0-rc.3","@aws-sdk/util-body-length-node":"1.0.0-rc.3","@aws-sdk/util-user-agent-browser":"1.0.0-rc.3","@aws-sdk/util-user-agent-node":"1.0.0-rc.3","@aws-sdk/util-utf8-browser":"1.0.0-rc.3","@aws-sdk/util-utf8-node":"1.0.0-rc.3","tslib":"^2.0.0"},"devDependencies":{"@aws-sdk/client-documentation-generator":"1.0.0-rc.3","@types/node":"^12.7.5","jest":"^26.1.0","rimraf":"^3.0.0","typedoc":"^0.17.8","typescript":"~4.0.2"},"engines":{"node":">=10.0.0"},"author":{"name":"AWS SDK for JavaScript Team","url":"https://aws.amazon.com/javascript/"},"license":"Apache-2.0","homepage":"https://github.com/aws/aws-sdk-js-v3/tree/master/clients/client-pinpoint","repository":{"type":"git","url":"https://github.com/aws/aws-sdk-js-v3.git","directory":"clients/client-pinpoint"}}')},function(e){e.exports=JSON.parse('{"name":"@aws-sdk/client-kinesis","description":"AWS SDK for JavaScript Kinesis Client for Node.js, Browser and React Native","version":"1.0.0-rc.4","scripts":{"clean":"npm run remove-definitions && npm run remove-dist","build-documentation":"npm run clean && typedoc ./","prepublishOnly":"yarn build","pretest":"yarn build:cjs","remove-definitions":"rimraf ./types","remove-dist":"rimraf ./dist","remove-documentation":"rimraf ./docs","test":"exit 0","build:cjs":"tsc -p tsconfig.json","build:es":"tsc -p tsconfig.es.json","build":"yarn build:cjs && yarn build:es"},"main":"./dist/cjs/index.js","types":"./types/index.d.ts","module":"./dist/es/index.js","browser":{"./runtimeConfig":"./runtimeConfig.browser"},"react-native":{"./runtimeConfig":"./runtimeConfig.native"},"sideEffects":false,"dependencies":{"@aws-crypto/sha256-browser":"^1.0.0","@aws-crypto/sha256-js":"^1.0.0","@aws-sdk/config-resolver":"1.0.0-rc.3","@aws-sdk/credential-provider-node":"1.0.0-rc.3","@aws-sdk/eventstream-serde-browser":"1.0.0-rc.3","@aws-sdk/eventstream-serde-config-resolver":"1.0.0-rc.3","@aws-sdk/eventstream-serde-node":"1.0.0-rc.3","@aws-sdk/fetch-http-handler":"1.0.0-rc.3","@aws-sdk/hash-node":"1.0.0-rc.3","@aws-sdk/invalid-dependency":"1.0.0-rc.3","@aws-sdk/middleware-content-length":"1.0.0-rc.3","@aws-sdk/middleware-host-header":"1.0.0-rc.3","@aws-sdk/middleware-logger":"1.0.0-rc.4","@aws-sdk/middleware-retry":"1.0.0-rc.4","@aws-sdk/middleware-serde":"1.0.0-rc.3","@aws-sdk/middleware-signing":"1.0.0-rc.3","@aws-sdk/middleware-stack":"1.0.0-rc.4","@aws-sdk/middleware-user-agent":"1.0.0-rc.3","@aws-sdk/node-config-provider":"1.0.0-rc.3","@aws-sdk/node-http-handler":"1.0.0-rc.3","@aws-sdk/protocol-http":"1.0.0-rc.3","@aws-sdk/smithy-client":"1.0.0-rc.4","@aws-sdk/types":"1.0.0-rc.3","@aws-sdk/url-parser-browser":"1.0.0-rc.3","@aws-sdk/url-parser-node":"1.0.0-rc.3","@aws-sdk/util-base64-browser":"1.0.0-rc.3","@aws-sdk/util-base64-node":"1.0.0-rc.3","@aws-sdk/util-body-length-browser":"1.0.0-rc.3","@aws-sdk/util-body-length-node":"1.0.0-rc.3","@aws-sdk/util-user-agent-browser":"1.0.0-rc.3","@aws-sdk/util-user-agent-node":"1.0.0-rc.3","@aws-sdk/util-utf8-browser":"1.0.0-rc.3","@aws-sdk/util-utf8-node":"1.0.0-rc.3","tslib":"^2.0.0"},"devDependencies":{"@aws-sdk/client-documentation-generator":"1.0.0-rc.3","@types/node":"^12.7.5","jest":"^26.1.0","rimraf":"^3.0.0","typedoc":"^0.17.8","typescript":"~4.0.2"},"engines":{"node":">=10.0.0"},"author":{"name":"AWS SDK for JavaScript Team","url":"https://aws.amazon.com/javascript/"},"license":"Apache-2.0","homepage":"https://github.com/aws/aws-sdk-js-v3/tree/master/clients/client-kinesis","repository":{"type":"git","url":"https://github.com/aws/aws-sdk-js-v3.git","directory":"clients/client-kinesis"}}')},function(e){e.exports=JSON.parse('{"name":"@aws-sdk/client-firehose","description":"AWS SDK for JavaScript Firehose Client for Node.js, Browser and React Native","version":"1.0.0-rc.4","scripts":{"clean":"npm run remove-definitions && npm run remove-dist","build-documentation":"npm run clean && typedoc ./","prepublishOnly":"yarn build","pretest":"yarn build:cjs","remove-definitions":"rimraf ./types","remove-dist":"rimraf ./dist","remove-documentation":"rimraf ./docs","test":"exit 0","build:cjs":"tsc -p tsconfig.json","build:es":"tsc -p tsconfig.es.json","build":"yarn build:cjs && yarn build:es"},"main":"./dist/cjs/index.js","types":"./types/index.d.ts","module":"./dist/es/index.js","browser":{"./runtimeConfig":"./runtimeConfig.browser"},"react-native":{"./runtimeConfig":"./runtimeConfig.native"},"sideEffects":false,"dependencies":{"@aws-crypto/sha256-browser":"^1.0.0","@aws-crypto/sha256-js":"^1.0.0","@aws-sdk/config-resolver":"1.0.0-rc.3","@aws-sdk/credential-provider-node":"1.0.0-rc.3","@aws-sdk/fetch-http-handler":"1.0.0-rc.3","@aws-sdk/hash-node":"1.0.0-rc.3","@aws-sdk/invalid-dependency":"1.0.0-rc.3","@aws-sdk/middleware-content-length":"1.0.0-rc.3","@aws-sdk/middleware-host-header":"1.0.0-rc.3","@aws-sdk/middleware-logger":"1.0.0-rc.4","@aws-sdk/middleware-retry":"1.0.0-rc.4","@aws-sdk/middleware-serde":"1.0.0-rc.3","@aws-sdk/middleware-signing":"1.0.0-rc.3","@aws-sdk/middleware-stack":"1.0.0-rc.4","@aws-sdk/middleware-user-agent":"1.0.0-rc.3","@aws-sdk/node-config-provider":"1.0.0-rc.3","@aws-sdk/node-http-handler":"1.0.0-rc.3","@aws-sdk/protocol-http":"1.0.0-rc.3","@aws-sdk/smithy-client":"1.0.0-rc.4","@aws-sdk/types":"1.0.0-rc.3","@aws-sdk/url-parser-browser":"1.0.0-rc.3","@aws-sdk/url-parser-node":"1.0.0-rc.3","@aws-sdk/util-base64-browser":"1.0.0-rc.3","@aws-sdk/util-base64-node":"1.0.0-rc.3","@aws-sdk/util-body-length-browser":"1.0.0-rc.3","@aws-sdk/util-body-length-node":"1.0.0-rc.3","@aws-sdk/util-user-agent-browser":"1.0.0-rc.3","@aws-sdk/util-user-agent-node":"1.0.0-rc.3","@aws-sdk/util-utf8-browser":"1.0.0-rc.3","@aws-sdk/util-utf8-node":"1.0.0-rc.3","tslib":"^2.0.0"},"devDependencies":{"@aws-sdk/client-documentation-generator":"1.0.0-rc.3","@types/node":"^12.7.5","jest":"^26.1.0","rimraf":"^3.0.0","typedoc":"^0.17.8","typescript":"~4.0.2"},"engines":{"node":">=10.0.0"},"author":{"name":"AWS SDK for JavaScript Team","url":"https://aws.amazon.com/javascript/"},"license":"Apache-2.0","homepage":"https://github.com/aws/aws-sdk-js-v3/tree/master/clients/client-firehose","repository":{"type":"git","url":"https://github.com/aws/aws-sdk-js-v3.git","directory":"clients/client-firehose"}}')},function(e){e.exports=JSON.parse('{"name":"@aws-sdk/client-personalize-events","description":"AWS SDK for JavaScript Personalize Events Client for Node.js, Browser and React Native","version":"1.0.0-rc.4","scripts":{"clean":"npm run remove-definitions && npm run remove-dist","build-documentation":"npm run clean && typedoc ./","prepublishOnly":"yarn build","pretest":"yarn build:cjs","remove-definitions":"rimraf ./types","remove-dist":"rimraf ./dist","remove-documentation":"rimraf ./docs","test":"exit 0","build:cjs":"tsc -p tsconfig.json","build:es":"tsc -p tsconfig.es.json","build":"yarn build:cjs && yarn build:es"},"main":"./dist/cjs/index.js","types":"./types/index.d.ts","module":"./dist/es/index.js","browser":{"./runtimeConfig":"./runtimeConfig.browser"},"react-native":{"./runtimeConfig":"./runtimeConfig.native"},"sideEffects":false,"dependencies":{"@aws-crypto/sha256-browser":"^1.0.0","@aws-crypto/sha256-js":"^1.0.0","@aws-sdk/config-resolver":"1.0.0-rc.3","@aws-sdk/credential-provider-node":"1.0.0-rc.3","@aws-sdk/fetch-http-handler":"1.0.0-rc.3","@aws-sdk/hash-node":"1.0.0-rc.3","@aws-sdk/invalid-dependency":"1.0.0-rc.3","@aws-sdk/middleware-content-length":"1.0.0-rc.3","@aws-sdk/middleware-host-header":"1.0.0-rc.3","@aws-sdk/middleware-logger":"1.0.0-rc.4","@aws-sdk/middleware-retry":"1.0.0-rc.4","@aws-sdk/middleware-serde":"1.0.0-rc.3","@aws-sdk/middleware-signing":"1.0.0-rc.3","@aws-sdk/middleware-stack":"1.0.0-rc.4","@aws-sdk/middleware-user-agent":"1.0.0-rc.3","@aws-sdk/node-config-provider":"1.0.0-rc.3","@aws-sdk/node-http-handler":"1.0.0-rc.3","@aws-sdk/protocol-http":"1.0.0-rc.3","@aws-sdk/smithy-client":"1.0.0-rc.4","@aws-sdk/types":"1.0.0-rc.3","@aws-sdk/url-parser-browser":"1.0.0-rc.3","@aws-sdk/url-parser-node":"1.0.0-rc.3","@aws-sdk/util-base64-browser":"1.0.0-rc.3","@aws-sdk/util-base64-node":"1.0.0-rc.3","@aws-sdk/util-body-length-browser":"1.0.0-rc.3","@aws-sdk/util-body-length-node":"1.0.0-rc.3","@aws-sdk/util-user-agent-browser":"1.0.0-rc.3","@aws-sdk/util-user-agent-node":"1.0.0-rc.3","@aws-sdk/util-utf8-browser":"1.0.0-rc.3","@aws-sdk/util-utf8-node":"1.0.0-rc.3","tslib":"^2.0.0"},"devDependencies":{"@aws-sdk/client-documentation-generator":"1.0.0-rc.3","@types/node":"^12.7.5","jest":"^26.1.0","rimraf":"^3.0.0","typedoc":"^0.17.8","typescript":"~4.0.2"},"engines":{"node":">=10.0.0"},"author":{"name":"AWS SDK for JavaScript Team","url":"https://aws.amazon.com/javascript/"},"license":"Apache-2.0","homepage":"https://github.com/aws/aws-sdk-js-v3/tree/master/clients/client-personalize-events","repository":{"type":"git","url":"https://github.com/aws/aws-sdk-js-v3.git","directory":"clients/client-personalize-events"}}')},function(e){e.exports=JSON.parse('{"name":"@aws-sdk/client-s3","description":"AWS SDK for JavaScript S3 Client for Node.js, Browser and React Native","version":"1.0.0-rc.4","scripts":{"clean":"npm run remove-definitions && npm run remove-dist","build-documentation":"npm run clean && typedoc ./","prepublishOnly":"yarn build","pretest":"yarn build:cjs","remove-definitions":"rimraf ./types","remove-dist":"rimraf ./dist","remove-documentation":"rimraf ./docs","test:unit":"mocha **/cjs/**/*.spec.js","test:e2e":"mocha **/cjs/**/*.ispec.js && karma start karma.conf.js","test":"yarn test:unit","build:cjs":"tsc -p tsconfig.json","build:es":"tsc -p tsconfig.es.json","build":"yarn build:cjs && yarn build:es"},"main":"./dist/cjs/index.js","types":"./types/index.d.ts","module":"./dist/es/index.js","browser":{"./runtimeConfig":"./runtimeConfig.browser"},"react-native":{"./runtimeConfig":"./runtimeConfig.native"},"sideEffects":false,"dependencies":{"@aws-crypto/sha256-browser":"^1.0.0","@aws-crypto/sha256-js":"^1.0.0","@aws-sdk/config-resolver":"1.0.0-rc.3","@aws-sdk/credential-provider-node":"1.0.0-rc.3","@aws-sdk/eventstream-serde-browser":"1.0.0-rc.3","@aws-sdk/eventstream-serde-config-resolver":"1.0.0-rc.3","@aws-sdk/eventstream-serde-node":"1.0.0-rc.3","@aws-sdk/fetch-http-handler":"1.0.0-rc.3","@aws-sdk/hash-blob-browser":"1.0.0-rc.3","@aws-sdk/hash-node":"1.0.0-rc.3","@aws-sdk/hash-stream-node":"1.0.0-rc.3","@aws-sdk/invalid-dependency":"1.0.0-rc.3","@aws-sdk/md5-js":"1.0.0-rc.3","@aws-sdk/middleware-apply-body-checksum":"1.0.0-rc.3","@aws-sdk/middleware-bucket-endpoint":"1.0.0-rc.4","@aws-sdk/middleware-content-length":"1.0.0-rc.3","@aws-sdk/middleware-expect-continue":"1.0.0-rc.3","@aws-sdk/middleware-host-header":"1.0.0-rc.3","@aws-sdk/middleware-location-constraint":"1.0.0-rc.3","@aws-sdk/middleware-logger":"1.0.0-rc.4","@aws-sdk/middleware-retry":"1.0.0-rc.4","@aws-sdk/middleware-sdk-s3":"1.0.0-rc.3","@aws-sdk/middleware-serde":"1.0.0-rc.3","@aws-sdk/middleware-signing":"1.0.0-rc.3","@aws-sdk/middleware-ssec":"1.0.0-rc.3","@aws-sdk/middleware-stack":"1.0.0-rc.4","@aws-sdk/middleware-user-agent":"1.0.0-rc.3","@aws-sdk/node-config-provider":"1.0.0-rc.3","@aws-sdk/node-http-handler":"1.0.0-rc.3","@aws-sdk/protocol-http":"1.0.0-rc.3","@aws-sdk/smithy-client":"1.0.0-rc.4","@aws-sdk/types":"1.0.0-rc.3","@aws-sdk/url-parser-browser":"1.0.0-rc.3","@aws-sdk/url-parser-node":"1.0.0-rc.3","@aws-sdk/util-base64-browser":"1.0.0-rc.3","@aws-sdk/util-base64-node":"1.0.0-rc.3","@aws-sdk/util-body-length-browser":"1.0.0-rc.3","@aws-sdk/util-body-length-node":"1.0.0-rc.3","@aws-sdk/util-user-agent-browser":"1.0.0-rc.3","@aws-sdk/util-user-agent-node":"1.0.0-rc.3","@aws-sdk/util-utf8-browser":"1.0.0-rc.3","@aws-sdk/util-utf8-node":"1.0.0-rc.3","@aws-sdk/xml-builder":"1.0.0-rc.3","fast-xml-parser":"^3.16.0","tslib":"^2.0.0"},"devDependencies":{"@aws-sdk/client-documentation-generator":"1.0.0-rc.3","@types/chai":"^4.2.11","@types/mocha":"^7.0.2","@types/node":"^12.7.5","jest":"^26.1.0","rimraf":"^3.0.0","typedoc":"^0.17.8","typescript":"~4.0.2"},"engines":{"node":">=10.0.0"},"author":{"name":"AWS SDK for JavaScript Team","url":"https://aws.amazon.com/javascript/"},"license":"Apache-2.0","homepage":"https://github.com/aws/aws-sdk-js-v3/tree/master/clients/client-s3","repository":{"type":"git","url":"https://github.com/aws/aws-sdk-js-v3.git","directory":"clients/client-s3"}}')},function(e,t,n){"use strict";(function(e){n.d(t,"a",(function(){return r})),n.d(t,"b",(function(){return S}));var r,i,o=n(58),s=n(52),a=n(42),u=n(26),c=n(44),f=n(88),l=n(34),d=n(14),h=n(13),p=n(9),v=n(3),g=function(){return(g=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},m=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},b=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},y=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},w=function(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(y(arguments[t]));return e},_=new c.a("DataStore");!function(e){e.CONNECTED="CONNECTED"}(r||(r={})),function(e){e[e.none=0]="none",e[e.unauth=1]="unauth",e[e.auth=2]="auth"}(i||(i={}));var S=function(){function t(e,t,n){void 0===n&&(n={}),this.schema=e,this.syncPredicates=t,this.amplifyConfig=n,this.typeQuery=new WeakMap,this.buffer=[]}return t.prototype.buildSubscription=function(e,t,n,r,i,o){var s=this.amplifyConfig.aws_appsync_authenticationType,a=this.getAuthorizationInfo(t,r,s,i,o)||{},u=a.authMode,c=a.isOwner,f=a.ownerField,l=a.ownerValue,d=y(Object(h.c)(e,t,n,c,f),3);return{authMode:u,opType:d[0],opName:d[1],query:d[2],isOwner:c,ownerField:f,ownerValue:l}},t.prototype.getAuthorizationInfo=function(e,t,n,r,s){void 0===r&&(r={}),void 0===s&&(s={});var a=Object(h.e)(e);if(n===o.a.AWS_IAM&&a.find((function(e){return"private"===e.authStrategy&&"iam"===e.provider}))&&t===i.unauth)return null;var u,c=a.filter((function(e){return"groups"===e.authStrategy&&["userPools","oidc"].includes(e.provider)}));return n!==o.a.AMAZON_COGNITO_USER_POOLS&&n!==o.a.OPENID_CONNECT||!c.find((function(e){var t=Object(h.f)(r,e),n=Object(h.f)(s,e);return w(t,n).find((function(t){return e.groups.find((function(e){return e===t}))}))}))?((n===o.a.AMAZON_COGNITO_USER_POOLS?a.filter((function(e){return"owner"===e.authStrategy&&"userPools"===e.provider})):[]).forEach((function(e){var t=r[e.identityClaim];t&&(u={authMode:o.a.AMAZON_COGNITO_USER_POOLS,isOwner:!e.areSubscriptionsPublic,ownerField:e.ownerField,ownerValue:t})})),u||((n===o.a.OPENID_CONNECT?a.filter((function(e){return"owner"===e.authStrategy&&"oidc"===e.provider})):[]).forEach((function(e){var t=s[e.identityClaim];t&&(u={authMode:o.a.OPENID_CONNECT,isOwner:!e.areSubscriptionsPublic,ownerField:e.ownerField,ownerValue:t})})),u||{authMode:n,isOwner:!1})):{authMode:n,isOwner:!1}},t.prototype.hubQueryCompletionListener=function(e,t){t.payload.event===l.a.SUBSCRIPTION_ACK&&e()},t.prototype.start=function(){var t=this;return[new d.a((function(n){var o,c,l=[],d=[],v=i.none;return m(t,void 0,void 0,(function(){var t,w,S,E,M,A,I,k,O,x,C=this;return b(this,(function(T){switch(T.label){case 0:return T.trys.push([0,2,,3]),[4,a.a.currentCredentials()];case 1:return t=T.sent(),v=t.authenticated?i.auth:i.unauth,[3,3];case 2:return T.sent(),[3,3];case 3:return T.trys.push([3,5,,6]),[4,a.a.currentSession()];case 4:return w=T.sent(),o=w.getIdToken().decodePayload(),[3,6];case 5:return T.sent(),[3,6];case 6:if(T.trys.push([6,11,,12]),S=this.amplifyConfig,E=S.aws_cognito_region,M=S.Auth,!E||M&&!M.region)throw"Auth is not configured";return A=void 0,[4,u.a.getItem("federatedInfo")];case 7:return(I=T.sent())?(A=I.token,[3,10]):[3,8];case 8:return[4,a.a.currentAuthenticatedUser()];case 9:(k=T.sent())&&(A=k.token),T.label=10;case 10:return A&&(O=A.split(".")[1],c=JSON.parse(e.from(O,"base64").toString("utf8"))),[3,12];case 11:return x=T.sent(),_.debug("error getting OIDC JWT",x),[3,12];case 12:return Object.values(this.schema.namespaces).forEach((function(e){Object.values(e.models).filter((function(e){return e.syncable})).forEach((function(t){return m(C,void 0,void 0,(function(){var r=this;return b(this,(function(i){return[h.a.CREATE,h.a.UPDATE,h.a.DELETE].map((function(n){return r.buildSubscription(e,t,n,v,o,c)})).forEach((function(e){var i=e.opType,o=e.opName,a=e.query,u=e.isOwner,c=e.ownerField,h=e.ownerValue,v=e.authMode;return m(r,void 0,void 0,(function(){var e,r,w,S=this;return b(this,(function(E){if(e={},u){if(!h)return n.error("Owner field required, sign in is needed in order to perform this operation"),[2];e[c]=h}return r=s.a.graphql(g({query:a,variables:e},{authMode:v})),d.push(r.map((function(e){return e.value})).subscribe({next:function(e){var n=e.data,r=e.errors;if(Array.isArray(r)&&r.length>0){var s=r.map((function(e){return e.message}));return _.warn("Skipping incoming subscription. Messages: "+s.join("\n")),void S.drainBuffer()}var a=p.a.getPredicates(S.syncPredicates.get(t),!1),u=n[o];S.passesPredicateValidation(u,a)&&S.pushToBuffer(i,t,u),S.drainBuffer()},error:function(e){var t=e.error,r=y((void 0===t?{errors:[]}:t).errors,1)[0],i=(void 0===r?{}:r).message,o=void 0===i?"":i;_.warn("subscriptionError",o),"function"==typeof w&&w(),o.includes('"errorType":"Unauthorized"')||n.error(o)}})),l.push(m(S,void 0,void 0,(function(){var e,t=this;return b(this,(function(n){switch(n.label){case 0:return[4,new Promise((function(n){w=n,e=t.hubQueryCompletionListener.bind(t,n),f.a.listen("api",e)}))];case 1:return n.sent(),f.a.remove("api",e),[2]}}))}))),[2]}))}))})),[2]}))}))}))})),Promise.all(l).then((function(){return n.next(r.CONNECTED)})),[2]}}))})),function(){d.forEach((function(e){return e.unsubscribe()}))}})),new d.a((function(e){return t.dataObserver=e,t.drainBuffer(),function(){t.dataObserver=null}}))]},t.prototype.passesPredicateValidation=function(e,t){if(!t)return!0;var n=t.predicates,r=t.type;return Object(v.y)(e,r,n)},t.prototype.pushToBuffer=function(e,t,n){this.buffer.push([e,t,n])},t.prototype.drainBuffer=function(){var e=this;this.dataObserver&&(this.buffer.forEach((function(t){return e.dataObserver.next(t)})),this.buffer=[])},t}()}).call(this,n(6).Buffer)},function(e){e.exports=JSON.parse('{"name":"@aws-sdk/client-lex-runtime-service","description":"AWS SDK for JavaScript Lex Runtime Service Client for Node.js, Browser and React Native","version":"1.0.0-rc.4","scripts":{"clean":"npm run remove-definitions && npm run remove-dist","build-documentation":"npm run clean && typedoc ./","prepublishOnly":"yarn build","pretest":"yarn build:cjs","remove-definitions":"rimraf ./types","remove-dist":"rimraf ./dist","remove-documentation":"rimraf ./docs","test:unit":"mocha **/cjs/**/*.spec.js","test":"yarn test:unit","build:cjs":"tsc -p tsconfig.json","build:es":"tsc -p tsconfig.es.json","build":"yarn build:cjs && yarn build:es"},"main":"./dist/cjs/index.js","types":"./types/index.d.ts","module":"./dist/es/index.js","browser":{"./runtimeConfig":"./runtimeConfig.browser"},"react-native":{"./runtimeConfig":"./runtimeConfig.native"},"sideEffects":false,"dependencies":{"@aws-crypto/sha256-browser":"^1.0.0","@aws-crypto/sha256-js":"^1.0.0","@aws-sdk/config-resolver":"1.0.0-rc.3","@aws-sdk/credential-provider-node":"1.0.0-rc.3","@aws-sdk/fetch-http-handler":"1.0.0-rc.3","@aws-sdk/hash-node":"1.0.0-rc.3","@aws-sdk/invalid-dependency":"1.0.0-rc.3","@aws-sdk/middleware-content-length":"1.0.0-rc.3","@aws-sdk/middleware-host-header":"1.0.0-rc.3","@aws-sdk/middleware-logger":"1.0.0-rc.4","@aws-sdk/middleware-retry":"1.0.0-rc.4","@aws-sdk/middleware-serde":"1.0.0-rc.3","@aws-sdk/middleware-signing":"1.0.0-rc.3","@aws-sdk/middleware-stack":"1.0.0-rc.4","@aws-sdk/middleware-user-agent":"1.0.0-rc.3","@aws-sdk/node-config-provider":"1.0.0-rc.3","@aws-sdk/node-http-handler":"1.0.0-rc.3","@aws-sdk/protocol-http":"1.0.0-rc.3","@aws-sdk/smithy-client":"1.0.0-rc.4","@aws-sdk/types":"1.0.0-rc.3","@aws-sdk/url-parser-browser":"1.0.0-rc.3","@aws-sdk/url-parser-node":"1.0.0-rc.3","@aws-sdk/util-base64-browser":"1.0.0-rc.3","@aws-sdk/util-base64-node":"1.0.0-rc.3","@aws-sdk/util-body-length-browser":"1.0.0-rc.3","@aws-sdk/util-body-length-node":"1.0.0-rc.3","@aws-sdk/util-user-agent-browser":"1.0.0-rc.3","@aws-sdk/util-user-agent-node":"1.0.0-rc.3","@aws-sdk/util-utf8-browser":"1.0.0-rc.3","@aws-sdk/util-utf8-node":"1.0.0-rc.3","tslib":"^2.0.0"},"devDependencies":{"@aws-sdk/client-documentation-generator":"1.0.0-rc.3","@types/chai":"^4.2.11","@types/mocha":"^7.0.2","@types/node":"^12.7.5","jest":"^26.1.0","rimraf":"^3.0.0","typedoc":"^0.17.8","typescript":"~4.0.2"},"engines":{"node":">=10.0.0"},"author":{"name":"AWS SDK for JavaScript Team","url":"https://aws.amazon.com/javascript/"},"license":"Apache-2.0","homepage":"https://github.com/aws/aws-sdk-js-v3/tree/master/clients/client-lex-runtime-service","repository":{"type":"git","url":"https://github.com/aws/aws-sdk-js-v3.git","directory":"clients/client-lex-runtime-service"}}')},,function(e,t,n){"use strict";n.r(t);var r=n(19),i=n(143);n.d(t,"Amplify",(function(){return r.a}));var o=n(63),s=n(26),a=n(491);n.d(t,"Analytics",(function(){return a.a}));var u=n(144);n.d(t,"AWSPinpointProvider",(function(){return u.a}));var c=n(145);n.d(t,"AWSKinesisProvider",(function(){return c.a}));var f=n(492);n.d(t,"AWSKinesisFirehoseProvider",(function(){return f.a}));var l=n(490);n.d(t,"AmazonPersonalizeProvider",(function(){return l.a})),n.d(t,"Auth",(function(){return o.a}));var d=n(140);n.d(t,"Storage",(function(){return d.a})),n.d(t,"StorageClass",(function(){return d.b}));var h=n(62);n.d(t,"API",(function(){return h.a})),n.d(t,"APIClass",(function(){return h.b}));var p=n(248);n.d(t,"graphqlOperation",(function(){return p.b}));var v=n(258);n.d(t,"DataStore",(function(){return v.a}));var g=n(9);n.d(t,"Predicates",(function(){return g.b}));var m=n(4);n.d(t,"SortDirection",(function(){return m.e})),n.d(t,"syncExpression",(function(){return m.n}));var b=n(105);n.d(t,"PubSub",(function(){return b.a})),n.d(t,"Cache",(function(){return s.a}));var y=n(489);n.d(t,"Interactions",(function(){return y.a}));var w=n(246);for(var _ in w)["default","Analytics","AWSPinpointProvider","AWSKinesisProvider","AWSKinesisFirehoseProvider","AmazonPersonalizeProvider","Auth","Storage","StorageClass","API","APIClass","graphqlOperation","DataStore","Predicates","SortDirection","syncExpression","PubSub","Cache","Interactions","XR","Predictions","Logger","Hub","JS","ClientDevice","Signer","I18n","ServiceWorker","withSSRContext","Amplify"].indexOf(_)<0&&function(e){n.d(t,e,(function(){return w[e]}))}(_);var S=n(493);n.d(t,"XR",(function(){return S.a}));var E=n(488);n.d(t,"Predictions",(function(){return E.a}));var M=n(44);n.d(t,"Logger",(function(){return M.a}));var A=n(88);n.d(t,"Hub",(function(){return A.a}));var I=n(33);n.d(t,"JS",(function(){return I.a}));var k=n(141);n.d(t,"ClientDevice",(function(){return k.a}));var O=n(104);n.d(t,"Signer",(function(){return O.a}));var x=n(142);n.d(t,"I18n",(function(){return x.a})),n.d(t,"ServiceWorker",(function(){return i.a}));var C=n(247);n.d(t,"withSSRContext",(function(){return C.a})),r.a.Auth=o.a,r.a.Cache=s.a,r.a.ServiceWorker=i.a,t.default=r.a},,,function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t,n){var r;e.exports=(r=n(32),function(){if("function"==typeof ArrayBuffer){var e=r.lib.WordArray,t=e.init;(e.init=function(e){if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),(e instanceof Int8Array||"undefined"!=typeof Uint8ClampedArray&&e instanceof Uint8ClampedArray||e instanceof Int16Array||e instanceof Uint16Array||e instanceof Int32Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array)&&(e=new Uint8Array(e.buffer,e.byteOffset,e.byteLength)),e instanceof Uint8Array){for(var n=e.byteLength,r=[],i=0;i<n;i++)r[i>>>2]|=e[i]<<24-i%4*8;t.call(this,r,n)}else t.apply(this,arguments)}).prototype=e}}(),r.lib.WordArray)},function(e,t,n){"use strict";var r=n(8).Buffer,i=n(273).Transform;function o(e){i.call(this),this._block=r.allocUnsafe(e),this._blockSize=e,this._blockOffset=0,this._length=[0,0,0,0],this._finalized=!1}n(7)(o,i),o.prototype._transform=function(e,t,n){var r=null;try{this.update(e,t)}catch(e){r=e}n(r)},o.prototype._flush=function(e){var t=null;try{this.push(this.digest())}catch(e){t=e}e(t)},o.prototype.update=function(e,t){if(function(e,t){if(!r.isBuffer(e)&&"string"!=typeof e)throw new TypeError(t+" must be a string or a buffer")}(e,"Data"),this._finalized)throw new Error("Digest already called");r.isBuffer(e)||(e=r.from(e,t));for(var n=this._block,i=0;this._blockOffset+e.length-i>=this._blockSize;){for(var o=this._blockOffset;o<this._blockSize;)n[o++]=e[i++];this._update(),this._blockOffset=0}for(;i<e.length;)n[this._blockOffset++]=e[i++];for(var s=0,a=8*e.length;a>0;++s)this._length[s]+=a,(a=this._length[s]/4294967296|0)>0&&(this._length[s]-=4294967296*a);return this},o.prototype._update=function(){throw new Error("_update is not implemented")},o.prototype.digest=function(e){if(this._finalized)throw new Error("Digest already called");this._finalized=!0;var t=this._digest();void 0!==e&&(t=t.toString(e)),this._block.fill(0),this._blockOffset=0;for(var n=0;n<4;++n)this._length[n]=0;return t},o.prototype._digest=function(){throw new Error("_digest is not implemented")},e.exports=o},function(e,t,n){"use strict";(function(t,r){var i;e.exports=A,A.ReadableState=M;n(49).EventEmitter;var o=function(e,t){return e.listeners(t).length},s=n(164),a=n(6).Buffer,u=t.Uint8Array||function(){};var c,f=n(274);c=f&&f.debuglog?f.debuglog("stream"):function(){};var l,d,h,p=n(275),v=n(165),g=n(166).getHighWaterMark,m=n(67).codes,b=m.ERR_INVALID_ARG_TYPE,y=m.ERR_STREAM_PUSH_AFTER_EOF,w=m.ERR_METHOD_NOT_IMPLEMENTED,_=m.ERR_STREAM_UNSHIFT_AFTER_END_EVENT;n(7)(A,s);var S=v.errorOrDestroy,E=["error","close","destroy","pause","resume"];function M(e,t,r){i=i||n(68),e=e||{},"boolean"!=typeof r&&(r=t instanceof i),this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.readableObjectMode),this.highWaterMark=g(this,e,"readableHighWaterMark",r),this.buffer=new p,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.paused=!0,this.emitClose=!1!==e.emitClose,this.autoDestroy=!!e.autoDestroy,this.destroyed=!1,this.defaultEncoding=e.defaultEncoding||"utf8",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,e.encoding&&(l||(l=n(59).StringDecoder),this.decoder=new l(e.encoding),this.encoding=e.encoding)}function A(e){if(i=i||n(68),!(this instanceof A))return new A(e);var t=this instanceof i;this._readableState=new M(e,this,t),this.readable=!0,e&&("function"==typeof e.read&&(this._read=e.read),"function"==typeof e.destroy&&(this._destroy=e.destroy)),s.call(this)}function I(e,t,n,r,i){c("readableAddChunk",t);var o,s=e._readableState;if(null===t)s.reading=!1,function(e,t){if(c("onEofChunk"),t.ended)return;if(t.decoder){var n=t.decoder.end();n&&n.length&&(t.buffer.push(n),t.length+=t.objectMode?1:n.length)}t.ended=!0,t.sync?x(e):(t.needReadable=!1,t.emittedReadable||(t.emittedReadable=!0,C(e)))}(e,s);else if(i||(o=function(e,t){var n;r=t,a.isBuffer(r)||r instanceof u||"string"==typeof t||void 0===t||e.objectMode||(n=new b("chunk",["string","Buffer","Uint8Array"],t));var r;return n}(s,t)),o)S(e,o);else if(s.objectMode||t&&t.length>0)if("string"==typeof t||s.objectMode||Object.getPrototypeOf(t)===a.prototype||(t=function(e){return a.from(e)}(t)),r)s.endEmitted?S(e,new _):k(e,s,t,!0);else if(s.ended)S(e,new y);else{if(s.destroyed)return!1;s.reading=!1,s.decoder&&!n?(t=s.decoder.write(t),s.objectMode||0!==t.length?k(e,s,t,!1):T(e,s)):k(e,s,t,!1)}else r||(s.reading=!1,T(e,s));return!s.ended&&(s.length<s.highWaterMark||0===s.length)}function k(e,t,n,r){t.flowing&&0===t.length&&!t.sync?(t.awaitDrain=0,e.emit("data",n)):(t.length+=t.objectMode?1:n.length,r?t.buffer.unshift(n):t.buffer.push(n),t.needReadable&&x(e)),T(e,t)}Object.defineProperty(A.prototype,"destroyed",{enumerable:!1,get:function(){return void 0!==this._readableState&&this._readableState.destroyed},set:function(e){this._readableState&&(this._readableState.destroyed=e)}}),A.prototype.destroy=v.destroy,A.prototype._undestroy=v.undestroy,A.prototype._destroy=function(e,t){t(e)},A.prototype.push=function(e,t){var n,r=this._readableState;return r.objectMode?n=!0:"string"==typeof e&&((t=t||r.defaultEncoding)!==r.encoding&&(e=a.from(e,t),t=""),n=!0),I(this,e,t,!1,n)},A.prototype.unshift=function(e){return I(this,e,null,!0,!1)},A.prototype.isPaused=function(){return!1===this._readableState.flowing},A.prototype.setEncoding=function(e){l||(l=n(59).StringDecoder);var t=new l(e);this._readableState.decoder=t,this._readableState.encoding=this._readableState.decoder.encoding;for(var r=this._readableState.buffer.head,i="";null!==r;)i+=t.write(r.data),r=r.next;return this._readableState.buffer.clear(),""!==i&&this._readableState.buffer.push(i),this._readableState.length=i.length,this};function O(e,t){return e<=0||0===t.length&&t.ended?0:t.objectMode?1:e!=e?t.flowing&&t.length?t.buffer.head.data.length:t.length:(e>t.highWaterMark&&(t.highWaterMark=function(e){return e>=1073741824?e=1073741824:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function x(e){var t=e._readableState;c("emitReadable",t.needReadable,t.emittedReadable),t.needReadable=!1,t.emittedReadable||(c("emitReadable",t.flowing),t.emittedReadable=!0,r.nextTick(C,e))}function C(e){var t=e._readableState;c("emitReadable_",t.destroyed,t.length,t.ended),t.destroyed||!t.length&&!t.ended||(e.emit("readable"),t.emittedReadable=!1),t.needReadable=!t.flowing&&!t.ended&&t.length<=t.highWaterMark,j(e)}function T(e,t){t.readingMore||(t.readingMore=!0,r.nextTick(P,e,t))}function P(e,t){for(;!t.reading&&!t.ended&&(t.length<t.highWaterMark||t.flowing&&0===t.length);){var n=t.length;if(c("maybeReadMore read 0"),e.read(0),n===t.length)break}t.readingMore=!1}function N(e){var t=e._readableState;t.readableListening=e.listenerCount("readable")>0,t.resumeScheduled&&!t.paused?t.flowing=!0:e.listenerCount("data")>0&&e.resume()}function R(e){c("readable nexttick read 0"),e.read(0)}function L(e,t){c("resume",t.reading),t.reading||e.read(0),t.resumeScheduled=!1,e.emit("resume"),j(e),t.flowing&&!t.reading&&e.read(0)}function j(e){var t=e._readableState;for(c("flow",t.flowing);t.flowing&&null!==e.read(););}function D(e,t){return 0===t.length?null:(t.objectMode?n=t.buffer.shift():!e||e>=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.first():t.buffer.concat(t.length),t.buffer.clear()):n=t.buffer.consume(e,t.decoder),n);var n}function U(e){var t=e._readableState;c("endReadable",t.endEmitted),t.endEmitted||(t.ended=!0,r.nextTick(B,t,e))}function B(e,t){if(c("endReadableNT",e.endEmitted,e.length),!e.endEmitted&&0===e.length&&(e.endEmitted=!0,t.readable=!1,t.emit("end"),e.autoDestroy)){var n=t._writableState;(!n||n.autoDestroy&&n.finished)&&t.destroy()}}function F(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1}A.prototype.read=function(e){c("read",e),e=parseInt(e,10);var t=this._readableState,n=e;if(0!==e&&(t.emittedReadable=!1),0===e&&t.needReadable&&((0!==t.highWaterMark?t.length>=t.highWaterMark:t.length>0)||t.ended))return c("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?U(this):x(this),null;if(0===(e=O(e,t))&&t.ended)return 0===t.length&&U(this),null;var r,i=t.needReadable;return c("need readable",i),(0===t.length||t.length-e<t.highWaterMark)&&c("length less than watermark",i=!0),t.ended||t.reading?c("reading or ended",i=!1):i&&(c("do read"),t.reading=!0,t.sync=!0,0===t.length&&(t.needReadable=!0),this._read(t.highWaterMark),t.sync=!1,t.reading||(e=O(n,t))),null===(r=e>0?D(e,t):null)?(t.needReadable=t.length<=t.highWaterMark,e=0):(t.length-=e,t.awaitDrain=0),0===t.length&&(t.ended||(t.needReadable=!0),n!==e&&t.ended&&U(this)),null!==r&&this.emit("data",r),r},A.prototype._read=function(e){S(this,new w("_read()"))},A.prototype.pipe=function(e,t){var n=this,i=this._readableState;switch(i.pipesCount){case 0:i.pipes=e;break;case 1:i.pipes=[i.pipes,e];break;default:i.pipes.push(e)}i.pipesCount+=1,c("pipe count=%d opts=%j",i.pipesCount,t);var s=(!t||!1!==t.end)&&e!==r.stdout&&e!==r.stderr?u:g;function a(t,r){c("onunpipe"),t===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,c("cleanup"),e.removeListener("close",p),e.removeListener("finish",v),e.removeListener("drain",f),e.removeListener("error",h),e.removeListener("unpipe",a),n.removeListener("end",u),n.removeListener("end",g),n.removeListener("data",d),l=!0,!i.awaitDrain||e._writableState&&!e._writableState.needDrain||f())}function u(){c("onend"),e.end()}i.endEmitted?r.nextTick(s):n.once("end",s),e.on("unpipe",a);var f=function(e){return function(){var t=e._readableState;c("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&o(e,"data")&&(t.flowing=!0,j(e))}}(n);e.on("drain",f);var l=!1;function d(t){c("ondata");var r=e.write(t);c("dest.write",r),!1===r&&((1===i.pipesCount&&i.pipes===e||i.pipesCount>1&&-1!==F(i.pipes,e))&&!l&&(c("false write response, pause",i.awaitDrain),i.awaitDrain++),n.pause())}function h(t){c("onerror",t),g(),e.removeListener("error",h),0===o(e,"error")&&S(e,t)}function p(){e.removeListener("finish",v),g()}function v(){c("onfinish"),e.removeListener("close",p),g()}function g(){c("unpipe"),n.unpipe(e)}return n.on("data",d),function(e,t,n){if("function"==typeof e.prependListener)return e.prependListener(t,n);e._events&&e._events[t]?Array.isArray(e._events[t])?e._events[t].unshift(n):e._events[t]=[n,e._events[t]]:e.on(t,n)}(e,"error",h),e.once("close",p),e.once("finish",v),e.emit("pipe",n),i.flowing||(c("pipe resume"),n.resume()),e},A.prototype.unpipe=function(e){var t=this._readableState,n={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes||(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,n)),this;if(!e){var r=t.pipes,i=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var o=0;o<i;o++)r[o].emit("unpipe",this,{hasUnpiped:!1});return this}var s=F(t.pipes,e);return-1===s||(t.pipes.splice(s,1),t.pipesCount-=1,1===t.pipesCount&&(t.pipes=t.pipes[0]),e.emit("unpipe",this,n)),this},A.prototype.on=function(e,t){var n=s.prototype.on.call(this,e,t),i=this._readableState;return"data"===e?(i.readableListening=this.listenerCount("readable")>0,!1!==i.flowing&&this.resume()):"readable"===e&&(i.endEmitted||i.readableListening||(i.readableListening=i.needReadable=!0,i.flowing=!1,i.emittedReadable=!1,c("on readable",i.length,i.reading),i.length?x(this):i.reading||r.nextTick(R,this))),n},A.prototype.addListener=A.prototype.on,A.prototype.removeListener=function(e,t){var n=s.prototype.removeListener.call(this,e,t);return"readable"===e&&r.nextTick(N,this),n},A.prototype.removeAllListeners=function(e){var t=s.prototype.removeAllListeners.apply(this,arguments);return"readable"!==e&&void 0!==e||r.nextTick(N,this),t},A.prototype.resume=function(){var e=this._readableState;return e.flowing||(c("resume"),e.flowing=!e.readableListening,function(e,t){t.resumeScheduled||(t.resumeScheduled=!0,r.nextTick(L,e,t))}(this,e)),e.paused=!1,this},A.prototype.pause=function(){return c("call pause flowing=%j",this._readableState.flowing),!1!==this._readableState.flowing&&(c("pause"),this._readableState.flowing=!1,this.emit("pause")),this._readableState.paused=!0,this},A.prototype.wrap=function(e){var t=this,n=this._readableState,r=!1;for(var i in e.on("end",(function(){if(c("wrapped end"),n.decoder&&!n.ended){var e=n.decoder.end();e&&e.length&&t.push(e)}t.push(null)})),e.on("data",(function(i){(c("wrapped data"),n.decoder&&(i=n.decoder.write(i)),n.objectMode&&null==i)||(n.objectMode||i&&i.length)&&(t.push(i)||(r=!0,e.pause()))})),e)void 0===this[i]&&"function"==typeof e[i]&&(this[i]=function(t){return function(){return e[t].apply(e,arguments)}}(i));for(var o=0;o<E.length;o++)e.on(E[o],this.emit.bind(this,E[o]));return this._read=function(t){c("wrapped _read",t),r&&(r=!1,e.resume())},this},"function"==typeof Symbol&&(A.prototype[Symbol.asyncIterator]=function(){return void 0===d&&(d=n(277)),d(this)}),Object.defineProperty(A.prototype,"readableHighWaterMark",{enumerable:!1,get:function(){return this._readableState.highWaterMark}}),Object.defineProperty(A.prototype,"readableBuffer",{enumerable:!1,get:function(){return this._readableState&&this._readableState.buffer}}),Object.defineProperty(A.prototype,"readableFlowing",{enumerable:!1,get:function(){return this._readableState.flowing},set:function(e){this._readableState&&(this._readableState.flowing=e)}}),A._fromList=D,Object.defineProperty(A.prototype,"readableLength",{enumerable:!1,get:function(){return this._readableState.length}}),"function"==typeof Symbol&&(A.from=function(e,t){return void 0===h&&(h=n(278)),h(A,e,t)})}).call(this,n(31),n(20))},function(e,t,n){e.exports=n(49).EventEmitter},function(e,t,n){"use strict";(function(t){function n(e,t){i(e,t),r(e)}function r(e){e._writableState&&!e._writableState.emitClose||e._readableState&&!e._readableState.emitClose||e.emit("close")}function i(e,t){e.emit("error",t)}e.exports={destroy:function(e,o){var s=this,a=this._readableState&&this._readableState.destroyed,u=this._writableState&&this._writableState.destroyed;return a||u?(o?o(e):e&&(this._writableState?this._writableState.errorEmitted||(this._writableState.errorEmitted=!0,t.nextTick(i,this,e)):t.nextTick(i,this,e)),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,(function(e){!o&&e?s._writableState?s._writableState.errorEmitted?t.nextTick(r,s):(s._writableState.errorEmitted=!0,t.nextTick(n,s,e)):t.nextTick(n,s,e):o?(t.nextTick(r,s),o(e)):t.nextTick(r,s)})),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finalCalled=!1,this._writableState.prefinished=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)},errorOrDestroy:function(e,t){var n=e._readableState,r=e._writableState;n&&n.autoDestroy||r&&r.autoDestroy?e.destroy(t):e.emit("error",t)}}}).call(this,n(20))},function(e,t,n){"use strict";var r=n(67).codes.ERR_INVALID_OPT_VALUE;e.exports={getHighWaterMark:function(e,t,n,i){var o=function(e,t,n){return null!=e.highWaterMark?e.highWaterMark:t?e[n]:null}(t,i,n);if(null!=o){if(!isFinite(o)||Math.floor(o)!==o||o<0)throw new r(i?n:"highWaterMark",o);return Math.floor(o)}return e.objectMode?16:16384}}},function(e,t,n){"use strict";(function(t,r){function i(e){var t=this;this.next=null,this.entry=null,this.finish=function(){!function(e,t,n){var r=e.entry;e.entry=null;for(;r;){var i=r.callback;t.pendingcb--,i(n),r=r.next}t.corkedRequestsFree.next=e}(t,e)}}var o;e.exports=A,A.WritableState=M;var s={deprecate:n(114)},a=n(164),u=n(6).Buffer,c=t.Uint8Array||function(){};var f,l=n(165),d=n(166).getHighWaterMark,h=n(67).codes,p=h.ERR_INVALID_ARG_TYPE,v=h.ERR_METHOD_NOT_IMPLEMENTED,g=h.ERR_MULTIPLE_CALLBACK,m=h.ERR_STREAM_CANNOT_PIPE,b=h.ERR_STREAM_DESTROYED,y=h.ERR_STREAM_NULL_VALUES,w=h.ERR_STREAM_WRITE_AFTER_END,_=h.ERR_UNKNOWN_ENCODING,S=l.errorOrDestroy;function E(){}function M(e,t,s){o=o||n(68),e=e||{},"boolean"!=typeof s&&(s=t instanceof o),this.objectMode=!!e.objectMode,s&&(this.objectMode=this.objectMode||!!e.writableObjectMode),this.highWaterMark=d(this,e,"writableHighWaterMark",s),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var a=!1===e.decodeStrings;this.decodeStrings=!a,this.defaultEncoding=e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){!function(e,t){var n=e._writableState,i=n.sync,o=n.writecb;if("function"!=typeof o)throw new g;if(function(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}(n),t)!function(e,t,n,i,o){--t.pendingcb,n?(r.nextTick(o,i),r.nextTick(T,e,t),e._writableState.errorEmitted=!0,S(e,i)):(o(i),e._writableState.errorEmitted=!0,S(e,i),T(e,t))}(e,n,i,t,o);else{var s=x(n)||e.destroyed;s||n.corked||n.bufferProcessing||!n.bufferedRequest||O(e,n),i?r.nextTick(k,e,n,s,o):k(e,n,s,o)}}(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.emitClose=!1!==e.emitClose,this.autoDestroy=!!e.autoDestroy,this.bufferedRequestCount=0,this.corkedRequestsFree=new i(this)}function A(e){var t=this instanceof(o=o||n(68));if(!t&&!f.call(A,this))return new A(e);this._writableState=new M(e,this,t),this.writable=!0,e&&("function"==typeof e.write&&(this._write=e.write),"function"==typeof e.writev&&(this._writev=e.writev),"function"==typeof e.destroy&&(this._destroy=e.destroy),"function"==typeof e.final&&(this._final=e.final)),a.call(this)}function I(e,t,n,r,i,o,s){t.writelen=r,t.writecb=s,t.writing=!0,t.sync=!0,t.destroyed?t.onwrite(new b("write")):n?e._writev(i,t.onwrite):e._write(i,o,t.onwrite),t.sync=!1}function k(e,t,n,r){n||function(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit("drain"))}(e,t),t.pendingcb--,r(),T(e,t)}function O(e,t){t.bufferProcessing=!0;var n=t.bufferedRequest;if(e._writev&&n&&n.next){var r=t.bufferedRequestCount,o=new Array(r),s=t.corkedRequestsFree;s.entry=n;for(var a=0,u=!0;n;)o[a]=n,n.isBuf||(u=!1),n=n.next,a+=1;o.allBuffers=u,I(e,t,!0,t.length,o,"",s.finish),t.pendingcb++,t.lastBufferedRequest=null,s.next?(t.corkedRequestsFree=s.next,s.next=null):t.corkedRequestsFree=new i(t),t.bufferedRequestCount=0}else{for(;n;){var c=n.chunk,f=n.encoding,l=n.callback;if(I(e,t,!1,t.objectMode?1:c.length,c,f,l),n=n.next,t.bufferedRequestCount--,t.writing)break}null===n&&(t.lastBufferedRequest=null)}t.bufferedRequest=n,t.bufferProcessing=!1}function x(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function C(e,t){e._final((function(n){t.pendingcb--,n&&S(e,n),t.prefinished=!0,e.emit("prefinish"),T(e,t)}))}function T(e,t){var n=x(t);if(n&&(function(e,t){t.prefinished||t.finalCalled||("function"!=typeof e._final||t.destroyed?(t.prefinished=!0,e.emit("prefinish")):(t.pendingcb++,t.finalCalled=!0,r.nextTick(C,e,t)))}(e,t),0===t.pendingcb&&(t.finished=!0,e.emit("finish"),t.autoDestroy))){var i=e._readableState;(!i||i.autoDestroy&&i.endEmitted)&&e.destroy()}return n}n(7)(A,a),M.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(M.prototype,"buffer",{get:s.deprecate((function(){return this.getBuffer()}),"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(e){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(f=Function.prototype[Symbol.hasInstance],Object.defineProperty(A,Symbol.hasInstance,{value:function(e){return!!f.call(this,e)||this===A&&(e&&e._writableState instanceof M)}})):f=function(e){return e instanceof this},A.prototype.pipe=function(){S(this,new m)},A.prototype.write=function(e,t,n){var i,o=this._writableState,s=!1,a=!o.objectMode&&(i=e,u.isBuffer(i)||i instanceof c);return a&&!u.isBuffer(e)&&(e=function(e){return u.from(e)}(e)),"function"==typeof t&&(n=t,t=null),a?t="buffer":t||(t=o.defaultEncoding),"function"!=typeof n&&(n=E),o.ending?function(e,t){var n=new w;S(e,n),r.nextTick(t,n)}(this,n):(a||function(e,t,n,i){var o;return null===n?o=new y:"string"==typeof n||t.objectMode||(o=new p("chunk",["string","Buffer"],n)),!o||(S(e,o),r.nextTick(i,o),!1)}(this,o,e,n))&&(o.pendingcb++,s=function(e,t,n,r,i,o){if(!n){var s=function(e,t,n){e.objectMode||!1===e.decodeStrings||"string"!=typeof t||(t=u.from(t,n));return t}(t,r,i);r!==s&&(n=!0,i="buffer",r=s)}var a=t.objectMode?1:r.length;t.length+=a;var c=t.length<t.highWaterMark;c||(t.needDrain=!0);if(t.writing||t.corked){var f=t.lastBufferedRequest;t.lastBufferedRequest={chunk:r,encoding:i,isBuf:n,callback:o,next:null},f?f.next=t.lastBufferedRequest:t.bufferedRequest=t.lastBufferedRequest,t.bufferedRequestCount+=1}else I(e,t,!1,a,r,i,o);return c}(this,o,a,e,t,n)),s},A.prototype.cork=function(){this._writableState.corked++},A.prototype.uncork=function(){var e=this._writableState;e.corked&&(e.corked--,e.writing||e.corked||e.bufferProcessing||!e.bufferedRequest||O(this,e))},A.prototype.setDefaultEncoding=function(e){if("string"==typeof e&&(e=e.toLowerCase()),!(["hex","utf8","utf-8","ascii","binary","base64","ucs2","ucs-2","utf16le","utf-16le","raw"].indexOf((e+"").toLowerCase())>-1))throw new _(e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(A.prototype,"writableBuffer",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}}),Object.defineProperty(A.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),A.prototype._write=function(e,t,n){n(new v("_write()"))},A.prototype._writev=null,A.prototype.end=function(e,t,n){var i=this._writableState;return"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!=e&&this.write(e,t),i.corked&&(i.corked=1,this.uncork()),i.ending||function(e,t,n){t.ending=!0,T(e,t),n&&(t.finished?r.nextTick(n):e.once("finish",n));t.ended=!0,e.writable=!1}(this,i,n),this},Object.defineProperty(A.prototype,"writableLength",{enumerable:!1,get:function(){return this._writableState.length}}),Object.defineProperty(A.prototype,"destroyed",{enumerable:!1,get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),A.prototype.destroy=l.destroy,A.prototype._undestroy=l.undestroy,A.prototype._destroy=function(e,t){t(e)}}).call(this,n(31),n(20))},function(e,t,n){"use strict";e.exports=f;var r=n(67).codes,i=r.ERR_METHOD_NOT_IMPLEMENTED,o=r.ERR_MULTIPLE_CALLBACK,s=r.ERR_TRANSFORM_ALREADY_TRANSFORMING,a=r.ERR_TRANSFORM_WITH_LENGTH_0,u=n(68);function c(e,t){var n=this._transformState;n.transforming=!1;var r=n.writecb;if(null===r)return this.emit("error",new o);n.writechunk=null,n.writecb=null,null!=t&&this.push(t),r(e);var i=this._readableState;i.reading=!1,(i.needReadable||i.length<i.highWaterMark)&&this._read(i.highWaterMark)}function f(e){if(!(this instanceof f))return new f(e);u.call(this,e),this._transformState={afterTransform:c.bind(this),needTransform:!1,transforming:!1,writecb:null,writechunk:null,writeencoding:null},this._readableState.needReadable=!0,this._readableState.sync=!1,e&&("function"==typeof e.transform&&(this._transform=e.transform),"function"==typeof e.flush&&(this._flush=e.flush)),this.on("prefinish",l)}function l(){var e=this;"function"!=typeof this._flush||this._readableState.destroyed?d(this,null,null):this._flush((function(t,n){d(e,t,n)}))}function d(e,t,n){if(t)return e.emit("error",t);if(null!=n&&e.push(n),e._writableState.length)throw new a;if(e._transformState.transforming)throw new s;return e.push(null)}n(7)(f,u),f.prototype.push=function(e,t){return this._transformState.needTransform=!1,u.prototype.push.call(this,e,t)},f.prototype._transform=function(e,t,n){n(new i("_transform()"))},f.prototype._write=function(e,t,n){var r=this._transformState;if(r.writecb=n,r.writechunk=e,r.writeencoding=t,!r.transforming){var i=this._readableState;(r.needTransform||i.needReadable||i.length<i.highWaterMark)&&this._read(i.highWaterMark)}},f.prototype._read=function(e){var t=this._transformState;null===t.writechunk||t.transforming?t.needTransform=!0:(t.transforming=!0,this._transform(t.writechunk,t.writeencoding,t.afterTransform))},f.prototype._destroy=function(e,t){u.prototype._destroy.call(this,e,(function(e){t(e)}))}},function(e,t,n){var r=n(7),i=n(69),o=n(8).Buffer,s=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],a=new Array(64);function u(){this.init(),this._w=a,i.call(this,64,56)}function c(e,t,n){return n^e&(t^n)}function f(e,t,n){return e&t|n&(e|t)}function l(e){return(e>>>2|e<<30)^(e>>>13|e<<19)^(e>>>22|e<<10)}function d(e){return(e>>>6|e<<26)^(e>>>11|e<<21)^(e>>>25|e<<7)}function h(e){return(e>>>7|e<<25)^(e>>>18|e<<14)^e>>>3}r(u,i),u.prototype.init=function(){return this._a=1779033703,this._b=3144134277,this._c=1013904242,this._d=2773480762,this._e=1359893119,this._f=2600822924,this._g=528734635,this._h=1541459225,this},u.prototype._update=function(e){for(var t,n=this._w,r=0|this._a,i=0|this._b,o=0|this._c,a=0|this._d,u=0|this._e,p=0|this._f,v=0|this._g,g=0|this._h,m=0;m<16;++m)n[m]=e.readInt32BE(4*m);for(;m<64;++m)n[m]=0|(((t=n[m-2])>>>17|t<<15)^(t>>>19|t<<13)^t>>>10)+n[m-7]+h(n[m-15])+n[m-16];for(var b=0;b<64;++b){var y=g+d(u)+c(u,p,v)+s[b]+n[b]|0,w=l(r)+f(r,i,o)|0;g=v,v=p,p=u,u=a+y|0,a=o,o=i,i=r,r=y+w|0}this._a=r+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=a+this._d|0,this._e=u+this._e|0,this._f=p+this._f|0,this._g=v+this._g|0,this._h=g+this._h|0},u.prototype._hash=function(){var e=o.allocUnsafe(32);return e.writeInt32BE(this._a,0),e.writeInt32BE(this._b,4),e.writeInt32BE(this._c,8),e.writeInt32BE(this._d,12),e.writeInt32BE(this._e,16),e.writeInt32BE(this._f,20),e.writeInt32BE(this._g,24),e.writeInt32BE(this._h,28),e},e.exports=u},function(e,t,n){var r=n(7),i=n(69),o=n(8).Buffer,s=[1116352408,3609767458,1899447441,602891725,3049323471,3964484399,3921009573,2173295548,961987163,4081628472,1508970993,3053834265,2453635748,2937671579,2870763221,3664609560,3624381080,2734883394,310598401,1164996542,607225278,1323610764,1426881987,3590304994,1925078388,4068182383,2162078206,991336113,2614888103,633803317,3248222580,3479774868,3835390401,2666613458,4022224774,944711139,264347078,2341262773,604807628,2007800933,770255983,1495990901,1249150122,1856431235,1555081692,3175218132,1996064986,2198950837,2554220882,3999719339,2821834349,766784016,2952996808,2566594879,3210313671,3203337956,3336571891,1034457026,3584528711,2466948901,113926993,3758326383,338241895,168717936,666307205,1188179964,773529912,1546045734,1294757372,1522805485,1396182291,2643833823,1695183700,2343527390,1986661051,1014477480,2177026350,1206759142,2456956037,344077627,2730485921,1290863460,2820302411,3158454273,3259730800,3505952657,3345764771,106217008,3516065817,3606008344,3600352804,1432725776,4094571909,1467031594,275423344,851169720,430227734,3100823752,506948616,1363258195,659060556,3750685593,883997877,3785050280,958139571,3318307427,1322822218,3812723403,1537002063,2003034995,1747873779,3602036899,1955562222,1575990012,2024104815,1125592928,2227730452,2716904306,2361852424,442776044,2428436474,593698344,2756734187,3733110249,3204031479,2999351573,3329325298,3815920427,3391569614,3928383900,3515267271,566280711,3940187606,3454069534,4118630271,4000239992,116418474,1914138554,174292421,2731055270,289380356,3203993006,460393269,320620315,685471733,587496836,852142971,1086792851,1017036298,365543100,1126000580,2618297676,1288033470,3409855158,1501505948,4234509866,1607167915,987167468,1816402316,1246189591],a=new Array(160);function u(){this.init(),this._w=a,i.call(this,128,112)}function c(e,t,n){return n^e&(t^n)}function f(e,t,n){return e&t|n&(e|t)}function l(e,t){return(e>>>28|t<<4)^(t>>>2|e<<30)^(t>>>7|e<<25)}function d(e,t){return(e>>>14|t<<18)^(e>>>18|t<<14)^(t>>>9|e<<23)}function h(e,t){return(e>>>1|t<<31)^(e>>>8|t<<24)^e>>>7}function p(e,t){return(e>>>1|t<<31)^(e>>>8|t<<24)^(e>>>7|t<<25)}function v(e,t){return(e>>>19|t<<13)^(t>>>29|e<<3)^e>>>6}function g(e,t){return(e>>>19|t<<13)^(t>>>29|e<<3)^(e>>>6|t<<26)}function m(e,t){return e>>>0<t>>>0?1:0}r(u,i),u.prototype.init=function(){return this._ah=1779033703,this._bh=3144134277,this._ch=1013904242,this._dh=2773480762,this._eh=1359893119,this._fh=2600822924,this._gh=528734635,this._hh=1541459225,this._al=4089235720,this._bl=2227873595,this._cl=4271175723,this._dl=1595750129,this._el=2917565137,this._fl=725511199,this._gl=4215389547,this._hl=327033209,this},u.prototype._update=function(e){for(var t=this._w,n=0|this._ah,r=0|this._bh,i=0|this._ch,o=0|this._dh,a=0|this._eh,u=0|this._fh,b=0|this._gh,y=0|this._hh,w=0|this._al,_=0|this._bl,S=0|this._cl,E=0|this._dl,M=0|this._el,A=0|this._fl,I=0|this._gl,k=0|this._hl,O=0;O<32;O+=2)t[O]=e.readInt32BE(4*O),t[O+1]=e.readInt32BE(4*O+4);for(;O<160;O+=2){var x=t[O-30],C=t[O-30+1],T=h(x,C),P=p(C,x),N=v(x=t[O-4],C=t[O-4+1]),R=g(C,x),L=t[O-14],j=t[O-14+1],D=t[O-32],U=t[O-32+1],B=P+j|0,F=T+L+m(B,P)|0;F=(F=F+N+m(B=B+R|0,R)|0)+D+m(B=B+U|0,U)|0,t[O]=F,t[O+1]=B}for(var z=0;z<160;z+=2){F=t[z],B=t[z+1];var q=f(n,r,i),K=f(w,_,S),H=l(n,w),V=l(w,n),G=d(a,M),W=d(M,a),$=s[z],Y=s[z+1],J=c(a,u,b),Z=c(M,A,I),X=k+W|0,Q=y+G+m(X,k)|0;Q=(Q=(Q=Q+J+m(X=X+Z|0,Z)|0)+$+m(X=X+Y|0,Y)|0)+F+m(X=X+B|0,B)|0;var ee=V+K|0,te=H+q+m(ee,V)|0;y=b,k=I,b=u,I=A,u=a,A=M,a=o+Q+m(M=E+X|0,E)|0,o=i,E=S,i=r,S=_,r=n,_=w,n=Q+te+m(w=X+ee|0,X)|0}this._al=this._al+w|0,this._bl=this._bl+_|0,this._cl=this._cl+S|0,this._dl=this._dl+E|0,this._el=this._el+M|0,this._fl=this._fl+A|0,this._gl=this._gl+I|0,this._hl=this._hl+k|0,this._ah=this._ah+n+m(this._al,w)|0,this._bh=this._bh+r+m(this._bl,_)|0,this._ch=this._ch+i+m(this._cl,S)|0,this._dh=this._dh+o+m(this._dl,E)|0,this._eh=this._eh+a+m(this._el,M)|0,this._fh=this._fh+u+m(this._fl,A)|0,this._gh=this._gh+b+m(this._gl,I)|0,this._hh=this._hh+y+m(this._hl,k)|0},u.prototype._hash=function(){var e=o.allocUnsafe(64);function t(t,n,r){e.writeInt32BE(t,r),e.writeInt32BE(n,r+4)}return t(this._ah,this._al,0),t(this._bh,this._bl,8),t(this._ch,this._cl,16),t(this._dh,this._dl,24),t(this._eh,this._el,32),t(this._fh,this._fl,40),t(this._gh,this._gl,48),t(this._hh,this._hl,56),e},e.exports=u},function(e,t,n){"use strict";(function(t,r){var i=n(92);e.exports=y;var o,s=n(160);y.ReadableState=b;n(49).EventEmitter;var a=function(e,t){return e.listeners(t).length},u=n(172),c=n(119).Buffer,f=t.Uint8Array||function(){};var l=Object.create(n(80));l.inherits=n(7);var d=n(286),h=void 0;h=d&&d.debuglog?d.debuglog("stream"):function(){};var p,v=n(287),g=n(173);l.inherits(y,u);var m=["error","close","destroy","pause","resume"];function b(e,t){e=e||{};var r=t instanceof(o=o||n(60));this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.readableObjectMode);var i=e.highWaterMark,s=e.readableHighWaterMark,a=this.objectMode?16:16384;this.highWaterMark=i||0===i?i:r&&(s||0===s)?s:a,this.highWaterMark=Math.floor(this.highWaterMark),this.buffer=new v,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.destroyed=!1,this.defaultEncoding=e.defaultEncoding||"utf8",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,e.encoding&&(p||(p=n(59).StringDecoder),this.decoder=new p(e.encoding),this.encoding=e.encoding)}function y(e){if(o=o||n(60),!(this instanceof y))return new y(e);this._readableState=new b(e,this),this.readable=!0,e&&("function"==typeof e.read&&(this._read=e.read),"function"==typeof e.destroy&&(this._destroy=e.destroy)),u.call(this)}function w(e,t,n,r,i){var o,s=e._readableState;null===t?(s.reading=!1,function(e,t){if(t.ended)return;if(t.decoder){var n=t.decoder.end();n&&n.length&&(t.buffer.push(n),t.length+=t.objectMode?1:n.length)}t.ended=!0,E(e)}(e,s)):(i||(o=function(e,t){var n;r=t,c.isBuffer(r)||r instanceof f||"string"==typeof t||void 0===t||e.objectMode||(n=new TypeError("Invalid non-string/buffer chunk"));var r;return n}(s,t)),o?e.emit("error",o):s.objectMode||t&&t.length>0?("string"==typeof t||s.objectMode||Object.getPrototypeOf(t)===c.prototype||(t=function(e){return c.from(e)}(t)),r?s.endEmitted?e.emit("error",new Error("stream.unshift() after end event")):_(e,s,t,!0):s.ended?e.emit("error",new Error("stream.push() after EOF")):(s.reading=!1,s.decoder&&!n?(t=s.decoder.write(t),s.objectMode||0!==t.length?_(e,s,t,!1):A(e,s)):_(e,s,t,!1))):r||(s.reading=!1));return function(e){return!e.ended&&(e.needReadable||e.length<e.highWaterMark||0===e.length)}(s)}function _(e,t,n,r){t.flowing&&0===t.length&&!t.sync?(e.emit("data",n),e.read(0)):(t.length+=t.objectMode?1:n.length,r?t.buffer.unshift(n):t.buffer.push(n),t.needReadable&&E(e)),A(e,t)}Object.defineProperty(y.prototype,"destroyed",{get:function(){return void 0!==this._readableState&&this._readableState.destroyed},set:function(e){this._readableState&&(this._readableState.destroyed=e)}}),y.prototype.destroy=g.destroy,y.prototype._undestroy=g.undestroy,y.prototype._destroy=function(e,t){this.push(null),t(e)},y.prototype.push=function(e,t){var n,r=this._readableState;return r.objectMode?n=!0:"string"==typeof e&&((t=t||r.defaultEncoding)!==r.encoding&&(e=c.from(e,t),t=""),n=!0),w(this,e,t,!1,n)},y.prototype.unshift=function(e){return w(this,e,null,!0,!1)},y.prototype.isPaused=function(){return!1===this._readableState.flowing},y.prototype.setEncoding=function(e){return p||(p=n(59).StringDecoder),this._readableState.decoder=new p(e),this._readableState.encoding=e,this};function S(e,t){return e<=0||0===t.length&&t.ended?0:t.objectMode?1:e!=e?t.flowing&&t.length?t.buffer.head.data.length:t.length:(e>t.highWaterMark&&(t.highWaterMark=function(e){return e>=8388608?e=8388608:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function E(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(h("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?i.nextTick(M,e):M(e))}function M(e){h("emit readable"),e.emit("readable"),x(e)}function A(e,t){t.readingMore||(t.readingMore=!0,i.nextTick(I,e,t))}function I(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length<t.highWaterMark&&(h("maybeReadMore read 0"),e.read(0),n!==t.length);)n=t.length;t.readingMore=!1}function k(e){h("readable nexttick read 0"),e.read(0)}function O(e,t){t.reading||(h("resume read 0"),e.read(0)),t.resumeScheduled=!1,t.awaitDrain=0,e.emit("resume"),x(e),t.flowing&&!t.reading&&e.read(0)}function x(e){var t=e._readableState;for(h("flow",t.flowing);t.flowing&&null!==e.read(););}function C(e,t){return 0===t.length?null:(t.objectMode?n=t.buffer.shift():!e||e>=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):n=function(e,t,n){var r;e<t.head.data.length?(r=t.head.data.slice(0,e),t.head.data=t.head.data.slice(e)):r=e===t.head.data.length?t.shift():n?function(e,t){var n=t.head,r=1,i=n.data;e-=i.length;for(;n=n.next;){var o=n.data,s=e>o.length?o.length:e;if(s===o.length?i+=o:i+=o.slice(0,e),0===(e-=s)){s===o.length?(++r,n.next?t.head=n.next:t.head=t.tail=null):(t.head=n,n.data=o.slice(s));break}++r}return t.length-=r,i}(e,t):function(e,t){var n=c.allocUnsafe(e),r=t.head,i=1;r.data.copy(n),e-=r.data.length;for(;r=r.next;){var o=r.data,s=e>o.length?o.length:e;if(o.copy(n,n.length-e,0,s),0===(e-=s)){s===o.length?(++i,r.next?t.head=r.next:t.head=t.tail=null):(t.head=r,r.data=o.slice(s));break}++i}return t.length-=i,n}(e,t);return r}(e,t.buffer,t.decoder),n);var n}function T(e){var t=e._readableState;if(t.length>0)throw new Error('"endReadable()" called on non-empty stream');t.endEmitted||(t.ended=!0,i.nextTick(P,t,e))}function P(e,t){e.endEmitted||0!==e.length||(e.endEmitted=!0,t.readable=!1,t.emit("end"))}function N(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1}y.prototype.read=function(e){h("read",e),e=parseInt(e,10);var t=this._readableState,n=e;if(0!==e&&(t.emittedReadable=!1),0===e&&t.needReadable&&(t.length>=t.highWaterMark||t.ended))return h("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?T(this):E(this),null;if(0===(e=S(e,t))&&t.ended)return 0===t.length&&T(this),null;var r,i=t.needReadable;return h("need readable",i),(0===t.length||t.length-e<t.highWaterMark)&&h("length less than watermark",i=!0),t.ended||t.reading?h("reading or ended",i=!1):i&&(h("do read"),t.reading=!0,t.sync=!0,0===t.length&&(t.needReadable=!0),this._read(t.highWaterMark),t.sync=!1,t.reading||(e=S(n,t))),null===(r=e>0?C(e,t):null)?(t.needReadable=!0,e=0):t.length-=e,0===t.length&&(t.ended||(t.needReadable=!0),n!==e&&t.ended&&T(this)),null!==r&&this.emit("data",r),r},y.prototype._read=function(e){this.emit("error",new Error("_read() is not implemented"))},y.prototype.pipe=function(e,t){var n=this,o=this._readableState;switch(o.pipesCount){case 0:o.pipes=e;break;case 1:o.pipes=[o.pipes,e];break;default:o.pipes.push(e)}o.pipesCount+=1,h("pipe count=%d opts=%j",o.pipesCount,t);var u=(!t||!1!==t.end)&&e!==r.stdout&&e!==r.stderr?f:y;function c(t,r){h("onunpipe"),t===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,h("cleanup"),e.removeListener("close",m),e.removeListener("finish",b),e.removeListener("drain",l),e.removeListener("error",g),e.removeListener("unpipe",c),n.removeListener("end",f),n.removeListener("end",y),n.removeListener("data",v),d=!0,!o.awaitDrain||e._writableState&&!e._writableState.needDrain||l())}function f(){h("onend"),e.end()}o.endEmitted?i.nextTick(u):n.once("end",u),e.on("unpipe",c);var l=function(e){return function(){var t=e._readableState;h("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&a(e,"data")&&(t.flowing=!0,x(e))}}(n);e.on("drain",l);var d=!1;var p=!1;function v(t){h("ondata"),p=!1,!1!==e.write(t)||p||((1===o.pipesCount&&o.pipes===e||o.pipesCount>1&&-1!==N(o.pipes,e))&&!d&&(h("false write response, pause",n._readableState.awaitDrain),n._readableState.awaitDrain++,p=!0),n.pause())}function g(t){h("onerror",t),y(),e.removeListener("error",g),0===a(e,"error")&&e.emit("error",t)}function m(){e.removeListener("finish",b),y()}function b(){h("onfinish"),e.removeListener("close",m),y()}function y(){h("unpipe"),n.unpipe(e)}return n.on("data",v),function(e,t,n){if("function"==typeof e.prependListener)return e.prependListener(t,n);e._events&&e._events[t]?s(e._events[t])?e._events[t].unshift(n):e._events[t]=[n,e._events[t]]:e.on(t,n)}(e,"error",g),e.once("close",m),e.once("finish",b),e.emit("pipe",n),o.flowing||(h("pipe resume"),n.resume()),e},y.prototype.unpipe=function(e){var t=this._readableState,n={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes||(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,n)),this;if(!e){var r=t.pipes,i=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var o=0;o<i;o++)r[o].emit("unpipe",this,n);return this}var s=N(t.pipes,e);return-1===s||(t.pipes.splice(s,1),t.pipesCount-=1,1===t.pipesCount&&(t.pipes=t.pipes[0]),e.emit("unpipe",this,n)),this},y.prototype.on=function(e,t){var n=u.prototype.on.call(this,e,t);if("data"===e)!1!==this._readableState.flowing&&this.resume();else if("readable"===e){var r=this._readableState;r.endEmitted||r.readableListening||(r.readableListening=r.needReadable=!0,r.emittedReadable=!1,r.reading?r.length&&E(this):i.nextTick(k,this))}return n},y.prototype.addListener=y.prototype.on,y.prototype.resume=function(){var e=this._readableState;return e.flowing||(h("resume"),e.flowing=!0,function(e,t){t.resumeScheduled||(t.resumeScheduled=!0,i.nextTick(O,e,t))}(this,e)),this},y.prototype.pause=function(){return h("call pause flowing=%j",this._readableState.flowing),!1!==this._readableState.flowing&&(h("pause"),this._readableState.flowing=!1,this.emit("pause")),this},y.prototype.wrap=function(e){var t=this,n=this._readableState,r=!1;for(var i in e.on("end",(function(){if(h("wrapped end"),n.decoder&&!n.ended){var e=n.decoder.end();e&&e.length&&t.push(e)}t.push(null)})),e.on("data",(function(i){(h("wrapped data"),n.decoder&&(i=n.decoder.write(i)),n.objectMode&&null==i)||(n.objectMode||i&&i.length)&&(t.push(i)||(r=!0,e.pause()))})),e)void 0===this[i]&&"function"==typeof e[i]&&(this[i]=function(t){return function(){return e[t].apply(e,arguments)}}(i));for(var o=0;o<m.length;o++)e.on(m[o],this.emit.bind(this,m[o]));return this._read=function(t){h("wrapped _read",t),r&&(r=!1,e.resume())},this},Object.defineProperty(y.prototype,"readableHighWaterMark",{enumerable:!1,get:function(){return this._readableState.highWaterMark}}),y._fromList=C}).call(this,n(31),n(20))},function(e,t,n){e.exports=n(49).EventEmitter},function(e,t,n){"use strict";var r=n(92);function i(e,t){e.emit("error",t)}e.exports={destroy:function(e,t){var n=this,o=this._readableState&&this._readableState.destroyed,s=this._writableState&&this._writableState.destroyed;return o||s?(t?t(e):!e||this._writableState&&this._writableState.errorEmitted||r.nextTick(i,this,e),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,(function(e){!t&&e?(r.nextTick(i,n,e),n._writableState&&(n._writableState.errorEmitted=!0)):t&&t(e)})),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},function(e,t,n){"use strict";e.exports=s;var r=n(60),i=Object.create(n(80));function o(e,t){var n=this._transformState;n.transforming=!1;var r=n.writecb;if(!r)return this.emit("error",new Error("write callback called multiple times"));n.writechunk=null,n.writecb=null,null!=t&&this.push(t),r(e);var i=this._readableState;i.reading=!1,(i.needReadable||i.length<i.highWaterMark)&&this._read(i.highWaterMark)}function s(e){if(!(this instanceof s))return new s(e);r.call(this,e),this._transformState={afterTransform:o.bind(this),needTransform:!1,transforming:!1,writecb:null,writechunk:null,writeencoding:null},this._readableState.needReadable=!0,this._readableState.sync=!1,e&&("function"==typeof e.transform&&(this._transform=e.transform),"function"==typeof e.flush&&(this._flush=e.flush)),this.on("prefinish",a)}function a(){var e=this;"function"==typeof this._flush?this._flush((function(t,n){u(e,t,n)})):u(this,null,null)}function u(e,t,n){if(t)return e.emit("error",t);if(null!=n&&e.push(n),e._writableState.length)throw new Error("Calling transform done when ws.length != 0");if(e._transformState.transforming)throw new Error("Calling transform done when still transforming");return e.push(null)}i.inherits=n(7),i.inherits(s,r),s.prototype.push=function(e,t){return this._transformState.needTransform=!1,r.prototype.push.call(this,e,t)},s.prototype._transform=function(e,t,n){throw new Error("_transform() is not implemented")},s.prototype._write=function(e,t,n){var r=this._transformState;if(r.writecb=n,r.writechunk=e,r.writeencoding=t,!r.transforming){var i=this._readableState;(r.needTransform||i.needReadable||i.length<i.highWaterMark)&&this._read(i.highWaterMark)}},s.prototype._read=function(e){var t=this._transformState;null!==t.writechunk&&t.writecb&&!t.transforming?(t.transforming=!0,this._transform(t.writechunk,t.writeencoding,t.afterTransform)):t.needTransform=!0},s.prototype._destroy=function(e,t){var n=this;r.prototype._destroy.call(this,e,(function(e){t(e),n.emit("close")}))}},function(e,t,n){"use strict";var r=n(7),i=n(296),o=n(56),s=n(8).Buffer,a=n(176),u=n(116),c=n(117),f=s.alloc(128);function l(e,t){o.call(this,"digest"),"string"==typeof t&&(t=s.from(t));var n="sha512"===e||"sha384"===e?128:64;(this._alg=e,this._key=t,t.length>n)?t=("rmd160"===e?new u:c(e)).update(t).digest():t.length<n&&(t=s.concat([t,f],n));for(var r=this._ipad=s.allocUnsafe(n),i=this._opad=s.allocUnsafe(n),a=0;a<n;a++)r[a]=54^t[a],i[a]=92^t[a];this._hash="rmd160"===e?new u:c(e),this._hash.update(r)}r(l,o),l.prototype._update=function(e){this._hash.update(e)},l.prototype._final=function(){var e=this._hash.digest();return("rmd160"===this._alg?new u:c(this._alg)).update(this._opad).update(e).digest()},e.exports=function(e,t){return"rmd160"===(e=e.toLowerCase())||"ripemd160"===e?new l("rmd160",t):"md5"===e?new i(a,t):new l(e,t)}},function(e,t,n){var r=n(113);e.exports=function(e){return(new r).update(e).digest()}},function(e){e.exports=JSON.parse('{"sha224WithRSAEncryption":{"sign":"rsa","hash":"sha224","id":"302d300d06096086480165030402040500041c"},"RSA-SHA224":{"sign":"ecdsa/rsa","hash":"sha224","id":"302d300d06096086480165030402040500041c"},"sha256WithRSAEncryption":{"sign":"rsa","hash":"sha256","id":"3031300d060960864801650304020105000420"},"RSA-SHA256":{"sign":"ecdsa/rsa","hash":"sha256","id":"3031300d060960864801650304020105000420"},"sha384WithRSAEncryption":{"sign":"rsa","hash":"sha384","id":"3041300d060960864801650304020205000430"},"RSA-SHA384":{"sign":"ecdsa/rsa","hash":"sha384","id":"3041300d060960864801650304020205000430"},"sha512WithRSAEncryption":{"sign":"rsa","hash":"sha512","id":"3051300d060960864801650304020305000440"},"RSA-SHA512":{"sign":"ecdsa/rsa","hash":"sha512","id":"3051300d060960864801650304020305000440"},"RSA-SHA1":{"sign":"rsa","hash":"sha1","id":"3021300906052b0e03021a05000414"},"ecdsa-with-SHA1":{"sign":"ecdsa","hash":"sha1","id":""},"sha256":{"sign":"ecdsa","hash":"sha256","id":""},"sha224":{"sign":"ecdsa","hash":"sha224","id":""},"sha384":{"sign":"ecdsa","hash":"sha384","id":""},"sha512":{"sign":"ecdsa","hash":"sha512","id":""},"DSA-SHA":{"sign":"dsa","hash":"sha1","id":""},"DSA-SHA1":{"sign":"dsa","hash":"sha1","id":""},"DSA":{"sign":"dsa","hash":"sha1","id":""},"DSA-WITH-SHA224":{"sign":"dsa","hash":"sha224","id":""},"DSA-SHA224":{"sign":"dsa","hash":"sha224","id":""},"DSA-WITH-SHA256":{"sign":"dsa","hash":"sha256","id":""},"DSA-SHA256":{"sign":"dsa","hash":"sha256","id":""},"DSA-WITH-SHA384":{"sign":"dsa","hash":"sha384","id":""},"DSA-SHA384":{"sign":"dsa","hash":"sha384","id":""},"DSA-WITH-SHA512":{"sign":"dsa","hash":"sha512","id":""},"DSA-SHA512":{"sign":"dsa","hash":"sha512","id":""},"DSA-RIPEMD160":{"sign":"dsa","hash":"rmd160","id":""},"ripemd160WithRSA":{"sign":"rsa","hash":"rmd160","id":"3021300906052b2403020105000414"},"RSA-RIPEMD160":{"sign":"rsa","hash":"rmd160","id":"3021300906052b2403020105000414"},"md5WithRSAEncryption":{"sign":"rsa","hash":"md5","id":"3020300c06082a864886f70d020505000410"},"RSA-MD5":{"sign":"rsa","hash":"md5","id":"3020300c06082a864886f70d020505000410"}}')},function(e,t,n){t.pbkdf2=n(298),t.pbkdf2Sync=n(181)},function(e,t){var n=Math.pow(2,30)-1;e.exports=function(e,t){if("number"!=typeof e)throw new TypeError("Iterations not a number");if(e<0)throw new TypeError("Bad iterations");if("number"!=typeof t)throw new TypeError("Key length not a number");if(t<0||t>n||t!=t)throw new TypeError("Bad key length")}},function(e,t,n){(function(t){var n;if(t.browser)n="utf-8";else if(t.version){n=parseInt(t.version.split(".")[0].slice(1),10)>=6?"utf-8":"binary"}else n="utf-8";e.exports=n}).call(this,n(20))},function(e,t,n){var r=n(176),i=n(116),o=n(117),s=n(8).Buffer,a=n(179),u=n(180),c=n(182),f=s.alloc(128),l={md5:16,sha1:20,sha224:28,sha256:32,sha384:48,sha512:64,rmd160:20,ripemd160:20};function d(e,t,n){var a=function(e){function t(t){return o(e).update(t).digest()}return"rmd160"===e||"ripemd160"===e?function(e){return(new i).update(e).digest()}:"md5"===e?r:t}(e),u="sha512"===e||"sha384"===e?128:64;t.length>u?t=a(t):t.length<u&&(t=s.concat([t,f],u));for(var c=s.allocUnsafe(u+l[e]),d=s.allocUnsafe(u+l[e]),h=0;h<u;h++)c[h]=54^t[h],d[h]=92^t[h];var p=s.allocUnsafe(u+n+4);c.copy(p,0,0,u),this.ipad1=p,this.ipad2=c,this.opad=d,this.alg=e,this.blocksize=u,this.hash=a,this.size=l[e]}d.prototype.run=function(e,t){return e.copy(t,this.blocksize),this.hash(t).copy(this.opad,this.blocksize),this.hash(this.opad)},e.exports=function(e,t,n,r,i){a(n,r);var o=new d(i=i||"sha1",e=c(e,u,"Password"),(t=c(t,u,"Salt")).length),f=s.allocUnsafe(r),h=s.allocUnsafe(t.length+4);t.copy(h,0,0,t.length);for(var p=0,v=l[i],g=Math.ceil(r/v),m=1;m<=g;m++){h.writeUInt32BE(m,t.length);for(var b=o.run(h,o.ipad1),y=b,w=1;w<n;w++){y=o.run(y,o.ipad2);for(var _=0;_<v;_++)b[_]^=y[_]}b.copy(f,p),p+=v}return f}},function(e,t,n){var r=n(8).Buffer;e.exports=function(e,t,n){if(r.isBuffer(e))return e;if("string"==typeof e)return r.from(e,t);if(ArrayBuffer.isView(e))return r.from(e.buffer);throw new TypeError(n+" must be a string, a Buffer, a typed array or a DataView")}},function(e,t,n){"use strict";t.readUInt32BE=function(e,t){return(e[0+t]<<24|e[1+t]<<16|e[2+t]<<8|e[3+t])>>>0},t.writeUInt32BE=function(e,t,n){e[0+n]=t>>>24,e[1+n]=t>>>16&255,e[2+n]=t>>>8&255,e[3+n]=255&t},t.ip=function(e,t,n,r){for(var i=0,o=0,s=6;s>=0;s-=2){for(var a=0;a<=24;a+=8)i<<=1,i|=t>>>a+s&1;for(a=0;a<=24;a+=8)i<<=1,i|=e>>>a+s&1}for(s=6;s>=0;s-=2){for(a=1;a<=25;a+=8)o<<=1,o|=t>>>a+s&1;for(a=1;a<=25;a+=8)o<<=1,o|=e>>>a+s&1}n[r+0]=i>>>0,n[r+1]=o>>>0},t.rip=function(e,t,n,r){for(var i=0,o=0,s=0;s<4;s++)for(var a=24;a>=0;a-=8)i<<=1,i|=t>>>a+s&1,i<<=1,i|=e>>>a+s&1;for(s=4;s<8;s++)for(a=24;a>=0;a-=8)o<<=1,o|=t>>>a+s&1,o<<=1,o|=e>>>a+s&1;n[r+0]=i>>>0,n[r+1]=o>>>0},t.pc1=function(e,t,n,r){for(var i=0,o=0,s=7;s>=5;s--){for(var a=0;a<=24;a+=8)i<<=1,i|=t>>a+s&1;for(a=0;a<=24;a+=8)i<<=1,i|=e>>a+s&1}for(a=0;a<=24;a+=8)i<<=1,i|=t>>a+s&1;for(s=1;s<=3;s++){for(a=0;a<=24;a+=8)o<<=1,o|=t>>a+s&1;for(a=0;a<=24;a+=8)o<<=1,o|=e>>a+s&1}for(a=0;a<=24;a+=8)o<<=1,o|=e>>a+s&1;n[r+0]=i>>>0,n[r+1]=o>>>0},t.r28shl=function(e,t){return e<<t&268435455|e>>>28-t};var r=[14,11,17,4,27,23,25,0,13,22,7,18,5,9,16,24,2,20,12,21,1,8,15,26,15,4,25,19,9,1,26,16,5,11,23,8,12,7,17,0,22,3,10,14,6,20,27,24];t.pc2=function(e,t,n,i){for(var o=0,s=0,a=r.length>>>1,u=0;u<a;u++)o<<=1,o|=e>>>r[u]&1;for(u=a;u<r.length;u++)s<<=1,s|=t>>>r[u]&1;n[i+0]=o>>>0,n[i+1]=s>>>0},t.expand=function(e,t,n){var r=0,i=0;r=(1&e)<<5|e>>>27;for(var o=23;o>=15;o-=4)r<<=6,r|=e>>>o&63;for(o=11;o>=3;o-=4)i|=e>>>o&63,i<<=6;i|=(31&e)<<1|e>>>31,t[n+0]=r>>>0,t[n+1]=i>>>0};var i=[14,0,4,15,13,7,1,4,2,14,15,2,11,13,8,1,3,10,10,6,6,12,12,11,5,9,9,5,0,3,7,8,4,15,1,12,14,8,8,2,13,4,6,9,2,1,11,7,15,5,12,11,9,3,7,14,3,10,10,0,5,6,0,13,15,3,1,13,8,4,14,7,6,15,11,2,3,8,4,14,9,12,7,0,2,1,13,10,12,6,0,9,5,11,10,5,0,13,14,8,7,10,11,1,10,3,4,15,13,4,1,2,5,11,8,6,12,7,6,12,9,0,3,5,2,14,15,9,10,13,0,7,9,0,14,9,6,3,3,4,15,6,5,10,1,2,13,8,12,5,7,14,11,12,4,11,2,15,8,1,13,1,6,10,4,13,9,0,8,6,15,9,3,8,0,7,11,4,1,15,2,14,12,3,5,11,10,5,14,2,7,12,7,13,13,8,14,11,3,5,0,6,6,15,9,0,10,3,1,4,2,7,8,2,5,12,11,1,12,10,4,14,15,9,10,3,6,15,9,0,0,6,12,10,11,1,7,13,13,8,15,9,1,4,3,5,14,11,5,12,2,7,8,2,4,14,2,14,12,11,4,2,1,12,7,4,10,7,11,13,6,1,8,5,5,0,3,15,15,10,13,3,0,9,14,8,9,6,4,11,2,8,1,12,11,7,10,1,13,14,7,2,8,13,15,6,9,15,12,0,5,9,6,10,3,4,0,5,14,3,12,10,1,15,10,4,15,2,9,7,2,12,6,9,8,5,0,6,13,1,3,13,4,14,14,0,7,11,5,3,11,8,9,4,14,3,15,2,5,12,2,9,8,5,12,15,3,10,7,11,0,14,4,1,10,7,1,6,13,0,11,8,6,13,4,13,11,0,2,11,14,7,15,4,0,9,8,1,13,10,3,14,12,3,9,5,7,12,5,2,10,15,6,8,1,6,1,6,4,11,11,13,13,8,12,1,3,4,7,10,14,7,10,9,15,5,6,0,8,15,0,14,5,2,9,3,2,12,13,1,2,15,8,13,4,8,6,10,15,3,11,7,1,4,10,12,9,5,3,6,14,11,5,0,0,14,12,9,7,2,7,2,11,1,4,14,1,7,9,4,12,10,14,8,2,13,0,15,6,12,10,9,13,0,15,3,3,5,5,6,8,11];t.substitute=function(e,t){for(var n=0,r=0;r<4;r++){n<<=4,n|=i[64*r+(e>>>18-6*r&63)]}for(r=0;r<4;r++){n<<=4,n|=i[256+64*r+(t>>>18-6*r&63)]}return n>>>0};var o=[16,25,12,11,3,20,4,15,31,17,9,6,27,14,1,22,30,24,8,18,0,5,29,23,13,19,2,26,10,21,28,7];t.permute=function(e){for(var t=0,n=0;n<o.length;n++)t<<=1,t|=e>>>o[n]&1;return t>>>0},t.padSplit=function(e,t,n){for(var r=e.toString(2);r.length<t;)r="0"+r;for(var i=[],o=0;o<t;o+=n)i.push(r.slice(o,o+n));return i.join(" ")}},function(e,t,n){"use strict";var r=n(46),i=n(7),o=n(183),s=n(121);function a(){this.tmp=new Array(2),this.keys=null}function u(e){s.call(this,e);var t=new a;this._desState=t,this.deriveKeys(t,e.key)}i(u,s),e.exports=u,u.create=function(e){return new u(e)};var c=[1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1];u.prototype.deriveKeys=function(e,t){e.keys=new Array(32),r.equal(t.length,this.blockSize,"Invalid key length");var n=o.readUInt32BE(t,0),i=o.readUInt32BE(t,4);o.pc1(n,i,e.tmp,0),n=e.tmp[0],i=e.tmp[1];for(var s=0;s<e.keys.length;s+=2){var a=c[s>>>1];n=o.r28shl(n,a),i=o.r28shl(i,a),o.pc2(n,i,e.keys,s)}},u.prototype._update=function(e,t,n,r){var i=this._desState,s=o.readUInt32BE(e,t),a=o.readUInt32BE(e,t+4);o.ip(s,a,i.tmp,0),s=i.tmp[0],a=i.tmp[1],"encrypt"===this.type?this._encrypt(i,s,a,i.tmp,0):this._decrypt(i,s,a,i.tmp,0),s=i.tmp[0],a=i.tmp[1],o.writeUInt32BE(n,s,r),o.writeUInt32BE(n,a,r+4)},u.prototype._pad=function(e,t){for(var n=e.length-t,r=t;r<e.length;r++)e[r]=n;return!0},u.prototype._unpad=function(e){for(var t=e[e.length-1],n=e.length-t;n<e.length;n++)r.equal(e[n],t);return e.slice(0,e.length-t)},u.prototype._encrypt=function(e,t,n,r,i){for(var s=t,a=n,u=0;u<e.keys.length;u+=2){var c=e.keys[u],f=e.keys[u+1];o.expand(a,e.tmp,0),c^=e.tmp[0],f^=e.tmp[1];var l=o.substitute(c,f),d=a;a=(s^o.permute(l))>>>0,s=d}o.rip(a,s,r,i)},u.prototype._decrypt=function(e,t,n,r,i){for(var s=n,a=t,u=e.keys.length-2;u>=0;u-=2){var c=e.keys[u],f=e.keys[u+1];o.expand(s,e.tmp,0),c^=e.tmp[0],f^=e.tmp[1];var l=o.substitute(c,f),d=s;s=(a^o.permute(l))>>>0,a=d}o.rip(s,a,r,i)}},function(e,t,n){var r=n(81),i=n(8).Buffer,o=n(186);function s(e){var t=e._cipher.encryptBlockRaw(e._prev);return o(e._prev),t}t.encrypt=function(e,t){var n=Math.ceil(t.length/16),o=e._cache.length;e._cache=i.concat([e._cache,i.allocUnsafe(16*n)]);for(var a=0;a<n;a++){var u=s(e),c=o+16*a;e._cache.writeUInt32BE(u[0],c+0),e._cache.writeUInt32BE(u[1],c+4),e._cache.writeUInt32BE(u[2],c+8),e._cache.writeUInt32BE(u[3],c+12)}var f=e._cache.slice(0,t.length);return e._cache=e._cache.slice(t.length),r(t,f)}},function(e,t){e.exports=function(e){for(var t,n=e.length;n--;){if(255!==(t=e.readUInt8(n))){t++,e.writeUInt8(t,n);break}e.writeUInt8(0,n)}}},function(e){e.exports=JSON.parse('{"aes-128-ecb":{"cipher":"AES","key":128,"iv":0,"mode":"ECB","type":"block"},"aes-192-ecb":{"cipher":"AES","key":192,"iv":0,"mode":"ECB","type":"block"},"aes-256-ecb":{"cipher":"AES","key":256,"iv":0,"mode":"ECB","type":"block"},"aes-128-cbc":{"cipher":"AES","key":128,"iv":16,"mode":"CBC","type":"block"},"aes-192-cbc":{"cipher":"AES","key":192,"iv":16,"mode":"CBC","type":"block"},"aes-256-cbc":{"cipher":"AES","key":256,"iv":16,"mode":"CBC","type":"block"},"aes128":{"cipher":"AES","key":128,"iv":16,"mode":"CBC","type":"block"},"aes192":{"cipher":"AES","key":192,"iv":16,"mode":"CBC","type":"block"},"aes256":{"cipher":"AES","key":256,"iv":16,"mode":"CBC","type":"block"},"aes-128-cfb":{"cipher":"AES","key":128,"iv":16,"mode":"CFB","type":"stream"},"aes-192-cfb":{"cipher":"AES","key":192,"iv":16,"mode":"CFB","type":"stream"},"aes-256-cfb":{"cipher":"AES","key":256,"iv":16,"mode":"CFB","type":"stream"},"aes-128-cfb8":{"cipher":"AES","key":128,"iv":16,"mode":"CFB8","type":"stream"},"aes-192-cfb8":{"cipher":"AES","key":192,"iv":16,"mode":"CFB8","type":"stream"},"aes-256-cfb8":{"cipher":"AES","key":256,"iv":16,"mode":"CFB8","type":"stream"},"aes-128-cfb1":{"cipher":"AES","key":128,"iv":16,"mode":"CFB1","type":"stream"},"aes-192-cfb1":{"cipher":"AES","key":192,"iv":16,"mode":"CFB1","type":"stream"},"aes-256-cfb1":{"cipher":"AES","key":256,"iv":16,"mode":"CFB1","type":"stream"},"aes-128-ofb":{"cipher":"AES","key":128,"iv":16,"mode":"OFB","type":"stream"},"aes-192-ofb":{"cipher":"AES","key":192,"iv":16,"mode":"OFB","type":"stream"},"aes-256-ofb":{"cipher":"AES","key":256,"iv":16,"mode":"OFB","type":"stream"},"aes-128-ctr":{"cipher":"AES","key":128,"iv":16,"mode":"CTR","type":"stream"},"aes-192-ctr":{"cipher":"AES","key":192,"iv":16,"mode":"CTR","type":"stream"},"aes-256-ctr":{"cipher":"AES","key":256,"iv":16,"mode":"CTR","type":"stream"},"aes-128-gcm":{"cipher":"AES","key":128,"iv":12,"mode":"GCM","type":"auth"},"aes-192-gcm":{"cipher":"AES","key":192,"iv":12,"mode":"GCM","type":"auth"},"aes-256-gcm":{"cipher":"AES","key":256,"iv":12,"mode":"GCM","type":"auth"}}')},function(e,t,n){var r=n(93),i=n(8).Buffer,o=n(56),s=n(7),a=n(311),u=n(81),c=n(186);function f(e,t,n,s){o.call(this);var u=i.alloc(4,0);this._cipher=new r.AES(t);var f=this._cipher.encryptBlock(u);this._ghash=new a(f),n=function(e,t,n){if(12===t.length)return e._finID=i.concat([t,i.from([0,0,0,1])]),i.concat([t,i.from([0,0,0,2])]);var r=new a(n),o=t.length,s=o%16;r.update(t),s&&(s=16-s,r.update(i.alloc(s,0))),r.update(i.alloc(8,0));var u=8*o,f=i.alloc(8);f.writeUIntBE(u,0,8),r.update(f),e._finID=r.state;var l=i.from(e._finID);return c(l),l}(this,n,f),this._prev=i.from(n),this._cache=i.allocUnsafe(0),this._secCache=i.allocUnsafe(0),this._decrypt=s,this._alen=0,this._len=0,this._mode=e,this._authTag=null,this._called=!1}s(f,o),f.prototype._update=function(e){if(!this._called&&this._alen){var t=16-this._alen%16;t<16&&(t=i.alloc(t,0),this._ghash.update(t))}this._called=!0;var n=this._mode.encrypt(this,e);return this._decrypt?this._ghash.update(e):this._ghash.update(n),this._len+=e.length,n},f.prototype._final=function(){if(this._decrypt&&!this._authTag)throw new Error("Unsupported state or unable to authenticate data");var e=u(this._ghash.final(8*this._alen,8*this._len),this._cipher.encryptBlock(this._finID));if(this._decrypt&&function(e,t){var n=0;e.length!==t.length&&n++;for(var r=Math.min(e.length,t.length),i=0;i<r;++i)n+=e[i]^t[i];return n}(e,this._authTag))throw new Error("Unsupported state or unable to authenticate data");this._authTag=e,this._cipher.scrub()},f.prototype.getAuthTag=function(){if(this._decrypt||!i.isBuffer(this._authTag))throw new Error("Attempting to get auth tag in unsupported state");return this._authTag},f.prototype.setAuthTag=function(e){if(!this._decrypt)throw new Error("Attempting to set auth tag in unsupported state");this._authTag=e},f.prototype.setAAD=function(e){if(this._called)throw new Error("Attempting to set AAD in unsupported state");this._ghash.update(e),this._alen+=e.length},e.exports=f},function(e,t,n){var r=n(93),i=n(8).Buffer,o=n(56);function s(e,t,n,s){o.call(this),this._cipher=new r.AES(t),this._prev=i.from(n),this._cache=i.allocUnsafe(0),this._secCache=i.allocUnsafe(0),this._decrypt=s,this._mode=e}n(7)(s,o),s.prototype._update=function(e){return this._mode.encrypt(this,e,this._decrypt)},s.prototype._final=function(){this._cipher.scrub()},e.exports=s},function(e,t,n){var r=n(66);e.exports=b,b.simpleSieve=g,b.fermatTest=m;var i=n(29),o=new i(24),s=new(n(191)),a=new i(1),u=new i(2),c=new i(5),f=(new i(16),new i(8),new i(10)),l=new i(3),d=(new i(7),new i(11)),h=new i(4),p=(new i(12),null);function v(){if(null!==p)return p;var e=[];e[0]=2;for(var t=1,n=3;n<1048576;n+=2){for(var r=Math.ceil(Math.sqrt(n)),i=0;i<t&&e[i]<=r&&n%e[i]!=0;i++);t!==i&&e[i]<=r||(e[t++]=n)}return p=e,e}function g(e){for(var t=v(),n=0;n<t.length;n++)if(0===e.modn(t[n]))return 0===e.cmpn(t[n]);return!0}function m(e){var t=i.mont(e);return 0===u.toRed(t).redPow(e.subn(1)).fromRed().cmpn(1)}function b(e,t){if(e<16)return new i(2===t||5===t?[140,123]:[140,39]);var n,p;for(t=new i(t);;){for(n=new i(r(Math.ceil(e/8)));n.bitLength()>e;)n.ishrn(1);if(n.isEven()&&n.iadd(a),n.testn(1)||n.iadd(u),t.cmp(u)){if(!t.cmp(c))for(;n.mod(f).cmp(l);)n.iadd(h)}else for(;n.mod(o).cmp(d);)n.iadd(h);if(g(p=n.shrn(1))&&g(n)&&m(p)&&m(n)&&s.test(p)&&s.test(n))return n}}},function(e,t,n){var r=n(29),i=n(124);function o(e){this.rand=e||new i.Rand}e.exports=o,o.create=function(e){return new o(e)},o.prototype._randbelow=function(e){var t=e.bitLength(),n=Math.ceil(t/8);do{var i=new r(this.rand.generate(n))}while(i.cmp(e)>=0);return i},o.prototype._randrange=function(e,t){var n=t.sub(e);return e.add(this._randbelow(n))},o.prototype.test=function(e,t,n){var i=e.bitLength(),o=r.mont(e),s=new r(1).toRed(o);t||(t=Math.max(1,i/48|0));for(var a=e.subn(1),u=0;!a.testn(u);u++);for(var c=e.shrn(u),f=a.toRed(o);t>0;t--){var l=this._randrange(new r(2),a);n&&n(l);var d=l.toRed(o).redPow(c);if(0!==d.cmp(s)&&0!==d.cmp(f)){for(var h=1;h<u;h++){if(0===(d=d.redSqr()).cmp(s))return!1;if(0===d.cmp(f))break}if(h===u)return!1}}return!0},o.prototype.getDivisor=function(e,t){var n=e.bitLength(),i=r.mont(e),o=new r(1).toRed(i);t||(t=Math.max(1,n/48|0));for(var s=e.subn(1),a=0;!s.testn(a);a++);for(var u=e.shrn(a),c=s.toRed(i);t>0;t--){var f=this._randrange(new r(2),s),l=e.gcd(f);if(0!==l.cmpn(1))return l;var d=f.toRed(i).redPow(u);if(0!==d.cmp(o)&&0!==d.cmp(c)){for(var h=1;h<a;h++){if(0===(d=d.redSqr()).cmp(o))return d.fromRed().subn(1).gcd(e);if(0===d.cmp(c))break}if(h===a)return(d=d.redSqr()).fromRed().subn(1).gcd(e)}}return!1}},function(e,t,n){"use strict";(function(t,r){var i;e.exports=A,A.ReadableState=M;n(49).EventEmitter;var o=function(e,t){return e.listeners(t).length},s=n(193),a=n(6).Buffer,u=t.Uint8Array||function(){};var c,f=n(321);c=f&&f.debuglog?f.debuglog("stream"):function(){};var l,d,h,p=n(322),v=n(194),g=n(195).getHighWaterMark,m=n(70).codes,b=m.ERR_INVALID_ARG_TYPE,y=m.ERR_STREAM_PUSH_AFTER_EOF,w=m.ERR_METHOD_NOT_IMPLEMENTED,_=m.ERR_STREAM_UNSHIFT_AFTER_END_EVENT;n(7)(A,s);var S=v.errorOrDestroy,E=["error","close","destroy","pause","resume"];function M(e,t,r){i=i||n(71),e=e||{},"boolean"!=typeof r&&(r=t instanceof i),this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.readableObjectMode),this.highWaterMark=g(this,e,"readableHighWaterMark",r),this.buffer=new p,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.paused=!0,this.emitClose=!1!==e.emitClose,this.autoDestroy=!!e.autoDestroy,this.destroyed=!1,this.defaultEncoding=e.defaultEncoding||"utf8",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,e.encoding&&(l||(l=n(59).StringDecoder),this.decoder=new l(e.encoding),this.encoding=e.encoding)}function A(e){if(i=i||n(71),!(this instanceof A))return new A(e);var t=this instanceof i;this._readableState=new M(e,this,t),this.readable=!0,e&&("function"==typeof e.read&&(this._read=e.read),"function"==typeof e.destroy&&(this._destroy=e.destroy)),s.call(this)}function I(e,t,n,r,i){c("readableAddChunk",t);var o,s=e._readableState;if(null===t)s.reading=!1,function(e,t){if(c("onEofChunk"),t.ended)return;if(t.decoder){var n=t.decoder.end();n&&n.length&&(t.buffer.push(n),t.length+=t.objectMode?1:n.length)}t.ended=!0,t.sync?x(e):(t.needReadable=!1,t.emittedReadable||(t.emittedReadable=!0,C(e)))}(e,s);else if(i||(o=function(e,t){var n;r=t,a.isBuffer(r)||r instanceof u||"string"==typeof t||void 0===t||e.objectMode||(n=new b("chunk",["string","Buffer","Uint8Array"],t));var r;return n}(s,t)),o)S(e,o);else if(s.objectMode||t&&t.length>0)if("string"==typeof t||s.objectMode||Object.getPrototypeOf(t)===a.prototype||(t=function(e){return a.from(e)}(t)),r)s.endEmitted?S(e,new _):k(e,s,t,!0);else if(s.ended)S(e,new y);else{if(s.destroyed)return!1;s.reading=!1,s.decoder&&!n?(t=s.decoder.write(t),s.objectMode||0!==t.length?k(e,s,t,!1):T(e,s)):k(e,s,t,!1)}else r||(s.reading=!1,T(e,s));return!s.ended&&(s.length<s.highWaterMark||0===s.length)}function k(e,t,n,r){t.flowing&&0===t.length&&!t.sync?(t.awaitDrain=0,e.emit("data",n)):(t.length+=t.objectMode?1:n.length,r?t.buffer.unshift(n):t.buffer.push(n),t.needReadable&&x(e)),T(e,t)}Object.defineProperty(A.prototype,"destroyed",{enumerable:!1,get:function(){return void 0!==this._readableState&&this._readableState.destroyed},set:function(e){this._readableState&&(this._readableState.destroyed=e)}}),A.prototype.destroy=v.destroy,A.prototype._undestroy=v.undestroy,A.prototype._destroy=function(e,t){t(e)},A.prototype.push=function(e,t){var n,r=this._readableState;return r.objectMode?n=!0:"string"==typeof e&&((t=t||r.defaultEncoding)!==r.encoding&&(e=a.from(e,t),t=""),n=!0),I(this,e,t,!1,n)},A.prototype.unshift=function(e){return I(this,e,null,!0,!1)},A.prototype.isPaused=function(){return!1===this._readableState.flowing},A.prototype.setEncoding=function(e){l||(l=n(59).StringDecoder);var t=new l(e);this._readableState.decoder=t,this._readableState.encoding=this._readableState.decoder.encoding;for(var r=this._readableState.buffer.head,i="";null!==r;)i+=t.write(r.data),r=r.next;return this._readableState.buffer.clear(),""!==i&&this._readableState.buffer.push(i),this._readableState.length=i.length,this};function O(e,t){return e<=0||0===t.length&&t.ended?0:t.objectMode?1:e!=e?t.flowing&&t.length?t.buffer.head.data.length:t.length:(e>t.highWaterMark&&(t.highWaterMark=function(e){return e>=1073741824?e=1073741824:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function x(e){var t=e._readableState;c("emitReadable",t.needReadable,t.emittedReadable),t.needReadable=!1,t.emittedReadable||(c("emitReadable",t.flowing),t.emittedReadable=!0,r.nextTick(C,e))}function C(e){var t=e._readableState;c("emitReadable_",t.destroyed,t.length,t.ended),t.destroyed||!t.length&&!t.ended||(e.emit("readable"),t.emittedReadable=!1),t.needReadable=!t.flowing&&!t.ended&&t.length<=t.highWaterMark,j(e)}function T(e,t){t.readingMore||(t.readingMore=!0,r.nextTick(P,e,t))}function P(e,t){for(;!t.reading&&!t.ended&&(t.length<t.highWaterMark||t.flowing&&0===t.length);){var n=t.length;if(c("maybeReadMore read 0"),e.read(0),n===t.length)break}t.readingMore=!1}function N(e){var t=e._readableState;t.readableListening=e.listenerCount("readable")>0,t.resumeScheduled&&!t.paused?t.flowing=!0:e.listenerCount("data")>0&&e.resume()}function R(e){c("readable nexttick read 0"),e.read(0)}function L(e,t){c("resume",t.reading),t.reading||e.read(0),t.resumeScheduled=!1,e.emit("resume"),j(e),t.flowing&&!t.reading&&e.read(0)}function j(e){var t=e._readableState;for(c("flow",t.flowing);t.flowing&&null!==e.read(););}function D(e,t){return 0===t.length?null:(t.objectMode?n=t.buffer.shift():!e||e>=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.first():t.buffer.concat(t.length),t.buffer.clear()):n=t.buffer.consume(e,t.decoder),n);var n}function U(e){var t=e._readableState;c("endReadable",t.endEmitted),t.endEmitted||(t.ended=!0,r.nextTick(B,t,e))}function B(e,t){if(c("endReadableNT",e.endEmitted,e.length),!e.endEmitted&&0===e.length&&(e.endEmitted=!0,t.readable=!1,t.emit("end"),e.autoDestroy)){var n=t._writableState;(!n||n.autoDestroy&&n.finished)&&t.destroy()}}function F(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1}A.prototype.read=function(e){c("read",e),e=parseInt(e,10);var t=this._readableState,n=e;if(0!==e&&(t.emittedReadable=!1),0===e&&t.needReadable&&((0!==t.highWaterMark?t.length>=t.highWaterMark:t.length>0)||t.ended))return c("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?U(this):x(this),null;if(0===(e=O(e,t))&&t.ended)return 0===t.length&&U(this),null;var r,i=t.needReadable;return c("need readable",i),(0===t.length||t.length-e<t.highWaterMark)&&c("length less than watermark",i=!0),t.ended||t.reading?c("reading or ended",i=!1):i&&(c("do read"),t.reading=!0,t.sync=!0,0===t.length&&(t.needReadable=!0),this._read(t.highWaterMark),t.sync=!1,t.reading||(e=O(n,t))),null===(r=e>0?D(e,t):null)?(t.needReadable=t.length<=t.highWaterMark,e=0):(t.length-=e,t.awaitDrain=0),0===t.length&&(t.ended||(t.needReadable=!0),n!==e&&t.ended&&U(this)),null!==r&&this.emit("data",r),r},A.prototype._read=function(e){S(this,new w("_read()"))},A.prototype.pipe=function(e,t){var n=this,i=this._readableState;switch(i.pipesCount){case 0:i.pipes=e;break;case 1:i.pipes=[i.pipes,e];break;default:i.pipes.push(e)}i.pipesCount+=1,c("pipe count=%d opts=%j",i.pipesCount,t);var s=(!t||!1!==t.end)&&e!==r.stdout&&e!==r.stderr?u:g;function a(t,r){c("onunpipe"),t===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,c("cleanup"),e.removeListener("close",p),e.removeListener("finish",v),e.removeListener("drain",f),e.removeListener("error",h),e.removeListener("unpipe",a),n.removeListener("end",u),n.removeListener("end",g),n.removeListener("data",d),l=!0,!i.awaitDrain||e._writableState&&!e._writableState.needDrain||f())}function u(){c("onend"),e.end()}i.endEmitted?r.nextTick(s):n.once("end",s),e.on("unpipe",a);var f=function(e){return function(){var t=e._readableState;c("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&o(e,"data")&&(t.flowing=!0,j(e))}}(n);e.on("drain",f);var l=!1;function d(t){c("ondata");var r=e.write(t);c("dest.write",r),!1===r&&((1===i.pipesCount&&i.pipes===e||i.pipesCount>1&&-1!==F(i.pipes,e))&&!l&&(c("false write response, pause",i.awaitDrain),i.awaitDrain++),n.pause())}function h(t){c("onerror",t),g(),e.removeListener("error",h),0===o(e,"error")&&S(e,t)}function p(){e.removeListener("finish",v),g()}function v(){c("onfinish"),e.removeListener("close",p),g()}function g(){c("unpipe"),n.unpipe(e)}return n.on("data",d),function(e,t,n){if("function"==typeof e.prependListener)return e.prependListener(t,n);e._events&&e._events[t]?Array.isArray(e._events[t])?e._events[t].unshift(n):e._events[t]=[n,e._events[t]]:e.on(t,n)}(e,"error",h),e.once("close",p),e.once("finish",v),e.emit("pipe",n),i.flowing||(c("pipe resume"),n.resume()),e},A.prototype.unpipe=function(e){var t=this._readableState,n={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes||(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,n)),this;if(!e){var r=t.pipes,i=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var o=0;o<i;o++)r[o].emit("unpipe",this,{hasUnpiped:!1});return this}var s=F(t.pipes,e);return-1===s||(t.pipes.splice(s,1),t.pipesCount-=1,1===t.pipesCount&&(t.pipes=t.pipes[0]),e.emit("unpipe",this,n)),this},A.prototype.on=function(e,t){var n=s.prototype.on.call(this,e,t),i=this._readableState;return"data"===e?(i.readableListening=this.listenerCount("readable")>0,!1!==i.flowing&&this.resume()):"readable"===e&&(i.endEmitted||i.readableListening||(i.readableListening=i.needReadable=!0,i.flowing=!1,i.emittedReadable=!1,c("on readable",i.length,i.reading),i.length?x(this):i.reading||r.nextTick(R,this))),n},A.prototype.addListener=A.prototype.on,A.prototype.removeListener=function(e,t){var n=s.prototype.removeListener.call(this,e,t);return"readable"===e&&r.nextTick(N,this),n},A.prototype.removeAllListeners=function(e){var t=s.prototype.removeAllListeners.apply(this,arguments);return"readable"!==e&&void 0!==e||r.nextTick(N,this),t},A.prototype.resume=function(){var e=this._readableState;return e.flowing||(c("resume"),e.flowing=!e.readableListening,function(e,t){t.resumeScheduled||(t.resumeScheduled=!0,r.nextTick(L,e,t))}(this,e)),e.paused=!1,this},A.prototype.pause=function(){return c("call pause flowing=%j",this._readableState.flowing),!1!==this._readableState.flowing&&(c("pause"),this._readableState.flowing=!1,this.emit("pause")),this._readableState.paused=!0,this},A.prototype.wrap=function(e){var t=this,n=this._readableState,r=!1;for(var i in e.on("end",(function(){if(c("wrapped end"),n.decoder&&!n.ended){var e=n.decoder.end();e&&e.length&&t.push(e)}t.push(null)})),e.on("data",(function(i){(c("wrapped data"),n.decoder&&(i=n.decoder.write(i)),n.objectMode&&null==i)||(n.objectMode||i&&i.length)&&(t.push(i)||(r=!0,e.pause()))})),e)void 0===this[i]&&"function"==typeof e[i]&&(this[i]=function(t){return function(){return e[t].apply(e,arguments)}}(i));for(var o=0;o<E.length;o++)e.on(E[o],this.emit.bind(this,E[o]));return this._read=function(t){c("wrapped _read",t),r&&(r=!1,e.resume())},this},"function"==typeof Symbol&&(A.prototype[Symbol.asyncIterator]=function(){return void 0===d&&(d=n(324)),d(this)}),Object.defineProperty(A.prototype,"readableHighWaterMark",{enumerable:!1,get:function(){return this._readableState.highWaterMark}}),Object.defineProperty(A.prototype,"readableBuffer",{enumerable:!1,get:function(){return this._readableState&&this._readableState.buffer}}),Object.defineProperty(A.prototype,"readableFlowing",{enumerable:!1,get:function(){return this._readableState.flowing},set:function(e){this._readableState&&(this._readableState.flowing=e)}}),A._fromList=D,Object.defineProperty(A.prototype,"readableLength",{enumerable:!1,get:function(){return this._readableState.length}}),"function"==typeof Symbol&&(A.from=function(e,t){return void 0===h&&(h=n(325)),h(A,e,t)})}).call(this,n(31),n(20))},function(e,t,n){e.exports=n(49).EventEmitter},function(e,t,n){"use strict";(function(t){function n(e,t){i(e,t),r(e)}function r(e){e._writableState&&!e._writableState.emitClose||e._readableState&&!e._readableState.emitClose||e.emit("close")}function i(e,t){e.emit("error",t)}e.exports={destroy:function(e,o){var s=this,a=this._readableState&&this._readableState.destroyed,u=this._writableState&&this._writableState.destroyed;return a||u?(o?o(e):e&&(this._writableState?this._writableState.errorEmitted||(this._writableState.errorEmitted=!0,t.nextTick(i,this,e)):t.nextTick(i,this,e)),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,(function(e){!o&&e?s._writableState?s._writableState.errorEmitted?t.nextTick(r,s):(s._writableState.errorEmitted=!0,t.nextTick(n,s,e)):t.nextTick(n,s,e):o?(t.nextTick(r,s),o(e)):t.nextTick(r,s)})),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finalCalled=!1,this._writableState.prefinished=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)},errorOrDestroy:function(e,t){var n=e._readableState,r=e._writableState;n&&n.autoDestroy||r&&r.autoDestroy?e.destroy(t):e.emit("error",t)}}}).call(this,n(20))},function(e,t,n){"use strict";var r=n(70).codes.ERR_INVALID_OPT_VALUE;e.exports={getHighWaterMark:function(e,t,n,i){var o=function(e,t,n){return null!=e.highWaterMark?e.highWaterMark:t?e[n]:null}(t,i,n);if(null!=o){if(!isFinite(o)||Math.floor(o)!==o||o<0)throw new r(i?n:"highWaterMark",o);return Math.floor(o)}return e.objectMode?16:16384}}},function(e,t,n){"use strict";(function(t,r){function i(e){var t=this;this.next=null,this.entry=null,this.finish=function(){!function(e,t,n){var r=e.entry;e.entry=null;for(;r;){var i=r.callback;t.pendingcb--,i(n),r=r.next}t.corkedRequestsFree.next=e}(t,e)}}var o;e.exports=A,A.WritableState=M;var s={deprecate:n(114)},a=n(193),u=n(6).Buffer,c=t.Uint8Array||function(){};var f,l=n(194),d=n(195).getHighWaterMark,h=n(70).codes,p=h.ERR_INVALID_ARG_TYPE,v=h.ERR_METHOD_NOT_IMPLEMENTED,g=h.ERR_MULTIPLE_CALLBACK,m=h.ERR_STREAM_CANNOT_PIPE,b=h.ERR_STREAM_DESTROYED,y=h.ERR_STREAM_NULL_VALUES,w=h.ERR_STREAM_WRITE_AFTER_END,_=h.ERR_UNKNOWN_ENCODING,S=l.errorOrDestroy;function E(){}function M(e,t,s){o=o||n(71),e=e||{},"boolean"!=typeof s&&(s=t instanceof o),this.objectMode=!!e.objectMode,s&&(this.objectMode=this.objectMode||!!e.writableObjectMode),this.highWaterMark=d(this,e,"writableHighWaterMark",s),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var a=!1===e.decodeStrings;this.decodeStrings=!a,this.defaultEncoding=e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){!function(e,t){var n=e._writableState,i=n.sync,o=n.writecb;if("function"!=typeof o)throw new g;if(function(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}(n),t)!function(e,t,n,i,o){--t.pendingcb,n?(r.nextTick(o,i),r.nextTick(T,e,t),e._writableState.errorEmitted=!0,S(e,i)):(o(i),e._writableState.errorEmitted=!0,S(e,i),T(e,t))}(e,n,i,t,o);else{var s=x(n)||e.destroyed;s||n.corked||n.bufferProcessing||!n.bufferedRequest||O(e,n),i?r.nextTick(k,e,n,s,o):k(e,n,s,o)}}(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.emitClose=!1!==e.emitClose,this.autoDestroy=!!e.autoDestroy,this.bufferedRequestCount=0,this.corkedRequestsFree=new i(this)}function A(e){var t=this instanceof(o=o||n(71));if(!t&&!f.call(A,this))return new A(e);this._writableState=new M(e,this,t),this.writable=!0,e&&("function"==typeof e.write&&(this._write=e.write),"function"==typeof e.writev&&(this._writev=e.writev),"function"==typeof e.destroy&&(this._destroy=e.destroy),"function"==typeof e.final&&(this._final=e.final)),a.call(this)}function I(e,t,n,r,i,o,s){t.writelen=r,t.writecb=s,t.writing=!0,t.sync=!0,t.destroyed?t.onwrite(new b("write")):n?e._writev(i,t.onwrite):e._write(i,o,t.onwrite),t.sync=!1}function k(e,t,n,r){n||function(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit("drain"))}(e,t),t.pendingcb--,r(),T(e,t)}function O(e,t){t.bufferProcessing=!0;var n=t.bufferedRequest;if(e._writev&&n&&n.next){var r=t.bufferedRequestCount,o=new Array(r),s=t.corkedRequestsFree;s.entry=n;for(var a=0,u=!0;n;)o[a]=n,n.isBuf||(u=!1),n=n.next,a+=1;o.allBuffers=u,I(e,t,!0,t.length,o,"",s.finish),t.pendingcb++,t.lastBufferedRequest=null,s.next?(t.corkedRequestsFree=s.next,s.next=null):t.corkedRequestsFree=new i(t),t.bufferedRequestCount=0}else{for(;n;){var c=n.chunk,f=n.encoding,l=n.callback;if(I(e,t,!1,t.objectMode?1:c.length,c,f,l),n=n.next,t.bufferedRequestCount--,t.writing)break}null===n&&(t.lastBufferedRequest=null)}t.bufferedRequest=n,t.bufferProcessing=!1}function x(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function C(e,t){e._final((function(n){t.pendingcb--,n&&S(e,n),t.prefinished=!0,e.emit("prefinish"),T(e,t)}))}function T(e,t){var n=x(t);if(n&&(function(e,t){t.prefinished||t.finalCalled||("function"!=typeof e._final||t.destroyed?(t.prefinished=!0,e.emit("prefinish")):(t.pendingcb++,t.finalCalled=!0,r.nextTick(C,e,t)))}(e,t),0===t.pendingcb&&(t.finished=!0,e.emit("finish"),t.autoDestroy))){var i=e._readableState;(!i||i.autoDestroy&&i.endEmitted)&&e.destroy()}return n}n(7)(A,a),M.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(M.prototype,"buffer",{get:s.deprecate((function(){return this.getBuffer()}),"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(e){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(f=Function.prototype[Symbol.hasInstance],Object.defineProperty(A,Symbol.hasInstance,{value:function(e){return!!f.call(this,e)||this===A&&(e&&e._writableState instanceof M)}})):f=function(e){return e instanceof this},A.prototype.pipe=function(){S(this,new m)},A.prototype.write=function(e,t,n){var i,o=this._writableState,s=!1,a=!o.objectMode&&(i=e,u.isBuffer(i)||i instanceof c);return a&&!u.isBuffer(e)&&(e=function(e){return u.from(e)}(e)),"function"==typeof t&&(n=t,t=null),a?t="buffer":t||(t=o.defaultEncoding),"function"!=typeof n&&(n=E),o.ending?function(e,t){var n=new w;S(e,n),r.nextTick(t,n)}(this,n):(a||function(e,t,n,i){var o;return null===n?o=new y:"string"==typeof n||t.objectMode||(o=new p("chunk",["string","Buffer"],n)),!o||(S(e,o),r.nextTick(i,o),!1)}(this,o,e,n))&&(o.pendingcb++,s=function(e,t,n,r,i,o){if(!n){var s=function(e,t,n){e.objectMode||!1===e.decodeStrings||"string"!=typeof t||(t=u.from(t,n));return t}(t,r,i);r!==s&&(n=!0,i="buffer",r=s)}var a=t.objectMode?1:r.length;t.length+=a;var c=t.length<t.highWaterMark;c||(t.needDrain=!0);if(t.writing||t.corked){var f=t.lastBufferedRequest;t.lastBufferedRequest={chunk:r,encoding:i,isBuf:n,callback:o,next:null},f?f.next=t.lastBufferedRequest:t.bufferedRequest=t.lastBufferedRequest,t.bufferedRequestCount+=1}else I(e,t,!1,a,r,i,o);return c}(this,o,a,e,t,n)),s},A.prototype.cork=function(){this._writableState.corked++},A.prototype.uncork=function(){var e=this._writableState;e.corked&&(e.corked--,e.writing||e.corked||e.bufferProcessing||!e.bufferedRequest||O(this,e))},A.prototype.setDefaultEncoding=function(e){if("string"==typeof e&&(e=e.toLowerCase()),!(["hex","utf8","utf-8","ascii","binary","base64","ucs2","ucs-2","utf16le","utf-16le","raw"].indexOf((e+"").toLowerCase())>-1))throw new _(e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(A.prototype,"writableBuffer",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}}),Object.defineProperty(A.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),A.prototype._write=function(e,t,n){n(new v("_write()"))},A.prototype._writev=null,A.prototype.end=function(e,t,n){var i=this._writableState;return"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!=e&&this.write(e,t),i.corked&&(i.corked=1,this.uncork()),i.ending||function(e,t,n){t.ending=!0,T(e,t),n&&(t.finished?r.nextTick(n):e.once("finish",n));t.ended=!0,e.writable=!1}(this,i,n),this},Object.defineProperty(A.prototype,"writableLength",{enumerable:!1,get:function(){return this._writableState.length}}),Object.defineProperty(A.prototype,"destroyed",{enumerable:!1,get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),A.prototype.destroy=l.destroy,A.prototype._undestroy=l.undestroy,A.prototype._destroy=function(e,t){t(e)}}).call(this,n(31),n(20))},function(e,t,n){"use strict";e.exports=f;var r=n(70).codes,i=r.ERR_METHOD_NOT_IMPLEMENTED,o=r.ERR_MULTIPLE_CALLBACK,s=r.ERR_TRANSFORM_ALREADY_TRANSFORMING,a=r.ERR_TRANSFORM_WITH_LENGTH_0,u=n(71);function c(e,t){var n=this._transformState;n.transforming=!1;var r=n.writecb;if(null===r)return this.emit("error",new o);n.writechunk=null,n.writecb=null,null!=t&&this.push(t),r(e);var i=this._readableState;i.reading=!1,(i.needReadable||i.length<i.highWaterMark)&&this._read(i.highWaterMark)}function f(e){if(!(this instanceof f))return new f(e);u.call(this,e),this._transformState={afterTransform:c.bind(this),needTransform:!1,transforming:!1,writecb:null,writechunk:null,writeencoding:null},this._readableState.needReadable=!0,this._readableState.sync=!1,e&&("function"==typeof e.transform&&(this._transform=e.transform),"function"==typeof e.flush&&(this._flush=e.flush)),this.on("prefinish",l)}function l(){var e=this;"function"!=typeof this._flush||this._readableState.destroyed?d(this,null,null):this._flush((function(t,n){d(e,t,n)}))}function d(e,t,n){if(t)return e.emit("error",t);if(null!=n&&e.push(n),e._writableState.length)throw new a;if(e._transformState.transforming)throw new s;return e.push(null)}n(7)(f,u),f.prototype.push=function(e,t){return this._transformState.needTransform=!1,u.prototype.push.call(this,e,t)},f.prototype._transform=function(e,t,n){n(new i("_transform()"))},f.prototype._write=function(e,t,n){var r=this._transformState;if(r.writecb=n,r.writechunk=e,r.writeencoding=t,!r.transforming){var i=this._readableState;(r.needTransform||i.needReadable||i.length<i.highWaterMark)&&this._read(i.highWaterMark)}},f.prototype._read=function(e){var t=this._transformState;null===t.writechunk||t.transforming?t.needTransform=!0:(t.transforming=!0,this._transform(t.writechunk,t.writeencoding,t.afterTransform))},f.prototype._destroy=function(e,t){u.prototype._destroy.call(this,e,(function(e){t(e)}))}},function(e,t,n){"use strict";var r=t;function i(e){return 1===e.length?"0"+e:e}function o(e){for(var t="",n=0;n<e.length;n++)t+=i(e[n].toString(16));return t}r.toArray=function(e,t){if(Array.isArray(e))return e.slice();if(!e)return[];var n=[];if("string"!=typeof e){for(var r=0;r<e.length;r++)n[r]=0|e[r];return n}if("hex"===t){(e=e.replace(/[^a-z0-9]+/gi,"")).length%2!=0&&(e="0"+e);for(r=0;r<e.length;r+=2)n.push(parseInt(e[r]+e[r+1],16))}else for(r=0;r<e.length;r++){var i=e.charCodeAt(r),o=i>>8,s=255&i;o?n.push(o,s):n.push(s)}return n},r.zero2=i,r.toHex=o,r.encode=function(e,t){return"hex"===t?o(e):e}},function(e,t,n){"use strict";var r=t;r.base=n(95),r.short=n(332),r.mont=n(333),r.edwards=n(334)},function(e,t,n){"use strict";var r=n(51).rotr32;function i(e,t,n){return e&t^~e&n}function o(e,t,n){return e&t^e&n^t&n}function s(e,t,n){return e^t^n}t.ft_1=function(e,t,n,r){return 0===e?i(t,n,r):1===e||3===e?s(t,n,r):2===e?o(t,n,r):void 0},t.ch32=i,t.maj32=o,t.p32=s,t.s0_256=function(e){return r(e,2)^r(e,13)^r(e,22)},t.s1_256=function(e){return r(e,6)^r(e,11)^r(e,25)},t.g0_256=function(e){return r(e,7)^r(e,18)^e>>>3},t.g1_256=function(e){return r(e,17)^r(e,19)^e>>>10}},function(e,t,n){"use strict";var r=n(51),i=n(82),o=n(200),s=n(46),a=r.sum32,u=r.sum32_4,c=r.sum32_5,f=o.ch32,l=o.maj32,d=o.s0_256,h=o.s1_256,p=o.g0_256,v=o.g1_256,g=i.BlockHash,m=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function b(){if(!(this instanceof b))return new b;g.call(this),this.h=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],this.k=m,this.W=new Array(64)}r.inherits(b,g),e.exports=b,b.blockSize=512,b.outSize=256,b.hmacStrength=192,b.padLength=64,b.prototype._update=function(e,t){for(var n=this.W,r=0;r<16;r++)n[r]=e[t+r];for(;r<n.length;r++)n[r]=u(v(n[r-2]),n[r-7],p(n[r-15]),n[r-16]);var i=this.h[0],o=this.h[1],g=this.h[2],m=this.h[3],b=this.h[4],y=this.h[5],w=this.h[6],_=this.h[7];for(s(this.k.length===n.length),r=0;r<n.length;r++){var S=c(_,h(b),f(b,y,w),this.k[r],n[r]),E=a(d(i),l(i,o,g));_=w,w=y,y=b,b=a(m,S),m=g,g=o,o=i,i=a(S,E)}this.h[0]=a(this.h[0],i),this.h[1]=a(this.h[1],o),this.h[2]=a(this.h[2],g),this.h[3]=a(this.h[3],m),this.h[4]=a(this.h[4],b),this.h[5]=a(this.h[5],y),this.h[6]=a(this.h[6],w),this.h[7]=a(this.h[7],_)},b.prototype._digest=function(e){return"hex"===e?r.toHex32(this.h,"big"):r.split32(this.h,"big")}},function(e,t,n){"use strict";var r=n(51),i=n(82),o=n(46),s=r.rotr64_hi,a=r.rotr64_lo,u=r.shr64_hi,c=r.shr64_lo,f=r.sum64,l=r.sum64_hi,d=r.sum64_lo,h=r.sum64_4_hi,p=r.sum64_4_lo,v=r.sum64_5_hi,g=r.sum64_5_lo,m=i.BlockHash,b=[1116352408,3609767458,1899447441,602891725,3049323471,3964484399,3921009573,2173295548,961987163,4081628472,1508970993,3053834265,2453635748,2937671579,2870763221,3664609560,3624381080,2734883394,310598401,1164996542,607225278,1323610764,1426881987,3590304994,1925078388,4068182383,2162078206,991336113,2614888103,633803317,3248222580,3479774868,3835390401,2666613458,4022224774,944711139,264347078,2341262773,604807628,2007800933,770255983,1495990901,1249150122,1856431235,1555081692,3175218132,1996064986,2198950837,2554220882,3999719339,2821834349,766784016,2952996808,2566594879,3210313671,3203337956,3336571891,1034457026,3584528711,2466948901,113926993,3758326383,338241895,168717936,666307205,1188179964,773529912,1546045734,1294757372,1522805485,1396182291,2643833823,1695183700,2343527390,1986661051,1014477480,2177026350,1206759142,2456956037,344077627,2730485921,1290863460,2820302411,3158454273,3259730800,3505952657,3345764771,106217008,3516065817,3606008344,3600352804,1432725776,4094571909,1467031594,275423344,851169720,430227734,3100823752,506948616,1363258195,659060556,3750685593,883997877,3785050280,958139571,3318307427,1322822218,3812723403,1537002063,2003034995,1747873779,3602036899,1955562222,1575990012,2024104815,1125592928,2227730452,2716904306,2361852424,442776044,2428436474,593698344,2756734187,3733110249,3204031479,2999351573,3329325298,3815920427,3391569614,3928383900,3515267271,566280711,3940187606,3454069534,4118630271,4000239992,116418474,1914138554,174292421,2731055270,289380356,3203993006,460393269,320620315,685471733,587496836,852142971,1086792851,1017036298,365543100,1126000580,2618297676,1288033470,3409855158,1501505948,4234509866,1607167915,987167468,1816402316,1246189591];function y(){if(!(this instanceof y))return new y;m.call(this),this.h=[1779033703,4089235720,3144134277,2227873595,1013904242,4271175723,2773480762,1595750129,1359893119,2917565137,2600822924,725511199,528734635,4215389547,1541459225,327033209],this.k=b,this.W=new Array(160)}function w(e,t,n,r,i){var o=e&n^~e&i;return o<0&&(o+=4294967296),o}function _(e,t,n,r,i,o){var s=t&r^~t&o;return s<0&&(s+=4294967296),s}function S(e,t,n,r,i){var o=e&n^e&i^n&i;return o<0&&(o+=4294967296),o}function E(e,t,n,r,i,o){var s=t&r^t&o^r&o;return s<0&&(s+=4294967296),s}function M(e,t){var n=s(e,t,28)^s(t,e,2)^s(t,e,7);return n<0&&(n+=4294967296),n}function A(e,t){var n=a(e,t,28)^a(t,e,2)^a(t,e,7);return n<0&&(n+=4294967296),n}function I(e,t){var n=s(e,t,14)^s(e,t,18)^s(t,e,9);return n<0&&(n+=4294967296),n}function k(e,t){var n=a(e,t,14)^a(e,t,18)^a(t,e,9);return n<0&&(n+=4294967296),n}function O(e,t){var n=s(e,t,1)^s(e,t,8)^u(e,t,7);return n<0&&(n+=4294967296),n}function x(e,t){var n=a(e,t,1)^a(e,t,8)^c(e,t,7);return n<0&&(n+=4294967296),n}function C(e,t){var n=s(e,t,19)^s(t,e,29)^u(e,t,6);return n<0&&(n+=4294967296),n}function T(e,t){var n=a(e,t,19)^a(t,e,29)^c(e,t,6);return n<0&&(n+=4294967296),n}r.inherits(y,m),e.exports=y,y.blockSize=1024,y.outSize=512,y.hmacStrength=192,y.padLength=128,y.prototype._prepareBlock=function(e,t){for(var n=this.W,r=0;r<32;r++)n[r]=e[t+r];for(;r<n.length;r+=2){var i=C(n[r-4],n[r-3]),o=T(n[r-4],n[r-3]),s=n[r-14],a=n[r-13],u=O(n[r-30],n[r-29]),c=x(n[r-30],n[r-29]),f=n[r-32],l=n[r-31];n[r]=h(i,o,s,a,u,c,f,l),n[r+1]=p(i,o,s,a,u,c,f,l)}},y.prototype._update=function(e,t){this._prepareBlock(e,t);var n=this.W,r=this.h[0],i=this.h[1],s=this.h[2],a=this.h[3],u=this.h[4],c=this.h[5],h=this.h[6],p=this.h[7],m=this.h[8],b=this.h[9],y=this.h[10],O=this.h[11],x=this.h[12],C=this.h[13],T=this.h[14],P=this.h[15];o(this.k.length===n.length);for(var N=0;N<n.length;N+=2){var R=T,L=P,j=I(m,b),D=k(m,b),U=w(m,b,y,O,x),B=_(m,b,y,O,x,C),F=this.k[N],z=this.k[N+1],q=n[N],K=n[N+1],H=v(R,L,j,D,U,B,F,z,q,K),V=g(R,L,j,D,U,B,F,z,q,K);R=M(r,i),L=A(r,i),j=S(r,i,s,a,u),D=E(r,i,s,a,u,c);var G=l(R,L,j,D),W=d(R,L,j,D);T=x,P=C,x=y,C=O,y=m,O=b,m=l(h,p,H,V),b=d(p,p,H,V),h=u,p=c,u=s,c=a,s=r,a=i,r=l(H,V,G,W),i=d(H,V,G,W)}f(this.h,0,r,i),f(this.h,2,s,a),f(this.h,4,u,c),f(this.h,6,h,p),f(this.h,8,m,b),f(this.h,10,y,O),f(this.h,12,x,C),f(this.h,14,T,P)},y.prototype._digest=function(e){return"hex"===e?r.toHex32(this.h,"big"):r.split32(this.h,"big")}},function(e,t,n){(function(e){!function(e,t){"use strict";function r(e,t){if(!e)throw new Error(t||"Assertion failed")}function i(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}function o(e,t,n){if(o.isBN(e))return e;this.negative=0,this.words=null,this.length=0,this.red=null,null!==e&&("le"!==t&&"be"!==t||(n=t,t=10),this._init(e||0,t||10,n||"be"))}var s;"object"==typeof e?e.exports=o:t.BN=o,o.BN=o,o.wordSize=26;try{s=n(349).Buffer}catch(e){}function a(e,t,n){for(var i=0,o=Math.min(e.length,n),s=0,a=t;a<o;a++){var u,c=e.charCodeAt(a)-48;i<<=4,i|=u=c>=49&&c<=54?c-49+10:c>=17&&c<=22?c-17+10:c,s|=u}return r(!(240&s),"Invalid character in "+e),i}function u(e,t,n,i){for(var o=0,s=0,a=Math.min(e.length,n),u=t;u<a;u++){var c=e.charCodeAt(u)-48;o*=i,s=c>=49?c-49+10:c>=17?c-17+10:c,r(c>=0&&s<i,"Invalid character"),o+=s}return o}function c(e,t){e.words=t.words,e.length=t.length,e.negative=t.negative,e.red=t.red}if(o.isBN=function(e){return e instanceof o||null!==e&&"object"==typeof e&&e.constructor.wordSize===o.wordSize&&Array.isArray(e.words)},o.max=function(e,t){return e.cmp(t)>0?e:t},o.min=function(e,t){return e.cmp(t)<0?e:t},o.prototype._init=function(e,t,n){if("number"==typeof e)return this._initNumber(e,t,n);if("object"==typeof e)return this._initArray(e,t,n);"hex"===t&&(t=16),r(t===(0|t)&&t>=2&&t<=36);var i=0;"-"===(e=e.toString().replace(/\s+/g,""))[0]&&i++,16===t?this._parseHex(e,i):this._parseBase(e,t,i),"-"===e[0]&&(this.negative=1),this._strip(),"le"===n&&this._initArray(this.toArray(),t,n)},o.prototype._initNumber=function(e,t,n){e<0&&(this.negative=1,e=-e),e<67108864?(this.words=[67108863&e],this.length=1):e<4503599627370496?(this.words=[67108863&e,e/67108864&67108863],this.length=2):(r(e<9007199254740992),this.words=[67108863&e,e/67108864&67108863,1],this.length=3),"le"===n&&this._initArray(this.toArray(),t,n)},o.prototype._initArray=function(e,t,n){if(r("number"==typeof e.length),e.length<=0)return this.words=[0],this.length=1,this;this.length=Math.ceil(e.length/3),this.words=new Array(this.length);for(var i=0;i<this.length;i++)this.words[i]=0;var o,s,a=0;if("be"===n)for(i=e.length-1,o=0;i>=0;i-=3)s=e[i]|e[i-1]<<8|e[i-2]<<16,this.words[o]|=s<<a&67108863,this.words[o+1]=s>>>26-a&67108863,(a+=24)>=26&&(a-=26,o++);else if("le"===n)for(i=0,o=0;i<e.length;i+=3)s=e[i]|e[i+1]<<8|e[i+2]<<16,this.words[o]|=s<<a&67108863,this.words[o+1]=s>>>26-a&67108863,(a+=24)>=26&&(a-=26,o++);return this._strip()},o.prototype._parseHex=function(e,t){this.length=Math.ceil((e.length-t)/6),this.words=new Array(this.length);for(var n=0;n<this.length;n++)this.words[n]=0;var r,i,o=0;for(n=e.length-6,r=0;n>=t;n-=6)i=a(e,n,n+6),this.words[r]|=i<<o&67108863,this.words[r+1]|=i>>>26-o&4194303,(o+=24)>=26&&(o-=26,r++);n+6!==t&&(i=a(e,t,n+6),this.words[r]|=i<<o&67108863,this.words[r+1]|=i>>>26-o&4194303),this._strip()},o.prototype._parseBase=function(e,t,n){this.words=[0],this.length=1;for(var r=0,i=1;i<=67108863;i*=t)r++;r--,i=i/t|0;for(var o=e.length-n,s=o%r,a=Math.min(o,o-s)+n,c=0,f=n;f<a;f+=r)c=u(e,f,f+r,t),this.imuln(i),this.words[0]+c<67108864?this.words[0]+=c:this._iaddn(c);if(0!==s){var l=1;for(c=u(e,f,e.length,t),f=0;f<s;f++)l*=t;this.imuln(l),this.words[0]+c<67108864?this.words[0]+=c:this._iaddn(c)}},o.prototype.copy=function(e){e.words=new Array(this.length);for(var t=0;t<this.length;t++)e.words[t]=this.words[t];e.length=this.length,e.negative=this.negative,e.red=this.red},o.prototype._move=function(e){c(e,this)},o.prototype.clone=function(){var e=new o(null);return this.copy(e),e},o.prototype._expand=function(e){for(;this.length<e;)this.words[this.length++]=0;return this},o.prototype._strip=function(){for(;this.length>1&&0===this.words[this.length-1];)this.length--;return this._normSign()},o.prototype._normSign=function(){return 1===this.length&&0===this.words[0]&&(this.negative=0),this},"undefined"!=typeof Symbol&&"function"==typeof Symbol.for)try{o.prototype[Symbol.for("nodejs.util.inspect.custom")]=f}catch(e){o.prototype.inspect=f}else o.prototype.inspect=f;function f(){return(this.red?"<BN-R: ":"<BN: ")+this.toString(16)+">"}var l=["","0","00","000","0000","00000","000000","0000000","00000000","000000000","0000000000","00000000000","000000000000","0000000000000","00000000000000","000000000000000","0000000000000000","00000000000000000","000000000000000000","0000000000000000000","00000000000000000000","000000000000000000000","0000000000000000000000","00000000000000000000000","000000000000000000000000","0000000000000000000000000"],d=[0,0,25,16,12,11,10,9,8,8,7,7,7,7,6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5],h=[0,0,33554432,43046721,16777216,48828125,60466176,40353607,16777216,43046721,1e7,19487171,35831808,62748517,7529536,11390625,16777216,24137569,34012224,47045881,64e6,4084101,5153632,6436343,7962624,9765625,11881376,14348907,17210368,20511149,243e5,28629151,33554432,39135393,45435424,52521875,60466176];o.prototype.toString=function(e,t){var n;if(t=0|t||1,16===(e=e||10)||"hex"===e){n="";for(var i=0,o=0,s=0;s<this.length;s++){var a=this.words[s],u=(16777215&(a<<i|o)).toString(16);n=0!==(o=a>>>24-i&16777215)||s!==this.length-1?l[6-u.length]+u+n:u+n,(i+=2)>=26&&(i-=26,s--)}for(0!==o&&(n=o.toString(16)+n);n.length%t!=0;)n="0"+n;return 0!==this.negative&&(n="-"+n),n}if(e===(0|e)&&e>=2&&e<=36){var c=d[e],f=h[e];n="";var p=this.clone();for(p.negative=0;!p.isZero();){var v=p.modrn(f).toString(e);n=(p=p.idivn(f)).isZero()?v+n:l[c-v.length]+v+n}for(this.isZero()&&(n="0"+n);n.length%t!=0;)n="0"+n;return 0!==this.negative&&(n="-"+n),n}r(!1,"Base should be between 2 and 36")},o.prototype.toNumber=function(){var e=this.words[0];return 2===this.length?e+=67108864*this.words[1]:3===this.length&&1===this.words[2]?e+=4503599627370496+67108864*this.words[1]:this.length>2&&r(!1,"Number can only safely store up to 53 bits"),0!==this.negative?-e:e},o.prototype.toJSON=function(){return this.toString(16,2)},s&&(o.prototype.toBuffer=function(e,t){return this.toArrayLike(s,e,t)}),o.prototype.toArray=function(e,t){return this.toArrayLike(Array,e,t)};function p(e,t,n){n.negative=t.negative^e.negative;var r=e.length+t.length|0;n.length=r,r=r-1|0;var i=0|e.words[0],o=0|t.words[0],s=i*o,a=67108863&s,u=s/67108864|0;n.words[0]=a;for(var c=1;c<r;c++){for(var f=u>>>26,l=67108863&u,d=Math.min(c,t.length-1),h=Math.max(0,c-e.length+1);h<=d;h++){var p=c-h|0;f+=(s=(i=0|e.words[p])*(o=0|t.words[h])+l)/67108864|0,l=67108863&s}n.words[c]=0|l,u=0|f}return 0!==u?n.words[c]=0|u:n.length--,n._strip()}o.prototype.toArrayLike=function(e,t,n){this._strip();var i=this.byteLength(),o=n||Math.max(1,i);r(i<=o,"byte array longer than desired length"),r(o>0,"Requested array length <= 0");var s=function(e,t){return e.allocUnsafe?e.allocUnsafe(t):new e(t)}(e,o);return this["_toArrayLike"+("le"===t?"LE":"BE")](s,i),s},o.prototype._toArrayLikeLE=function(e,t){for(var n=0,r=0,i=0,o=0;i<this.length;i++){var s=this.words[i]<<o|r;e[n++]=255&s,n<e.length&&(e[n++]=s>>8&255),n<e.length&&(e[n++]=s>>16&255),6===o?(n<e.length&&(e[n++]=s>>24&255),r=0,o=0):(r=s>>>24,o+=2)}if(n<e.length)for(e[n++]=r;n<e.length;)e[n++]=0},o.prototype._toArrayLikeBE=function(e,t){for(var n=e.length-1,r=0,i=0,o=0;i<this.length;i++){var s=this.words[i]<<o|r;e[n--]=255&s,n>=0&&(e[n--]=s>>8&255),n>=0&&(e[n--]=s>>16&255),6===o?(n>=0&&(e[n--]=s>>24&255),r=0,o=0):(r=s>>>24,o+=2)}if(n>=0)for(e[n--]=r;n>=0;)e[n--]=0},Math.clz32?o.prototype._countBits=function(e){return 32-Math.clz32(e)}:o.prototype._countBits=function(e){var t=e,n=0;return t>=4096&&(n+=13,t>>>=13),t>=64&&(n+=7,t>>>=7),t>=8&&(n+=4,t>>>=4),t>=2&&(n+=2,t>>>=2),n+t},o.prototype._zeroBits=function(e){if(0===e)return 26;var t=e,n=0;return 0==(8191&t)&&(n+=13,t>>>=13),0==(127&t)&&(n+=7,t>>>=7),0==(15&t)&&(n+=4,t>>>=4),0==(3&t)&&(n+=2,t>>>=2),0==(1&t)&&n++,n},o.prototype.bitLength=function(){var e=this.words[this.length-1],t=this._countBits(e);return 26*(this.length-1)+t},o.prototype.zeroBits=function(){if(this.isZero())return 0;for(var e=0,t=0;t<this.length;t++){var n=this._zeroBits(this.words[t]);if(e+=n,26!==n)break}return e},o.prototype.byteLength=function(){return Math.ceil(this.bitLength()/8)},o.prototype.toTwos=function(e){return 0!==this.negative?this.abs().inotn(e).iaddn(1):this.clone()},o.prototype.fromTwos=function(e){return this.testn(e-1)?this.notn(e).iaddn(1).ineg():this.clone()},o.prototype.isNeg=function(){return 0!==this.negative},o.prototype.neg=function(){return this.clone().ineg()},o.prototype.ineg=function(){return this.isZero()||(this.negative^=1),this},o.prototype.iuor=function(e){for(;this.length<e.length;)this.words[this.length++]=0;for(var t=0;t<e.length;t++)this.words[t]=this.words[t]|e.words[t];return this._strip()},o.prototype.ior=function(e){return r(0==(this.negative|e.negative)),this.iuor(e)},o.prototype.or=function(e){return this.length>e.length?this.clone().ior(e):e.clone().ior(this)},o.prototype.uor=function(e){return this.length>e.length?this.clone().iuor(e):e.clone().iuor(this)},o.prototype.iuand=function(e){var t;t=this.length>e.length?e:this;for(var n=0;n<t.length;n++)this.words[n]=this.words[n]&e.words[n];return this.length=t.length,this._strip()},o.prototype.iand=function(e){return r(0==(this.negative|e.negative)),this.iuand(e)},o.prototype.and=function(e){return this.length>e.length?this.clone().iand(e):e.clone().iand(this)},o.prototype.uand=function(e){return this.length>e.length?this.clone().iuand(e):e.clone().iuand(this)},o.prototype.iuxor=function(e){var t,n;this.length>e.length?(t=this,n=e):(t=e,n=this);for(var r=0;r<n.length;r++)this.words[r]=t.words[r]^n.words[r];if(this!==t)for(;r<t.length;r++)this.words[r]=t.words[r];return this.length=t.length,this._strip()},o.prototype.ixor=function(e){return r(0==(this.negative|e.negative)),this.iuxor(e)},o.prototype.xor=function(e){return this.length>e.length?this.clone().ixor(e):e.clone().ixor(this)},o.prototype.uxor=function(e){return this.length>e.length?this.clone().iuxor(e):e.clone().iuxor(this)},o.prototype.inotn=function(e){r("number"==typeof e&&e>=0);var t=0|Math.ceil(e/26),n=e%26;this._expand(t),n>0&&t--;for(var i=0;i<t;i++)this.words[i]=67108863&~this.words[i];return n>0&&(this.words[i]=~this.words[i]&67108863>>26-n),this._strip()},o.prototype.notn=function(e){return this.clone().inotn(e)},o.prototype.setn=function(e,t){r("number"==typeof e&&e>=0);var n=e/26|0,i=e%26;return this._expand(n+1),this.words[n]=t?this.words[n]|1<<i:this.words[n]&~(1<<i),this._strip()},o.prototype.iadd=function(e){var t,n,r;if(0!==this.negative&&0===e.negative)return this.negative=0,t=this.isub(e),this.negative^=1,this._normSign();if(0===this.negative&&0!==e.negative)return e.negative=0,t=this.isub(e),e.negative=1,t._normSign();this.length>e.length?(n=this,r=e):(n=e,r=this);for(var i=0,o=0;o<r.length;o++)t=(0|n.words[o])+(0|r.words[o])+i,this.words[o]=67108863&t,i=t>>>26;for(;0!==i&&o<n.length;o++)t=(0|n.words[o])+i,this.words[o]=67108863&t,i=t>>>26;if(this.length=n.length,0!==i)this.words[this.length]=i,this.length++;else if(n!==this)for(;o<n.length;o++)this.words[o]=n.words[o];return this},o.prototype.add=function(e){var t;return 0!==e.negative&&0===this.negative?(e.negative=0,t=this.sub(e),e.negative^=1,t):0===e.negative&&0!==this.negative?(this.negative=0,t=e.sub(this),this.negative=1,t):this.length>e.length?this.clone().iadd(e):e.clone().iadd(this)},o.prototype.isub=function(e){if(0!==e.negative){e.negative=0;var t=this.iadd(e);return e.negative=1,t._normSign()}if(0!==this.negative)return this.negative=0,this.iadd(e),this.negative=1,this._normSign();var n,r,i=this.cmp(e);if(0===i)return this.negative=0,this.length=1,this.words[0]=0,this;i>0?(n=this,r=e):(n=e,r=this);for(var o=0,s=0;s<r.length;s++)o=(t=(0|n.words[s])-(0|r.words[s])+o)>>26,this.words[s]=67108863&t;for(;0!==o&&s<n.length;s++)o=(t=(0|n.words[s])+o)>>26,this.words[s]=67108863&t;if(0===o&&s<n.length&&n!==this)for(;s<n.length;s++)this.words[s]=n.words[s];return this.length=Math.max(this.length,s),n!==this&&(this.negative=1),this._strip()},o.prototype.sub=function(e){return this.clone().isub(e)};var v=function(e,t,n){var r,i,o,s=e.words,a=t.words,u=n.words,c=0,f=0|s[0],l=8191&f,d=f>>>13,h=0|s[1],p=8191&h,v=h>>>13,g=0|s[2],m=8191&g,b=g>>>13,y=0|s[3],w=8191&y,_=y>>>13,S=0|s[4],E=8191&S,M=S>>>13,A=0|s[5],I=8191&A,k=A>>>13,O=0|s[6],x=8191&O,C=O>>>13,T=0|s[7],P=8191&T,N=T>>>13,R=0|s[8],L=8191&R,j=R>>>13,D=0|s[9],U=8191&D,B=D>>>13,F=0|a[0],z=8191&F,q=F>>>13,K=0|a[1],H=8191&K,V=K>>>13,G=0|a[2],W=8191&G,$=G>>>13,Y=0|a[3],J=8191&Y,Z=Y>>>13,X=0|a[4],Q=8191&X,ee=X>>>13,te=0|a[5],ne=8191&te,re=te>>>13,ie=0|a[6],oe=8191&ie,se=ie>>>13,ae=0|a[7],ue=8191&ae,ce=ae>>>13,fe=0|a[8],le=8191&fe,de=fe>>>13,he=0|a[9],pe=8191&he,ve=he>>>13;n.negative=e.negative^t.negative,n.length=19;var ge=(c+(r=Math.imul(l,z))|0)+((8191&(i=(i=Math.imul(l,q))+Math.imul(d,z)|0))<<13)|0;c=((o=Math.imul(d,q))+(i>>>13)|0)+(ge>>>26)|0,ge&=67108863,r=Math.imul(p,z),i=(i=Math.imul(p,q))+Math.imul(v,z)|0,o=Math.imul(v,q);var me=(c+(r=r+Math.imul(l,H)|0)|0)+((8191&(i=(i=i+Math.imul(l,V)|0)+Math.imul(d,H)|0))<<13)|0;c=((o=o+Math.imul(d,V)|0)+(i>>>13)|0)+(me>>>26)|0,me&=67108863,r=Math.imul(m,z),i=(i=Math.imul(m,q))+Math.imul(b,z)|0,o=Math.imul(b,q),r=r+Math.imul(p,H)|0,i=(i=i+Math.imul(p,V)|0)+Math.imul(v,H)|0,o=o+Math.imul(v,V)|0;var be=(c+(r=r+Math.imul(l,W)|0)|0)+((8191&(i=(i=i+Math.imul(l,$)|0)+Math.imul(d,W)|0))<<13)|0;c=((o=o+Math.imul(d,$)|0)+(i>>>13)|0)+(be>>>26)|0,be&=67108863,r=Math.imul(w,z),i=(i=Math.imul(w,q))+Math.imul(_,z)|0,o=Math.imul(_,q),r=r+Math.imul(m,H)|0,i=(i=i+Math.imul(m,V)|0)+Math.imul(b,H)|0,o=o+Math.imul(b,V)|0,r=r+Math.imul(p,W)|0,i=(i=i+Math.imul(p,$)|0)+Math.imul(v,W)|0,o=o+Math.imul(v,$)|0;var ye=(c+(r=r+Math.imul(l,J)|0)|0)+((8191&(i=(i=i+Math.imul(l,Z)|0)+Math.imul(d,J)|0))<<13)|0;c=((o=o+Math.imul(d,Z)|0)+(i>>>13)|0)+(ye>>>26)|0,ye&=67108863,r=Math.imul(E,z),i=(i=Math.imul(E,q))+Math.imul(M,z)|0,o=Math.imul(M,q),r=r+Math.imul(w,H)|0,i=(i=i+Math.imul(w,V)|0)+Math.imul(_,H)|0,o=o+Math.imul(_,V)|0,r=r+Math.imul(m,W)|0,i=(i=i+Math.imul(m,$)|0)+Math.imul(b,W)|0,o=o+Math.imul(b,$)|0,r=r+Math.imul(p,J)|0,i=(i=i+Math.imul(p,Z)|0)+Math.imul(v,J)|0,o=o+Math.imul(v,Z)|0;var we=(c+(r=r+Math.imul(l,Q)|0)|0)+((8191&(i=(i=i+Math.imul(l,ee)|0)+Math.imul(d,Q)|0))<<13)|0;c=((o=o+Math.imul(d,ee)|0)+(i>>>13)|0)+(we>>>26)|0,we&=67108863,r=Math.imul(I,z),i=(i=Math.imul(I,q))+Math.imul(k,z)|0,o=Math.imul(k,q),r=r+Math.imul(E,H)|0,i=(i=i+Math.imul(E,V)|0)+Math.imul(M,H)|0,o=o+Math.imul(M,V)|0,r=r+Math.imul(w,W)|0,i=(i=i+Math.imul(w,$)|0)+Math.imul(_,W)|0,o=o+Math.imul(_,$)|0,r=r+Math.imul(m,J)|0,i=(i=i+Math.imul(m,Z)|0)+Math.imul(b,J)|0,o=o+Math.imul(b,Z)|0,r=r+Math.imul(p,Q)|0,i=(i=i+Math.imul(p,ee)|0)+Math.imul(v,Q)|0,o=o+Math.imul(v,ee)|0;var _e=(c+(r=r+Math.imul(l,ne)|0)|0)+((8191&(i=(i=i+Math.imul(l,re)|0)+Math.imul(d,ne)|0))<<13)|0;c=((o=o+Math.imul(d,re)|0)+(i>>>13)|0)+(_e>>>26)|0,_e&=67108863,r=Math.imul(x,z),i=(i=Math.imul(x,q))+Math.imul(C,z)|0,o=Math.imul(C,q),r=r+Math.imul(I,H)|0,i=(i=i+Math.imul(I,V)|0)+Math.imul(k,H)|0,o=o+Math.imul(k,V)|0,r=r+Math.imul(E,W)|0,i=(i=i+Math.imul(E,$)|0)+Math.imul(M,W)|0,o=o+Math.imul(M,$)|0,r=r+Math.imul(w,J)|0,i=(i=i+Math.imul(w,Z)|0)+Math.imul(_,J)|0,o=o+Math.imul(_,Z)|0,r=r+Math.imul(m,Q)|0,i=(i=i+Math.imul(m,ee)|0)+Math.imul(b,Q)|0,o=o+Math.imul(b,ee)|0,r=r+Math.imul(p,ne)|0,i=(i=i+Math.imul(p,re)|0)+Math.imul(v,ne)|0,o=o+Math.imul(v,re)|0;var Se=(c+(r=r+Math.imul(l,oe)|0)|0)+((8191&(i=(i=i+Math.imul(l,se)|0)+Math.imul(d,oe)|0))<<13)|0;c=((o=o+Math.imul(d,se)|0)+(i>>>13)|0)+(Se>>>26)|0,Se&=67108863,r=Math.imul(P,z),i=(i=Math.imul(P,q))+Math.imul(N,z)|0,o=Math.imul(N,q),r=r+Math.imul(x,H)|0,i=(i=i+Math.imul(x,V)|0)+Math.imul(C,H)|0,o=o+Math.imul(C,V)|0,r=r+Math.imul(I,W)|0,i=(i=i+Math.imul(I,$)|0)+Math.imul(k,W)|0,o=o+Math.imul(k,$)|0,r=r+Math.imul(E,J)|0,i=(i=i+Math.imul(E,Z)|0)+Math.imul(M,J)|0,o=o+Math.imul(M,Z)|0,r=r+Math.imul(w,Q)|0,i=(i=i+Math.imul(w,ee)|0)+Math.imul(_,Q)|0,o=o+Math.imul(_,ee)|0,r=r+Math.imul(m,ne)|0,i=(i=i+Math.imul(m,re)|0)+Math.imul(b,ne)|0,o=o+Math.imul(b,re)|0,r=r+Math.imul(p,oe)|0,i=(i=i+Math.imul(p,se)|0)+Math.imul(v,oe)|0,o=o+Math.imul(v,se)|0;var Ee=(c+(r=r+Math.imul(l,ue)|0)|0)+((8191&(i=(i=i+Math.imul(l,ce)|0)+Math.imul(d,ue)|0))<<13)|0;c=((o=o+Math.imul(d,ce)|0)+(i>>>13)|0)+(Ee>>>26)|0,Ee&=67108863,r=Math.imul(L,z),i=(i=Math.imul(L,q))+Math.imul(j,z)|0,o=Math.imul(j,q),r=r+Math.imul(P,H)|0,i=(i=i+Math.imul(P,V)|0)+Math.imul(N,H)|0,o=o+Math.imul(N,V)|0,r=r+Math.imul(x,W)|0,i=(i=i+Math.imul(x,$)|0)+Math.imul(C,W)|0,o=o+Math.imul(C,$)|0,r=r+Math.imul(I,J)|0,i=(i=i+Math.imul(I,Z)|0)+Math.imul(k,J)|0,o=o+Math.imul(k,Z)|0,r=r+Math.imul(E,Q)|0,i=(i=i+Math.imul(E,ee)|0)+Math.imul(M,Q)|0,o=o+Math.imul(M,ee)|0,r=r+Math.imul(w,ne)|0,i=(i=i+Math.imul(w,re)|0)+Math.imul(_,ne)|0,o=o+Math.imul(_,re)|0,r=r+Math.imul(m,oe)|0,i=(i=i+Math.imul(m,se)|0)+Math.imul(b,oe)|0,o=o+Math.imul(b,se)|0,r=r+Math.imul(p,ue)|0,i=(i=i+Math.imul(p,ce)|0)+Math.imul(v,ue)|0,o=o+Math.imul(v,ce)|0;var Me=(c+(r=r+Math.imul(l,le)|0)|0)+((8191&(i=(i=i+Math.imul(l,de)|0)+Math.imul(d,le)|0))<<13)|0;c=((o=o+Math.imul(d,de)|0)+(i>>>13)|0)+(Me>>>26)|0,Me&=67108863,r=Math.imul(U,z),i=(i=Math.imul(U,q))+Math.imul(B,z)|0,o=Math.imul(B,q),r=r+Math.imul(L,H)|0,i=(i=i+Math.imul(L,V)|0)+Math.imul(j,H)|0,o=o+Math.imul(j,V)|0,r=r+Math.imul(P,W)|0,i=(i=i+Math.imul(P,$)|0)+Math.imul(N,W)|0,o=o+Math.imul(N,$)|0,r=r+Math.imul(x,J)|0,i=(i=i+Math.imul(x,Z)|0)+Math.imul(C,J)|0,o=o+Math.imul(C,Z)|0,r=r+Math.imul(I,Q)|0,i=(i=i+Math.imul(I,ee)|0)+Math.imul(k,Q)|0,o=o+Math.imul(k,ee)|0,r=r+Math.imul(E,ne)|0,i=(i=i+Math.imul(E,re)|0)+Math.imul(M,ne)|0,o=o+Math.imul(M,re)|0,r=r+Math.imul(w,oe)|0,i=(i=i+Math.imul(w,se)|0)+Math.imul(_,oe)|0,o=o+Math.imul(_,se)|0,r=r+Math.imul(m,ue)|0,i=(i=i+Math.imul(m,ce)|0)+Math.imul(b,ue)|0,o=o+Math.imul(b,ce)|0,r=r+Math.imul(p,le)|0,i=(i=i+Math.imul(p,de)|0)+Math.imul(v,le)|0,o=o+Math.imul(v,de)|0;var Ae=(c+(r=r+Math.imul(l,pe)|0)|0)+((8191&(i=(i=i+Math.imul(l,ve)|0)+Math.imul(d,pe)|0))<<13)|0;c=((o=o+Math.imul(d,ve)|0)+(i>>>13)|0)+(Ae>>>26)|0,Ae&=67108863,r=Math.imul(U,H),i=(i=Math.imul(U,V))+Math.imul(B,H)|0,o=Math.imul(B,V),r=r+Math.imul(L,W)|0,i=(i=i+Math.imul(L,$)|0)+Math.imul(j,W)|0,o=o+Math.imul(j,$)|0,r=r+Math.imul(P,J)|0,i=(i=i+Math.imul(P,Z)|0)+Math.imul(N,J)|0,o=o+Math.imul(N,Z)|0,r=r+Math.imul(x,Q)|0,i=(i=i+Math.imul(x,ee)|0)+Math.imul(C,Q)|0,o=o+Math.imul(C,ee)|0,r=r+Math.imul(I,ne)|0,i=(i=i+Math.imul(I,re)|0)+Math.imul(k,ne)|0,o=o+Math.imul(k,re)|0,r=r+Math.imul(E,oe)|0,i=(i=i+Math.imul(E,se)|0)+Math.imul(M,oe)|0,o=o+Math.imul(M,se)|0,r=r+Math.imul(w,ue)|0,i=(i=i+Math.imul(w,ce)|0)+Math.imul(_,ue)|0,o=o+Math.imul(_,ce)|0,r=r+Math.imul(m,le)|0,i=(i=i+Math.imul(m,de)|0)+Math.imul(b,le)|0,o=o+Math.imul(b,de)|0;var Ie=(c+(r=r+Math.imul(p,pe)|0)|0)+((8191&(i=(i=i+Math.imul(p,ve)|0)+Math.imul(v,pe)|0))<<13)|0;c=((o=o+Math.imul(v,ve)|0)+(i>>>13)|0)+(Ie>>>26)|0,Ie&=67108863,r=Math.imul(U,W),i=(i=Math.imul(U,$))+Math.imul(B,W)|0,o=Math.imul(B,$),r=r+Math.imul(L,J)|0,i=(i=i+Math.imul(L,Z)|0)+Math.imul(j,J)|0,o=o+Math.imul(j,Z)|0,r=r+Math.imul(P,Q)|0,i=(i=i+Math.imul(P,ee)|0)+Math.imul(N,Q)|0,o=o+Math.imul(N,ee)|0,r=r+Math.imul(x,ne)|0,i=(i=i+Math.imul(x,re)|0)+Math.imul(C,ne)|0,o=o+Math.imul(C,re)|0,r=r+Math.imul(I,oe)|0,i=(i=i+Math.imul(I,se)|0)+Math.imul(k,oe)|0,o=o+Math.imul(k,se)|0,r=r+Math.imul(E,ue)|0,i=(i=i+Math.imul(E,ce)|0)+Math.imul(M,ue)|0,o=o+Math.imul(M,ce)|0,r=r+Math.imul(w,le)|0,i=(i=i+Math.imul(w,de)|0)+Math.imul(_,le)|0,o=o+Math.imul(_,de)|0;var ke=(c+(r=r+Math.imul(m,pe)|0)|0)+((8191&(i=(i=i+Math.imul(m,ve)|0)+Math.imul(b,pe)|0))<<13)|0;c=((o=o+Math.imul(b,ve)|0)+(i>>>13)|0)+(ke>>>26)|0,ke&=67108863,r=Math.imul(U,J),i=(i=Math.imul(U,Z))+Math.imul(B,J)|0,o=Math.imul(B,Z),r=r+Math.imul(L,Q)|0,i=(i=i+Math.imul(L,ee)|0)+Math.imul(j,Q)|0,o=o+Math.imul(j,ee)|0,r=r+Math.imul(P,ne)|0,i=(i=i+Math.imul(P,re)|0)+Math.imul(N,ne)|0,o=o+Math.imul(N,re)|0,r=r+Math.imul(x,oe)|0,i=(i=i+Math.imul(x,se)|0)+Math.imul(C,oe)|0,o=o+Math.imul(C,se)|0,r=r+Math.imul(I,ue)|0,i=(i=i+Math.imul(I,ce)|0)+Math.imul(k,ue)|0,o=o+Math.imul(k,ce)|0,r=r+Math.imul(E,le)|0,i=(i=i+Math.imul(E,de)|0)+Math.imul(M,le)|0,o=o+Math.imul(M,de)|0;var Oe=(c+(r=r+Math.imul(w,pe)|0)|0)+((8191&(i=(i=i+Math.imul(w,ve)|0)+Math.imul(_,pe)|0))<<13)|0;c=((o=o+Math.imul(_,ve)|0)+(i>>>13)|0)+(Oe>>>26)|0,Oe&=67108863,r=Math.imul(U,Q),i=(i=Math.imul(U,ee))+Math.imul(B,Q)|0,o=Math.imul(B,ee),r=r+Math.imul(L,ne)|0,i=(i=i+Math.imul(L,re)|0)+Math.imul(j,ne)|0,o=o+Math.imul(j,re)|0,r=r+Math.imul(P,oe)|0,i=(i=i+Math.imul(P,se)|0)+Math.imul(N,oe)|0,o=o+Math.imul(N,se)|0,r=r+Math.imul(x,ue)|0,i=(i=i+Math.imul(x,ce)|0)+Math.imul(C,ue)|0,o=o+Math.imul(C,ce)|0,r=r+Math.imul(I,le)|0,i=(i=i+Math.imul(I,de)|0)+Math.imul(k,le)|0,o=o+Math.imul(k,de)|0;var xe=(c+(r=r+Math.imul(E,pe)|0)|0)+((8191&(i=(i=i+Math.imul(E,ve)|0)+Math.imul(M,pe)|0))<<13)|0;c=((o=o+Math.imul(M,ve)|0)+(i>>>13)|0)+(xe>>>26)|0,xe&=67108863,r=Math.imul(U,ne),i=(i=Math.imul(U,re))+Math.imul(B,ne)|0,o=Math.imul(B,re),r=r+Math.imul(L,oe)|0,i=(i=i+Math.imul(L,se)|0)+Math.imul(j,oe)|0,o=o+Math.imul(j,se)|0,r=r+Math.imul(P,ue)|0,i=(i=i+Math.imul(P,ce)|0)+Math.imul(N,ue)|0,o=o+Math.imul(N,ce)|0,r=r+Math.imul(x,le)|0,i=(i=i+Math.imul(x,de)|0)+Math.imul(C,le)|0,o=o+Math.imul(C,de)|0;var Ce=(c+(r=r+Math.imul(I,pe)|0)|0)+((8191&(i=(i=i+Math.imul(I,ve)|0)+Math.imul(k,pe)|0))<<13)|0;c=((o=o+Math.imul(k,ve)|0)+(i>>>13)|0)+(Ce>>>26)|0,Ce&=67108863,r=Math.imul(U,oe),i=(i=Math.imul(U,se))+Math.imul(B,oe)|0,o=Math.imul(B,se),r=r+Math.imul(L,ue)|0,i=(i=i+Math.imul(L,ce)|0)+Math.imul(j,ue)|0,o=o+Math.imul(j,ce)|0,r=r+Math.imul(P,le)|0,i=(i=i+Math.imul(P,de)|0)+Math.imul(N,le)|0,o=o+Math.imul(N,de)|0;var Te=(c+(r=r+Math.imul(x,pe)|0)|0)+((8191&(i=(i=i+Math.imul(x,ve)|0)+Math.imul(C,pe)|0))<<13)|0;c=((o=o+Math.imul(C,ve)|0)+(i>>>13)|0)+(Te>>>26)|0,Te&=67108863,r=Math.imul(U,ue),i=(i=Math.imul(U,ce))+Math.imul(B,ue)|0,o=Math.imul(B,ce),r=r+Math.imul(L,le)|0,i=(i=i+Math.imul(L,de)|0)+Math.imul(j,le)|0,o=o+Math.imul(j,de)|0;var Pe=(c+(r=r+Math.imul(P,pe)|0)|0)+((8191&(i=(i=i+Math.imul(P,ve)|0)+Math.imul(N,pe)|0))<<13)|0;c=((o=o+Math.imul(N,ve)|0)+(i>>>13)|0)+(Pe>>>26)|0,Pe&=67108863,r=Math.imul(U,le),i=(i=Math.imul(U,de))+Math.imul(B,le)|0,o=Math.imul(B,de);var Ne=(c+(r=r+Math.imul(L,pe)|0)|0)+((8191&(i=(i=i+Math.imul(L,ve)|0)+Math.imul(j,pe)|0))<<13)|0;c=((o=o+Math.imul(j,ve)|0)+(i>>>13)|0)+(Ne>>>26)|0,Ne&=67108863;var Re=(c+(r=Math.imul(U,pe))|0)+((8191&(i=(i=Math.imul(U,ve))+Math.imul(B,pe)|0))<<13)|0;return c=((o=Math.imul(B,ve))+(i>>>13)|0)+(Re>>>26)|0,Re&=67108863,u[0]=ge,u[1]=me,u[2]=be,u[3]=ye,u[4]=we,u[5]=_e,u[6]=Se,u[7]=Ee,u[8]=Me,u[9]=Ae,u[10]=Ie,u[11]=ke,u[12]=Oe,u[13]=xe,u[14]=Ce,u[15]=Te,u[16]=Pe,u[17]=Ne,u[18]=Re,0!==c&&(u[19]=c,n.length++),n};function g(e,t,n){n.negative=t.negative^e.negative,n.length=e.length+t.length;for(var r=0,i=0,o=0;o<n.length-1;o++){var s=i;i=0;for(var a=67108863&r,u=Math.min(o,t.length-1),c=Math.max(0,o-e.length+1);c<=u;c++){var f=o-c,l=(0|e.words[f])*(0|t.words[c]),d=67108863&l;a=67108863&(d=d+a|0),i+=(s=(s=s+(l/67108864|0)|0)+(d>>>26)|0)>>>26,s&=67108863}n.words[o]=a,r=s,s=i}return 0!==r?n.words[o]=r:n.length--,n._strip()}function m(e,t,n){return g(e,t,n)}function b(e,t){this.x=e,this.y=t}Math.imul||(v=p),o.prototype.mulTo=function(e,t){var n=this.length+e.length;return 10===this.length&&10===e.length?v(this,e,t):n<63?p(this,e,t):n<1024?g(this,e,t):m(this,e,t)},b.prototype.makeRBT=function(e){for(var t=new Array(e),n=o.prototype._countBits(e)-1,r=0;r<e;r++)t[r]=this.revBin(r,n,e);return t},b.prototype.revBin=function(e,t,n){if(0===e||e===n-1)return e;for(var r=0,i=0;i<t;i++)r|=(1&e)<<t-i-1,e>>=1;return r},b.prototype.permute=function(e,t,n,r,i,o){for(var s=0;s<o;s++)r[s]=t[e[s]],i[s]=n[e[s]]},b.prototype.transform=function(e,t,n,r,i,o){this.permute(o,e,t,n,r,i);for(var s=1;s<i;s<<=1)for(var a=s<<1,u=Math.cos(2*Math.PI/a),c=Math.sin(2*Math.PI/a),f=0;f<i;f+=a)for(var l=u,d=c,h=0;h<s;h++){var p=n[f+h],v=r[f+h],g=n[f+h+s],m=r[f+h+s],b=l*g-d*m;m=l*m+d*g,g=b,n[f+h]=p+g,r[f+h]=v+m,n[f+h+s]=p-g,r[f+h+s]=v-m,h!==a&&(b=u*l-c*d,d=u*d+c*l,l=b)}},b.prototype.guessLen13b=function(e,t){var n=1|Math.max(t,e),r=1&n,i=0;for(n=n/2|0;n;n>>>=1)i++;return 1<<i+1+r},b.prototype.conjugate=function(e,t,n){if(!(n<=1))for(var r=0;r<n/2;r++){var i=e[r];e[r]=e[n-r-1],e[n-r-1]=i,i=t[r],t[r]=-t[n-r-1],t[n-r-1]=-i}},b.prototype.normalize13b=function(e,t){for(var n=0,r=0;r<t/2;r++){var i=8192*Math.round(e[2*r+1]/t)+Math.round(e[2*r]/t)+n;e[r]=67108863&i,n=i<67108864?0:i/67108864|0}return e},b.prototype.convert13b=function(e,t,n,i){for(var o=0,s=0;s<t;s++)o+=0|e[s],n[2*s]=8191&o,o>>>=13,n[2*s+1]=8191&o,o>>>=13;for(s=2*t;s<i;++s)n[s]=0;r(0===o),r(0==(-8192&o))},b.prototype.stub=function(e){for(var t=new Array(e),n=0;n<e;n++)t[n]=0;return t},b.prototype.mulp=function(e,t,n){var r=2*this.guessLen13b(e.length,t.length),i=this.makeRBT(r),o=this.stub(r),s=new Array(r),a=new Array(r),u=new Array(r),c=new Array(r),f=new Array(r),l=new Array(r),d=n.words;d.length=r,this.convert13b(e.words,e.length,s,r),this.convert13b(t.words,t.length,c,r),this.transform(s,o,a,u,r,i),this.transform(c,o,f,l,r,i);for(var h=0;h<r;h++){var p=a[h]*f[h]-u[h]*l[h];u[h]=a[h]*l[h]+u[h]*f[h],a[h]=p}return this.conjugate(a,u,r),this.transform(a,u,d,o,r,i),this.conjugate(d,o,r),this.normalize13b(d,r),n.negative=e.negative^t.negative,n.length=e.length+t.length,n._strip()},o.prototype.mul=function(e){var t=new o(null);return t.words=new Array(this.length+e.length),this.mulTo(e,t)},o.prototype.mulf=function(e){var t=new o(null);return t.words=new Array(this.length+e.length),m(this,e,t)},o.prototype.imul=function(e){return this.clone().mulTo(e,this)},o.prototype.imuln=function(e){var t=e<0;t&&(e=-e),r("number"==typeof e),r(e<67108864);for(var n=0,i=0;i<this.length;i++){var o=(0|this.words[i])*e,s=(67108863&o)+(67108863&n);n>>=26,n+=o/67108864|0,n+=s>>>26,this.words[i]=67108863&s}return 0!==n&&(this.words[i]=n,this.length++),t?this.ineg():this},o.prototype.muln=function(e){return this.clone().imuln(e)},o.prototype.sqr=function(){return this.mul(this)},o.prototype.isqr=function(){return this.imul(this.clone())},o.prototype.pow=function(e){var t=function(e){for(var t=new Array(e.bitLength()),n=0;n<t.length;n++){var r=n/26|0,i=n%26;t[n]=e.words[r]>>>i&1}return t}(e);if(0===t.length)return new o(1);for(var n=this,r=0;r<t.length&&0===t[r];r++,n=n.sqr());if(++r<t.length)for(var i=n.sqr();r<t.length;r++,i=i.sqr())0!==t[r]&&(n=n.mul(i));return n},o.prototype.iushln=function(e){r("number"==typeof e&&e>=0);var t,n=e%26,i=(e-n)/26,o=67108863>>>26-n<<26-n;if(0!==n){var s=0;for(t=0;t<this.length;t++){var a=this.words[t]&o,u=(0|this.words[t])-a<<n;this.words[t]=u|s,s=a>>>26-n}s&&(this.words[t]=s,this.length++)}if(0!==i){for(t=this.length-1;t>=0;t--)this.words[t+i]=this.words[t];for(t=0;t<i;t++)this.words[t]=0;this.length+=i}return this._strip()},o.prototype.ishln=function(e){return r(0===this.negative),this.iushln(e)},o.prototype.iushrn=function(e,t,n){var i;r("number"==typeof e&&e>=0),i=t?(t-t%26)/26:0;var o=e%26,s=Math.min((e-o)/26,this.length),a=67108863^67108863>>>o<<o,u=n;if(i-=s,i=Math.max(0,i),u){for(var c=0;c<s;c++)u.words[c]=this.words[c];u.length=s}if(0===s);else if(this.length>s)for(this.length-=s,c=0;c<this.length;c++)this.words[c]=this.words[c+s];else this.words[0]=0,this.length=1;var f=0;for(c=this.length-1;c>=0&&(0!==f||c>=i);c--){var l=0|this.words[c];this.words[c]=f<<26-o|l>>>o,f=l&a}return u&&0!==f&&(u.words[u.length++]=f),0===this.length&&(this.words[0]=0,this.length=1),this._strip()},o.prototype.ishrn=function(e,t,n){return r(0===this.negative),this.iushrn(e,t,n)},o.prototype.shln=function(e){return this.clone().ishln(e)},o.prototype.ushln=function(e){return this.clone().iushln(e)},o.prototype.shrn=function(e){return this.clone().ishrn(e)},o.prototype.ushrn=function(e){return this.clone().iushrn(e)},o.prototype.testn=function(e){r("number"==typeof e&&e>=0);var t=e%26,n=(e-t)/26,i=1<<t;return!(this.length<=n)&&!!(this.words[n]&i)},o.prototype.imaskn=function(e){r("number"==typeof e&&e>=0);var t=e%26,n=(e-t)/26;if(r(0===this.negative,"imaskn works only with positive numbers"),this.length<=n)return this;if(0!==t&&n++,this.length=Math.min(n,this.length),0!==t){var i=67108863^67108863>>>t<<t;this.words[this.length-1]&=i}return this._strip()},o.prototype.maskn=function(e){return this.clone().imaskn(e)},o.prototype.iaddn=function(e){return r("number"==typeof e),r(e<67108864),e<0?this.isubn(-e):0!==this.negative?1===this.length&&(0|this.words[0])<=e?(this.words[0]=e-(0|this.words[0]),this.negative=0,this):(this.negative=0,this.isubn(e),this.negative=1,this):this._iaddn(e)},o.prototype._iaddn=function(e){this.words[0]+=e;for(var t=0;t<this.length&&this.words[t]>=67108864;t++)this.words[t]-=67108864,t===this.length-1?this.words[t+1]=1:this.words[t+1]++;return this.length=Math.max(this.length,t+1),this},o.prototype.isubn=function(e){if(r("number"==typeof e),r(e<67108864),e<0)return this.iaddn(-e);if(0!==this.negative)return this.negative=0,this.iaddn(e),this.negative=1,this;if(this.words[0]-=e,1===this.length&&this.words[0]<0)this.words[0]=-this.words[0],this.negative=1;else for(var t=0;t<this.length&&this.words[t]<0;t++)this.words[t]+=67108864,this.words[t+1]-=1;return this._strip()},o.prototype.addn=function(e){return this.clone().iaddn(e)},o.prototype.subn=function(e){return this.clone().isubn(e)},o.prototype.iabs=function(){return this.negative=0,this},o.prototype.abs=function(){return this.clone().iabs()},o.prototype._ishlnsubmul=function(e,t,n){var i,o,s=e.length+n;this._expand(s);var a=0;for(i=0;i<e.length;i++){o=(0|this.words[i+n])+a;var u=(0|e.words[i])*t;a=((o-=67108863&u)>>26)-(u/67108864|0),this.words[i+n]=67108863&o}for(;i<this.length-n;i++)a=(o=(0|this.words[i+n])+a)>>26,this.words[i+n]=67108863&o;if(0===a)return this._strip();for(r(-1===a),a=0,i=0;i<this.length;i++)a=(o=-(0|this.words[i])+a)>>26,this.words[i]=67108863&o;return this.negative=1,this._strip()},o.prototype._wordDiv=function(e,t){var n=(this.length,e.length),r=this.clone(),i=e,s=0|i.words[i.length-1];0!==(n=26-this._countBits(s))&&(i=i.ushln(n),r.iushln(n),s=0|i.words[i.length-1]);var a,u=r.length-i.length;if("mod"!==t){(a=new o(null)).length=u+1,a.words=new Array(a.length);for(var c=0;c<a.length;c++)a.words[c]=0}var f=r.clone()._ishlnsubmul(i,1,u);0===f.negative&&(r=f,a&&(a.words[u]=1));for(var l=u-1;l>=0;l--){var d=67108864*(0|r.words[i.length+l])+(0|r.words[i.length+l-1]);for(d=Math.min(d/s|0,67108863),r._ishlnsubmul(i,d,l);0!==r.negative;)d--,r.negative=0,r._ishlnsubmul(i,1,l),r.isZero()||(r.negative^=1);a&&(a.words[l]=d)}return a&&a._strip(),r._strip(),"div"!==t&&0!==n&&r.iushrn(n),{div:a||null,mod:r}},o.prototype.divmod=function(e,t,n){return r(!e.isZero()),this.isZero()?{div:new o(0),mod:new o(0)}:0!==this.negative&&0===e.negative?(a=this.neg().divmod(e,t),"mod"!==t&&(i=a.div.neg()),"div"!==t&&(s=a.mod.neg(),n&&0!==s.negative&&s.iadd(e)),{div:i,mod:s}):0===this.negative&&0!==e.negative?(a=this.divmod(e.neg(),t),"mod"!==t&&(i=a.div.neg()),{div:i,mod:a.mod}):0!=(this.negative&e.negative)?(a=this.neg().divmod(e.neg(),t),"div"!==t&&(s=a.mod.neg(),n&&0!==s.negative&&s.isub(e)),{div:a.div,mod:s}):e.length>this.length||this.cmp(e)<0?{div:new o(0),mod:this}:1===e.length?"div"===t?{div:this.divn(e.words[0]),mod:null}:"mod"===t?{div:null,mod:new o(this.modrn(e.words[0]))}:{div:this.divn(e.words[0]),mod:new o(this.modrn(e.words[0]))}:this._wordDiv(e,t);var i,s,a},o.prototype.div=function(e){return this.divmod(e,"div",!1).div},o.prototype.mod=function(e){return this.divmod(e,"mod",!1).mod},o.prototype.umod=function(e){return this.divmod(e,"mod",!0).mod},o.prototype.divRound=function(e){var t=this.divmod(e);if(t.mod.isZero())return t.div;var n=0!==t.div.negative?t.mod.isub(e):t.mod,r=e.ushrn(1),i=e.andln(1),o=n.cmp(r);return o<0||1===i&&0===o?t.div:0!==t.div.negative?t.div.isubn(1):t.div.iaddn(1)},o.prototype.modrn=function(e){var t=e<0;t&&(e=-e),r(e<=67108863);for(var n=(1<<26)%e,i=0,o=this.length-1;o>=0;o--)i=(n*i+(0|this.words[o]))%e;return t?-i:i},o.prototype.modn=function(e){return this.modrn(e)},o.prototype.idivn=function(e){var t=e<0;t&&(e=-e),r(e<=67108863);for(var n=0,i=this.length-1;i>=0;i--){var o=(0|this.words[i])+67108864*n;this.words[i]=o/e|0,n=o%e}return this._strip(),t?this.ineg():this},o.prototype.divn=function(e){return this.clone().idivn(e)},o.prototype.egcd=function(e){r(0===e.negative),r(!e.isZero());var t=this,n=e.clone();t=0!==t.negative?t.umod(e):t.clone();for(var i=new o(1),s=new o(0),a=new o(0),u=new o(1),c=0;t.isEven()&&n.isEven();)t.iushrn(1),n.iushrn(1),++c;for(var f=n.clone(),l=t.clone();!t.isZero();){for(var d=0,h=1;0==(t.words[0]&h)&&d<26;++d,h<<=1);if(d>0)for(t.iushrn(d);d-- >0;)(i.isOdd()||s.isOdd())&&(i.iadd(f),s.isub(l)),i.iushrn(1),s.iushrn(1);for(var p=0,v=1;0==(n.words[0]&v)&&p<26;++p,v<<=1);if(p>0)for(n.iushrn(p);p-- >0;)(a.isOdd()||u.isOdd())&&(a.iadd(f),u.isub(l)),a.iushrn(1),u.iushrn(1);t.cmp(n)>=0?(t.isub(n),i.isub(a),s.isub(u)):(n.isub(t),a.isub(i),u.isub(s))}return{a:a,b:u,gcd:n.iushln(c)}},o.prototype._invmp=function(e){r(0===e.negative),r(!e.isZero());var t=this,n=e.clone();t=0!==t.negative?t.umod(e):t.clone();for(var i,s=new o(1),a=new o(0),u=n.clone();t.cmpn(1)>0&&n.cmpn(1)>0;){for(var c=0,f=1;0==(t.words[0]&f)&&c<26;++c,f<<=1);if(c>0)for(t.iushrn(c);c-- >0;)s.isOdd()&&s.iadd(u),s.iushrn(1);for(var l=0,d=1;0==(n.words[0]&d)&&l<26;++l,d<<=1);if(l>0)for(n.iushrn(l);l-- >0;)a.isOdd()&&a.iadd(u),a.iushrn(1);t.cmp(n)>=0?(t.isub(n),s.isub(a)):(n.isub(t),a.isub(s))}return(i=0===t.cmpn(1)?s:a).cmpn(0)<0&&i.iadd(e),i},o.prototype.gcd=function(e){if(this.isZero())return e.abs();if(e.isZero())return this.abs();var t=this.clone(),n=e.clone();t.negative=0,n.negative=0;for(var r=0;t.isEven()&&n.isEven();r++)t.iushrn(1),n.iushrn(1);for(;;){for(;t.isEven();)t.iushrn(1);for(;n.isEven();)n.iushrn(1);var i=t.cmp(n);if(i<0){var o=t;t=n,n=o}else if(0===i||0===n.cmpn(1))break;t.isub(n)}return n.iushln(r)},o.prototype.invm=function(e){return this.egcd(e).a.umod(e)},o.prototype.isEven=function(){return 0==(1&this.words[0])},o.prototype.isOdd=function(){return 1==(1&this.words[0])},o.prototype.andln=function(e){return this.words[0]&e},o.prototype.bincn=function(e){r("number"==typeof e);var t=e%26,n=(e-t)/26,i=1<<t;if(this.length<=n)return this._expand(n+1),this.words[n]|=i,this;for(var o=i,s=n;0!==o&&s<this.length;s++){var a=0|this.words[s];o=(a+=o)>>>26,a&=67108863,this.words[s]=a}return 0!==o&&(this.words[s]=o,this.length++),this},o.prototype.isZero=function(){return 1===this.length&&0===this.words[0]},o.prototype.cmpn=function(e){var t,n=e<0;if(0!==this.negative&&!n)return-1;if(0===this.negative&&n)return 1;if(this._strip(),this.length>1)t=1;else{n&&(e=-e),r(e<=67108863,"Number is too big");var i=0|this.words[0];t=i===e?0:i<e?-1:1}return 0!==this.negative?0|-t:t},o.prototype.cmp=function(e){if(0!==this.negative&&0===e.negative)return-1;if(0===this.negative&&0!==e.negative)return 1;var t=this.ucmp(e);return 0!==this.negative?0|-t:t},o.prototype.ucmp=function(e){if(this.length>e.length)return 1;if(this.length<e.length)return-1;for(var t=0,n=this.length-1;n>=0;n--){var r=0|this.words[n],i=0|e.words[n];if(r!==i){r<i?t=-1:r>i&&(t=1);break}}return t},o.prototype.gtn=function(e){return 1===this.cmpn(e)},o.prototype.gt=function(e){return 1===this.cmp(e)},o.prototype.gten=function(e){return this.cmpn(e)>=0},o.prototype.gte=function(e){return this.cmp(e)>=0},o.prototype.ltn=function(e){return-1===this.cmpn(e)},o.prototype.lt=function(e){return-1===this.cmp(e)},o.prototype.lten=function(e){return this.cmpn(e)<=0},o.prototype.lte=function(e){return this.cmp(e)<=0},o.prototype.eqn=function(e){return 0===this.cmpn(e)},o.prototype.eq=function(e){return 0===this.cmp(e)},o.red=function(e){return new A(e)},o.prototype.toRed=function(e){return r(!this.red,"Already a number in reduction context"),r(0===this.negative,"red works only with positives"),e.convertTo(this)._forceRed(e)},o.prototype.fromRed=function(){return r(this.red,"fromRed works only with numbers in reduction context"),this.red.convertFrom(this)},o.prototype._forceRed=function(e){return this.red=e,this},o.prototype.forceRed=function(e){return r(!this.red,"Already a number in reduction context"),this._forceRed(e)},o.prototype.redAdd=function(e){return r(this.red,"redAdd works only with red numbers"),this.red.add(this,e)},o.prototype.redIAdd=function(e){return r(this.red,"redIAdd works only with red numbers"),this.red.iadd(this,e)},o.prototype.redSub=function(e){return r(this.red,"redSub works only with red numbers"),this.red.sub(this,e)},o.prototype.redISub=function(e){return r(this.red,"redISub works only with red numbers"),this.red.isub(this,e)},o.prototype.redShl=function(e){return r(this.red,"redShl works only with red numbers"),this.red.shl(this,e)},o.prototype.redMul=function(e){return r(this.red,"redMul works only with red numbers"),this.red._verify2(this,e),this.red.mul(this,e)},o.prototype.redIMul=function(e){return r(this.red,"redMul works only with red numbers"),this.red._verify2(this,e),this.red.imul(this,e)},o.prototype.redSqr=function(){return r(this.red,"redSqr works only with red numbers"),this.red._verify1(this),this.red.sqr(this)},o.prototype.redISqr=function(){return r(this.red,"redISqr works only with red numbers"),this.red._verify1(this),this.red.isqr(this)},o.prototype.redSqrt=function(){return r(this.red,"redSqrt works only with red numbers"),this.red._verify1(this),this.red.sqrt(this)},o.prototype.redInvm=function(){return r(this.red,"redInvm works only with red numbers"),this.red._verify1(this),this.red.invm(this)},o.prototype.redNeg=function(){return r(this.red,"redNeg works only with red numbers"),this.red._verify1(this),this.red.neg(this)},o.prototype.redPow=function(e){return r(this.red&&!e.red,"redPow(normalNum)"),this.red._verify1(this),this.red.pow(this,e)};var y={k256:null,p224:null,p192:null,p25519:null};function w(e,t){this.name=e,this.p=new o(t,16),this.n=this.p.bitLength(),this.k=new o(1).iushln(this.n).isub(this.p),this.tmp=this._tmp()}function _(){w.call(this,"k256","ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f")}function S(){w.call(this,"p224","ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001")}function E(){w.call(this,"p192","ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff")}function M(){w.call(this,"25519","7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed")}function A(e){if("string"==typeof e){var t=o._prime(e);this.m=t.p,this.prime=t}else r(e.gtn(1),"modulus must be greater than 1"),this.m=e,this.prime=null}function I(e){A.call(this,e),this.shift=this.m.bitLength(),this.shift%26!=0&&(this.shift+=26-this.shift%26),this.r=new o(1).iushln(this.shift),this.r2=this.imod(this.r.sqr()),this.rinv=this.r._invmp(this.m),this.minv=this.rinv.mul(this.r).isubn(1).div(this.m),this.minv=this.minv.umod(this.r),this.minv=this.r.sub(this.minv)}w.prototype._tmp=function(){var e=new o(null);return e.words=new Array(Math.ceil(this.n/13)),e},w.prototype.ireduce=function(e){var t,n=e;do{this.split(n,this.tmp),t=(n=(n=this.imulK(n)).iadd(this.tmp)).bitLength()}while(t>this.n);var r=t<this.n?-1:n.ucmp(this.p);return 0===r?(n.words[0]=0,n.length=1):r>0?n.isub(this.p):void 0!==n.strip?n.strip():n._strip(),n},w.prototype.split=function(e,t){e.iushrn(this.n,0,t)},w.prototype.imulK=function(e){return e.imul(this.k)},i(_,w),_.prototype.split=function(e,t){for(var n=Math.min(e.length,9),r=0;r<n;r++)t.words[r]=e.words[r];if(t.length=n,e.length<=9)return e.words[0]=0,void(e.length=1);var i=e.words[9];for(t.words[t.length++]=4194303&i,r=10;r<e.length;r++){var o=0|e.words[r];e.words[r-10]=(4194303&o)<<4|i>>>22,i=o}i>>>=22,e.words[r-10]=i,0===i&&e.length>10?e.length-=10:e.length-=9},_.prototype.imulK=function(e){e.words[e.length]=0,e.words[e.length+1]=0,e.length+=2;for(var t=0,n=0;n<e.length;n++){var r=0|e.words[n];t+=977*r,e.words[n]=67108863&t,t=64*r+(t/67108864|0)}return 0===e.words[e.length-1]&&(e.length--,0===e.words[e.length-1]&&e.length--),e},i(S,w),i(E,w),i(M,w),M.prototype.imulK=function(e){for(var t=0,n=0;n<e.length;n++){var r=19*(0|e.words[n])+t,i=67108863&r;r>>>=26,e.words[n]=i,t=r}return 0!==t&&(e.words[e.length++]=t),e},o._prime=function(e){if(y[e])return y[e];var t;if("k256"===e)t=new _;else if("p224"===e)t=new S;else if("p192"===e)t=new E;else{if("p25519"!==e)throw new Error("Unknown prime "+e);t=new M}return y[e]=t,t},A.prototype._verify1=function(e){r(0===e.negative,"red works only with positives"),r(e.red,"red works only with red numbers")},A.prototype._verify2=function(e,t){r(0==(e.negative|t.negative),"red works only with positives"),r(e.red&&e.red===t.red,"red works only with red numbers")},A.prototype.imod=function(e){return this.prime?this.prime.ireduce(e)._forceRed(this):(c(e,e.umod(this.m)._forceRed(this)),e)},A.prototype.neg=function(e){return e.isZero()?e.clone():this.m.sub(e)._forceRed(this)},A.prototype.add=function(e,t){this._verify2(e,t);var n=e.add(t);return n.cmp(this.m)>=0&&n.isub(this.m),n._forceRed(this)},A.prototype.iadd=function(e,t){this._verify2(e,t);var n=e.iadd(t);return n.cmp(this.m)>=0&&n.isub(this.m),n},A.prototype.sub=function(e,t){this._verify2(e,t);var n=e.sub(t);return n.cmpn(0)<0&&n.iadd(this.m),n._forceRed(this)},A.prototype.isub=function(e,t){this._verify2(e,t);var n=e.isub(t);return n.cmpn(0)<0&&n.iadd(this.m),n},A.prototype.shl=function(e,t){return this._verify1(e),this.imod(e.ushln(t))},A.prototype.imul=function(e,t){return this._verify2(e,t),this.imod(e.imul(t))},A.prototype.mul=function(e,t){return this._verify2(e,t),this.imod(e.mul(t))},A.prototype.isqr=function(e){return this.imul(e,e.clone())},A.prototype.sqr=function(e){return this.mul(e,e)},A.prototype.sqrt=function(e){if(e.isZero())return e.clone();var t=this.m.andln(3);if(r(t%2==1),3===t){var n=this.m.add(new o(1)).iushrn(2);return this.pow(e,n)}for(var i=this.m.subn(1),s=0;!i.isZero()&&0===i.andln(1);)s++,i.iushrn(1);r(!i.isZero());var a=new o(1).toRed(this),u=a.redNeg(),c=this.m.subn(1).iushrn(1),f=this.m.bitLength();for(f=new o(2*f*f).toRed(this);0!==this.pow(f,c).cmp(u);)f.redIAdd(u);for(var l=this.pow(f,i),d=this.pow(e,i.addn(1).iushrn(1)),h=this.pow(e,i),p=s;0!==h.cmp(a);){for(var v=h,g=0;0!==v.cmp(a);g++)v=v.redSqr();r(g<p);var m=this.pow(l,new o(1).iushln(p-g-1));d=d.redMul(m),l=m.redSqr(),h=h.redMul(l),p=g}return d},A.prototype.invm=function(e){var t=e._invmp(this.m);return 0!==t.negative?(t.negative=0,this.imod(t).redNeg()):this.imod(t)},A.prototype.pow=function(e,t){if(t.isZero())return new o(1).toRed(this);if(0===t.cmpn(1))return e.clone();var n=new Array(16);n[0]=new o(1).toRed(this),n[1]=e;for(var r=2;r<n.length;r++)n[r]=this.mul(n[r-1],e);var i=n[0],s=0,a=0,u=t.bitLength()%26;for(0===u&&(u=26),r=t.length-1;r>=0;r--){for(var c=t.words[r],f=u-1;f>=0;f--){var l=c>>f&1;i!==n[0]&&(i=this.sqr(i)),0!==l||0!==s?(s<<=1,s|=l,(4===++a||0===r&&0===f)&&(i=this.mul(i,n[s]),a=0,s=0)):a=0}u=26}return i},A.prototype.convertTo=function(e){var t=e.umod(this.m);return t===e?t.clone():t},A.prototype.convertFrom=function(e){var t=e.clone();return t.red=null,t},o.mont=function(e){return new I(e)},i(I,A),I.prototype.convertTo=function(e){return this.imod(e.ushln(this.shift))},I.prototype.convertFrom=function(e){var t=this.imod(e.mul(this.rinv));return t.red=null,t},I.prototype.imul=function(e,t){if(e.isZero()||t.isZero())return e.words[0]=0,e.length=1,e;var n=e.imul(t),r=n.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=n.isub(r).iushrn(this.shift),o=i;return i.cmp(this.m)>=0?o=i.isub(this.m):i.cmpn(0)<0&&(o=i.iadd(this.m)),o._forceRed(this)},I.prototype.mul=function(e,t){if(e.isZero()||t.isZero())return new o(0)._forceRed(this);var n=e.mul(t),r=n.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=n.isub(r).iushrn(this.shift),s=i;return i.cmp(this.m)>=0?s=i.isub(this.m):i.cmpn(0)<0&&(s=i.iadd(this.m)),s._forceRed(this)},I.prototype.invm=function(e){return this.imod(e._invmp(this.m).mul(this.r2))._forceRed(this)}}(e,this)}).call(this,n(57)(e))},function(e,t,n){"use strict";const r=t;r.bignum=n(29),r.define=n(351).define,r.base=n(354),r.constants=n(355),r.decoders=n(207),r.encoders=n(205)},function(e,t,n){"use strict";const r=t;r.der=n(206),r.pem=n(352)},function(e,t,n){"use strict";const r=n(7),i=n(130).Buffer,o=n(131),s=n(133);function a(e){this.enc="der",this.name=e.name,this.entity=e,this.tree=new u,this.tree._init(e.body)}function u(e){o.call(this,"der",e)}function c(e){return e<10?"0"+e:e}e.exports=a,a.prototype.encode=function(e,t){return this.tree._encode(e,t).join()},r(u,o),u.prototype._encodeComposite=function(e,t,n,r){const o=function(e,t,n,r){let i;"seqof"===e?e="seq":"setof"===e&&(e="set");if(s.tagByName.hasOwnProperty(e))i=s.tagByName[e];else{if("number"!=typeof e||(0|e)!==e)return r.error("Unknown tag: "+e);i=e}if(i>=31)return r.error("Multi-octet tag encoding unsupported");t||(i|=32);return i|=s.tagClassByName[n||"universal"]<<6,i}(e,t,n,this.reporter);if(r.length<128){const e=i.alloc(2);return e[0]=o,e[1]=r.length,this._createEncoderBuffer([e,r])}let a=1;for(let e=r.length;e>=256;e>>=8)a++;const u=i.alloc(2+a);u[0]=o,u[1]=128|a;for(let e=1+a,t=r.length;t>0;e--,t>>=8)u[e]=255&t;return this._createEncoderBuffer([u,r])},u.prototype._encodeStr=function(e,t){if("bitstr"===t)return this._createEncoderBuffer([0|e.unused,e.data]);if("bmpstr"===t){const t=i.alloc(2*e.length);for(let n=0;n<e.length;n++)t.writeUInt16BE(e.charCodeAt(n),2*n);return this._createEncoderBuffer(t)}return"numstr"===t?this._isNumstr(e)?this._createEncoderBuffer(e):this.reporter.error("Encoding of string type: numstr supports only digits and space"):"printstr"===t?this._isPrintstr(e)?this._createEncoderBuffer(e):this.reporter.error("Encoding of string type: printstr supports only latin upper and lower case letters, digits, space, apostrophe, left and rigth parenthesis, plus sign, comma, hyphen, dot, slash, colon, equal sign, question mark"):/str$/.test(t)||"objDesc"===t?this._createEncoderBuffer(e):this.reporter.error("Encoding of string type: "+t+" unsupported")},u.prototype._encodeObjid=function(e,t,n){if("string"==typeof e){if(!t)return this.reporter.error("string objid given, but no values map found");if(!t.hasOwnProperty(e))return this.reporter.error("objid not found in values map");e=t[e].split(/[\s.]+/g);for(let t=0;t<e.length;t++)e[t]|=0}else if(Array.isArray(e)){e=e.slice();for(let t=0;t<e.length;t++)e[t]|=0}if(!Array.isArray(e))return this.reporter.error("objid() should be either array or string, got: "+JSON.stringify(e));if(!n){if(e[1]>=40)return this.reporter.error("Second objid identifier OOB");e.splice(0,2,40*e[0]+e[1])}let r=0;for(let t=0;t<e.length;t++){let n=e[t];for(r++;n>=128;n>>=7)r++}const o=i.alloc(r);let s=o.length-1;for(let t=e.length-1;t>=0;t--){let n=e[t];for(o[s--]=127&n;(n>>=7)>0;)o[s--]=128|127&n}return this._createEncoderBuffer(o)},u.prototype._encodeTime=function(e,t){let n;const r=new Date(e);return"gentime"===t?n=[c(r.getUTCFullYear()),c(r.getUTCMonth()+1),c(r.getUTCDate()),c(r.getUTCHours()),c(r.getUTCMinutes()),c(r.getUTCSeconds()),"Z"].join(""):"utctime"===t?n=[c(r.getUTCFullYear()%100),c(r.getUTCMonth()+1),c(r.getUTCDate()),c(r.getUTCHours()),c(r.getUTCMinutes()),c(r.getUTCSeconds()),"Z"].join(""):this.reporter.error("Encoding "+t+" time is not supported yet"),this._encodeStr(n,"octstr")},u.prototype._encodeNull=function(){return this._createEncoderBuffer("")},u.prototype._encodeInt=function(e,t){if("string"==typeof e){if(!t)return this.reporter.error("String int or enum given, but no values map");if(!t.hasOwnProperty(e))return this.reporter.error("Values map doesn't contain: "+JSON.stringify(e));e=t[e]}if("number"!=typeof e&&!i.isBuffer(e)){const t=e.toArray();!e.sign&&128&t[0]&&t.unshift(0),e=i.from(t)}if(i.isBuffer(e)){let t=e.length;0===e.length&&t++;const n=i.alloc(t);return e.copy(n),0===e.length&&(n[0]=0),this._createEncoderBuffer(n)}if(e<128)return this._createEncoderBuffer(e);if(e<256)return this._createEncoderBuffer([0,e]);let n=1;for(let t=e;t>=256;t>>=8)n++;const r=new Array(n);for(let t=r.length-1;t>=0;t--)r[t]=255&e,e>>=8;return 128&r[0]&&r.unshift(0),this._createEncoderBuffer(i.from(r))},u.prototype._encodeBool=function(e){return this._createEncoderBuffer(e?255:0)},u.prototype._use=function(e,t){return"function"==typeof e&&(e=e(t)),e._getEncoder("der").tree},u.prototype._skipDefault=function(e,t,n){const r=this._baseState;let i;if(null===r.default)return!1;const o=e.join();if(void 0===r.defaultBuffer&&(r.defaultBuffer=this._encodeValue(r.default,t,n).join()),o.length!==r.defaultBuffer.length)return!1;for(i=0;i<o.length;i++)if(o[i]!==r.defaultBuffer[i])return!1;return!0}},function(e,t,n){"use strict";const r=t;r.der=n(208),r.pem=n(353)},function(e,t,n){"use strict";const r=n(7),i=n(29),o=n(83).DecoderBuffer,s=n(131),a=n(133);function u(e){this.enc="der",this.name=e.name,this.entity=e,this.tree=new c,this.tree._init(e.body)}function c(e){s.call(this,"der",e)}function f(e,t){let n=e.readUInt8(t);if(e.isError(n))return n;const r=a.tagClass[n>>6],i=0==(32&n);if(31==(31&n)){let r=n;for(n=0;128==(128&r);){if(r=e.readUInt8(t),e.isError(r))return r;n<<=7,n|=127&r}}else n&=31;return{cls:r,primitive:i,tag:n,tagStr:a.tag[n]}}function l(e,t,n){let r=e.readUInt8(n);if(e.isError(r))return r;if(!t&&128===r)return null;if(0==(128&r))return r;const i=127&r;if(i>4)return e.error("length octect is too long");r=0;for(let t=0;t<i;t++){r<<=8;const t=e.readUInt8(n);if(e.isError(t))return t;r|=t}return r}e.exports=u,u.prototype.decode=function(e,t){return o.isDecoderBuffer(e)||(e=new o(e,t)),this.tree._decode(e,t)},r(c,s),c.prototype._peekTag=function(e,t,n){if(e.isEmpty())return!1;const r=e.save(),i=f(e,'Failed to peek tag: "'+t+'"');return e.isError(i)?i:(e.restore(r),i.tag===t||i.tagStr===t||i.tagStr+"of"===t||n)},c.prototype._decodeTag=function(e,t,n){const r=f(e,'Failed to decode tag of "'+t+'"');if(e.isError(r))return r;let i=l(e,r.primitive,'Failed to get length of "'+t+'"');if(e.isError(i))return i;if(!n&&r.tag!==t&&r.tagStr!==t&&r.tagStr+"of"!==t)return e.error('Failed to match tag: "'+t+'"');if(r.primitive||null!==i)return e.skip(i,'Failed to match body of: "'+t+'"');const o=e.save(),s=this._skipUntilEnd(e,'Failed to skip indefinite length body: "'+this.tag+'"');return e.isError(s)?s:(i=e.offset-o.offset,e.restore(o),e.skip(i,'Failed to match body of: "'+t+'"'))},c.prototype._skipUntilEnd=function(e,t){for(;;){const n=f(e,t);if(e.isError(n))return n;const r=l(e,n.primitive,t);if(e.isError(r))return r;let i;if(i=n.primitive||null!==r?e.skip(r):this._skipUntilEnd(e,t),e.isError(i))return i;if("end"===n.tagStr)break}},c.prototype._decodeList=function(e,t,n,r){const i=[];for(;!e.isEmpty();){const t=this._peekTag(e,"end");if(e.isError(t))return t;const o=n.decode(e,"der",r);if(e.isError(o)&&t)break;i.push(o)}return i},c.prototype._decodeStr=function(e,t){if("bitstr"===t){const t=e.readUInt8();return e.isError(t)?t:{unused:t,data:e.raw()}}if("bmpstr"===t){const t=e.raw();if(t.length%2==1)return e.error("Decoding of string type: bmpstr length mismatch");let n="";for(let e=0;e<t.length/2;e++)n+=String.fromCharCode(t.readUInt16BE(2*e));return n}if("numstr"===t){const t=e.raw().toString("ascii");return this._isNumstr(t)?t:e.error("Decoding of string type: numstr unsupported characters")}if("octstr"===t)return e.raw();if("objDesc"===t)return e.raw();if("printstr"===t){const t=e.raw().toString("ascii");return this._isPrintstr(t)?t:e.error("Decoding of string type: printstr unsupported characters")}return/str$/.test(t)?e.raw().toString():e.error("Decoding of string type: "+t+" unsupported")},c.prototype._decodeObjid=function(e,t,n){let r;const i=[];let o=0,s=0;for(;!e.isEmpty();)s=e.readUInt8(),o<<=7,o|=127&s,0==(128&s)&&(i.push(o),o=0);128&s&&i.push(o);const a=i[0]/40|0,u=i[0]%40;if(r=n?i:[a,u].concat(i.slice(1)),t){let e=t[r.join(" ")];void 0===e&&(e=t[r.join(".")]),void 0!==e&&(r=e)}return r},c.prototype._decodeTime=function(e,t){const n=e.raw().toString();let r,i,o,s,a,u;if("gentime"===t)r=0|n.slice(0,4),i=0|n.slice(4,6),o=0|n.slice(6,8),s=0|n.slice(8,10),a=0|n.slice(10,12),u=0|n.slice(12,14);else{if("utctime"!==t)return e.error("Decoding "+t+" time is not supported yet");r=0|n.slice(0,2),i=0|n.slice(2,4),o=0|n.slice(4,6),s=0|n.slice(6,8),a=0|n.slice(8,10),u=0|n.slice(10,12),r=r<70?2e3+r:1900+r}return Date.UTC(r,i-1,o,s,a,u,0)},c.prototype._decodeNull=function(){return null},c.prototype._decodeBool=function(e){const t=e.readUInt8();return e.isError(t)?t:0!==t},c.prototype._decodeInt=function(e,t){const n=e.raw();let r=new i(n);return t&&(r=t[r.toString(10)]||r),r},c.prototype._use=function(e,t){return"function"==typeof e&&(e=e(t)),e._getDecoder("der").tree}},function(e){e.exports=JSON.parse('{"1.3.132.0.10":"secp256k1","1.3.132.0.33":"p224","1.2.840.10045.3.1.1":"p192","1.2.840.10045.3.1.7":"p256","1.3.132.0.34":"p384","1.3.132.0.35":"p521"}')},function(e,t,n){var r=n(79),i=n(8).Buffer;function o(e){var t=i.allocUnsafe(4);return t.writeUInt32BE(e,0),t}e.exports=function(e,t){for(var n,s=i.alloc(0),a=0;s.length<t;)n=o(a++),s=i.concat([s,r("sha1").update(e).update(n).digest()]);return s.slice(0,t)}},function(e,t){e.exports=function(e,t){for(var n=e.length,r=-1;++r<n;)e[r]^=t[r];return e}},function(e,t,n){var r=n(29),i=n(8).Buffer;e.exports=function(e,t){return i.from(e.toRed(r.mont(t.modulus)).redPow(new r(t.publicExponent)).fromRed().toArray())}},function(e,t,n){"use strict";n.r(t),t.default=function(e,t){return t=t||{},new Promise((function(n,r){var i=new XMLHttpRequest,o=[],s=[],a={},u=function(){return{ok:2==(i.status/100|0),statusText:i.statusText,status:i.status,url:i.responseURL,text:function(){return Promise.resolve(i.responseText)},json:function(){return Promise.resolve(i.responseText).then(JSON.parse)},blob:function(){return Promise.resolve(new Blob([i.response]))},clone:u,headers:{keys:function(){return o},entries:function(){return s},get:function(e){return a[e.toLowerCase()]},has:function(e){return e.toLowerCase()in a}}}};for(var c in i.open(t.method||"get",e,!0),i.onload=function(){i.getAllResponseHeaders().replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm,(function(e,t,n){o.push(t=t.toLowerCase()),s.push([t,n]),a[t]=a[t]?a[t]+","+n:n})),n(u())},i.onerror=r,i.withCredentials="include"==t.credentials,t.headers)i.setRequestHeader(c,t.headers[c]);i.send(t.body||null)}))}},function(e,t){var n="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof window.msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto);if(n){var r=new Uint8Array(16);e.exports=function(){return n(r),r}}else{var i=new Array(16);e.exports=function(){for(var e,t=0;t<16;t++)0==(3&t)&&(e=4294967296*Math.random()),i[t]=e>>>((3&t)<<3)&255;return i}}},function(e,t){for(var n=[],r=0;r<256;++r)n[r]=(r+256).toString(16).substr(1);e.exports=function(e,t){var r=t||0,i=n;return[i[e[r++]],i[e[r++]],i[e[r++]],i[e[r++]],"-",i[e[r++]],i[e[r++]],"-",i[e[r++]],i[e[r++]],"-",i[e[r++]],i[e[r++]],"-",i[e[r++]],i[e[r++]],i[e[r++]],i[e[r++]],i[e[r++]],i[e[r++]]].join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Sha256=void 0;var r=n(217),i=n(218),o=n(249),s=n(134),a=function(){function e(e){e?(this.operation=function(e){return new Promise((function(t,n){var r=s.locateWindow().msCrypto.subtle.importKey("raw",u(e),i.SHA_256_HMAC_ALGO,!1,["sign"]);r.oncomplete=function(){r.result&&t(r.result),n("ImportKey completed without importing key.")},r.onerror=function(){n("ImportKey failed to import key.")}}))}(e).then((function(e){return s.locateWindow().msCrypto.subtle.sign(i.SHA_256_HMAC_ALGO,e)})),this.operation.catch((function(){}))):this.operation=Promise.resolve(s.locateWindow().msCrypto.subtle.digest("SHA-256"))}return e.prototype.update=function(e){var t=this;r.isEmptyData(e)||(this.operation=this.operation.then((function(n){return n.onerror=function(){t.operation=Promise.reject(new Error("Error encountered updating hash"))},n.process(u(e)),n})),this.operation.catch((function(){})))},e.prototype.digest=function(){return this.operation.then((function(e){return new Promise((function(t,n){e.onerror=function(){n("Error encountered finalizing hash")},e.oncomplete=function(){e.result&&t(new Uint8Array(e.result)),n("Error encountered finalizing hash")},e.finish()}))}))},e}();function u(e){return"string"==typeof e?o.fromUtf8(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength/Uint8Array.BYTES_PER_ELEMENT):new Uint8Array(e)}t.Sha256=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isEmptyData=void 0,t.isEmptyData=function(e){return"string"==typeof e?0===e.length:0===e.byteLength}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.EMPTY_DATA_SHA_256=t.SHA_256_HMAC_ALGO=t.SHA_256_HASH=void 0,t.SHA_256_HASH={name:"SHA-256"},t.SHA_256_HMAC_ALGO={name:"HMAC",hash:t.SHA_256_HASH},t.EMPTY_DATA_SHA_256=new Uint8Array([227,176,196,66,152,252,28,20,154,251,244,200,153,111,185,36,39,174,65,228,100,155,147,76,164,149,153,27,120,82,184,85])},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Sha256=void 0;var r=n(249),i=n(217),o=n(218),s=n(134),a=function(){function e(e){this.toHash=new Uint8Array(0),void 0!==e&&(this.key=new Promise((function(t,n){s.locateWindow().crypto.subtle.importKey("raw",u(e),o.SHA_256_HMAC_ALGO,!1,["sign"]).then(t,n)})),this.key.catch((function(){})))}return e.prototype.update=function(e){if(!i.isEmptyData(e)){var t=u(e),n=new Uint8Array(this.toHash.byteLength+t.byteLength);n.set(this.toHash,0),n.set(t,this.toHash.byteLength),this.toHash=n}},e.prototype.digest=function(){var e=this;return this.key?this.key.then((function(t){return s.locateWindow().crypto.subtle.sign(o.SHA_256_HMAC_ALGO,t,e.toHash).then((function(e){return new Uint8Array(e)}))})):i.isEmptyData(this.toHash)?Promise.resolve(o.EMPTY_DATA_SHA_256):Promise.resolve().then((function(){return s.locateWindow().crypto.subtle.digest(o.SHA_256_HASH,e.toHash)})).then((function(e){return Promise.resolve(new Uint8Array(e))}))},e}();function u(e){return"string"==typeof e?r.fromUtf8(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength/Uint8Array.BYTES_PER_ELEMENT):new Uint8Array(e)}t.Sha256=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.MAX_HASHABLE_LENGTH=t.INIT=t.KEY=t.DIGEST_LENGTH=t.BLOCK_SIZE=void 0,t.BLOCK_SIZE=64,t.DIGEST_LENGTH=32,t.KEY=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),t.INIT=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],t.MAX_HASHABLE_LENGTH=Math.pow(2,53)-1},function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));var r=new(n(44).a)("Parser"),i=function(e){var t,n={};if(e.aws_mobile_analytics_app_id){var i={AWSPinpoint:{appId:e.aws_mobile_analytics_app_id,region:e.aws_mobile_analytics_app_region}};n.Analytics=i}return(e.aws_cognito_identity_pool_id||e.aws_user_pools_id)&&(n.Auth={userPoolId:e.aws_user_pools_id,userPoolWebClientId:e.aws_user_pools_web_client_id,region:e.aws_cognito_region,identityPoolId:e.aws_cognito_identity_pool_id,identityPoolRegion:e.aws_cognito_region,mandatorySignIn:"enable"===e.aws_mandatory_sign_in}),t=e.aws_user_files_s3_bucket?{AWSS3:{bucket:e.aws_user_files_s3_bucket,region:e.aws_user_files_s3_bucket_region,dangerouslyConnectToHttpEndpointForTesting:e.aws_user_files_s3_dangerously_connect_to_http_endpoint_for_testing}}:e?e.Storage||e:{},n.Analytics=Object.assign({},n.Analytics,e.Analytics),n.Auth=Object.assign({},n.Auth,e.Auth),n.Storage=Object.assign({},t),r.debug("parse config",e,"to amplifyconfig",n),n},o=function(){function e(){}return e.parseMobilehubConfig=i,e}()},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.BLOCK_SIZE=64,t.DIGEST_LENGTH=32,t.KEY=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),t.INIT=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],t.MAX_HASHABLE_LENGTH=Math.pow(2,53)-1},function(e,t,n){(function(t){var n="object"==typeof t&&t&&t.Object===Object&&t;e.exports=n}).call(this,n(31))},function(e,t,n){var r=n(84),i=n(225);e.exports=function(e){if(!i(e))return!1;var t=r(e);return"[object Function]"==t||"[object GeneratorFunction]"==t||"[object AsyncFunction]"==t||"[object Proxy]"==t}},function(e,t){e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},function(e,t){var n=Function.prototype.toString;e.exports=function(e){if(null!=e){try{return n.call(e)}catch(e){}try{return e+""}catch(e){}}return""}},function(e,t){e.exports=function(e,t){return e===t||e!=e&&t!=t}},function(e,t,n){var r=n(229),i=n(420),o=Object.prototype.hasOwnProperty;e.exports=function(e){if(!r(e))return i(e);var t=[];for(var n in Object(e))o.call(e,n)&&"constructor"!=n&&t.push(n);return t}},function(e,t){var n=Object.prototype;e.exports=function(e){var t=e&&e.constructor;return e===("function"==typeof t&&t.prototype||n)}},function(e,t,n){var r=n(422),i=n(137),o=n(423),s=n(424),a=n(425),u=n(84),c=n(226),f=c(r),l=c(i),d=c(o),h=c(s),p=c(a),v=u;(r&&"[object DataView]"!=v(new r(new ArrayBuffer(1)))||i&&"[object Map]"!=v(new i)||o&&"[object Promise]"!=v(o.resolve())||s&&"[object Set]"!=v(new s)||a&&"[object WeakMap]"!=v(new a))&&(v=function(e){var t=u(e),n="[object Object]"==t?e.constructor:void 0,r=n?c(n):"";if(r)switch(r){case f:return"[object DataView]";case l:return"[object Map]";case d:return"[object Promise]";case h:return"[object Set]";case p:return"[object WeakMap]"}return t}),e.exports=v},function(e,t,n){var r=n(426),i=n(85),o=Object.prototype,s=o.hasOwnProperty,a=o.propertyIsEnumerable,u=r(function(){return arguments}())?r:function(e){return i(e)&&s.call(e,"callee")&&!a.call(e,"callee")};e.exports=u},function(e,t,n){var r=n(224),i=n(233);e.exports=function(e){return null!=e&&i(e.length)&&!r(e)}},function(e,t){e.exports=function(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=9007199254740991}},function(e,t,n){var r=n(439),i=n(442),o=n(443);e.exports=function(e,t,n,s,a,u){var c=1&n,f=e.length,l=t.length;if(f!=l&&!(c&&l>f))return!1;var d=u.get(e),h=u.get(t);if(d&&h)return d==t&&h==e;var p=-1,v=!0,g=2&n?new r:void 0;for(u.set(e,t),u.set(t,e);++p<f;){var m=e[p],b=t[p];if(s)var y=c?s(b,m,p,t,e,u):s(m,b,p,e,t,u);if(void 0!==y){if(y)continue;v=!1;break}if(g){if(!i(t,(function(e,t){if(!o(g,t)&&(m===e||a(m,e,n,s,u)))return g.push(t)}))){v=!1;break}}else if(m!==b&&!a(m,b,n,s,u)){v=!1;break}}return u.delete(e),u.delete(t),v}},function(e,t,n){"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r<n.length;r++)n[r]=arguments[r];return e.apply(t,n)}}},function(e,t,n){"use strict";var r=n(45);function i(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}e.exports=function(e,t,n){if(!t)return e;var o;if(n)o=n(t);else if(r.isURLSearchParams(t))o=t.toString();else{var s=[];r.forEach(t,(function(e,t){null!=e&&(r.isArray(e)?t+="[]":e=[e],r.forEach(e,(function(e){r.isDate(e)?e=e.toISOString():r.isObject(e)&&(e=JSON.stringify(e)),s.push(i(t)+"="+i(e))})))})),o=s.join("&")}if(o){var a=e.indexOf("#");-1!==a&&(e=e.slice(0,a)),e+=(-1===e.indexOf("?")?"?":"&")+o}return e}},function(e,t,n){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},function(e,t,n){"use strict";(function(t){var r=n(45),i=n(470),o={"Content-Type":"application/x-www-form-urlencoded"};function s(e,t){!r.isUndefined(e)&&r.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}var a,u={adapter:(("undefined"!=typeof XMLHttpRequest||void 0!==t&&"[object process]"===Object.prototype.toString.call(t))&&(a=n(239)),a),transformRequest:[function(e,t){return i(t,"Accept"),i(t,"Content-Type"),r.isFormData(e)||r.isArrayBuffer(e)||r.isBuffer(e)||r.isStream(e)||r.isFile(e)||r.isBlob(e)?e:r.isArrayBufferView(e)?e.buffer:r.isURLSearchParams(e)?(s(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):r.isObject(e)?(s(t,"application/json;charset=utf-8"),JSON.stringify(e)):e}],transformResponse:[function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(e){}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,validateStatus:function(e){return e>=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],(function(e){u.headers[e]={}})),r.forEach(["post","put","patch"],(function(e){u.headers[e]=r.merge(o)})),e.exports=u}).call(this,n(20))},function(e,t,n){"use strict";var r=n(45),i=n(471),o=n(473),s=n(236),a=n(474),u=n(477),c=n(478),f=n(240);e.exports=function(e){return new Promise((function(t,n){var l=e.data,d=e.headers;r.isFormData(l)&&delete d["Content-Type"];var h=new XMLHttpRequest;if(e.auth){var p=e.auth.username||"",v=e.auth.password?unescape(encodeURIComponent(e.auth.password)):"";d.Authorization="Basic "+btoa(p+":"+v)}var g=a(e.baseURL,e.url);if(h.open(e.method.toUpperCase(),s(g,e.params,e.paramsSerializer),!0),h.timeout=e.timeout,h.onreadystatechange=function(){if(h&&4===h.readyState&&(0!==h.status||h.responseURL&&0===h.responseURL.indexOf("file:"))){var r="getAllResponseHeaders"in h?u(h.getAllResponseHeaders()):null,o={data:e.responseType&&"text"!==e.responseType?h.response:h.responseText,status:h.status,statusText:h.statusText,headers:r,config:e,request:h};i(t,n,o),h=null}},h.onabort=function(){h&&(n(f("Request aborted",e,"ECONNABORTED",h)),h=null)},h.onerror=function(){n(f("Network Error",e,null,h)),h=null},h.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),n(f(t,e,"ECONNABORTED",h)),h=null},r.isStandardBrowserEnv()){var m=(e.withCredentials||c(g))&&e.xsrfCookieName?o.read(e.xsrfCookieName):void 0;m&&(d[e.xsrfHeaderName]=m)}if("setRequestHeader"in h&&r.forEach(d,(function(e,t){void 0===l&&"content-type"===t.toLowerCase()?delete d[t]:h.setRequestHeader(t,e)})),r.isUndefined(e.withCredentials)||(h.withCredentials=!!e.withCredentials),e.responseType)try{h.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&h.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&h.upload&&h.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then((function(e){h&&(h.abort(),n(e),h=null)})),l||(l=null),h.send(l)}))}},function(e,t,n){"use strict";var r=n(472);e.exports=function(e,t,n,i,o){var s=new Error(e);return r(s,t,n,i,o)}},function(e,t,n){"use strict";var r=n(45);e.exports=function(e,t){t=t||{};var n={},i=["url","method","data"],o=["headers","auth","proxy","params"],s=["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","timeoutMessage","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","decompress","maxContentLength","maxBodyLength","maxRedirects","transport","httpAgent","httpsAgent","cancelToken","socketPath","responseEncoding"],a=["validateStatus"];function u(e,t){return r.isPlainObject(e)&&r.isPlainObject(t)?r.merge(e,t):r.isPlainObject(t)?r.merge({},t):r.isArray(t)?t.slice():t}function c(i){r.isUndefined(t[i])?r.isUndefined(e[i])||(n[i]=u(void 0,e[i])):n[i]=u(e[i],t[i])}r.forEach(i,(function(e){r.isUndefined(t[e])||(n[e]=u(void 0,t[e]))})),r.forEach(o,c),r.forEach(s,(function(i){r.isUndefined(t[i])?r.isUndefined(e[i])||(n[i]=u(void 0,e[i])):n[i]=u(void 0,t[i])})),r.forEach(a,(function(r){r in t?n[r]=u(e[r],t[r]):r in e&&(n[r]=u(void 0,e[r]))}));var f=i.concat(o).concat(s).concat(a),l=Object.keys(e).concat(Object.keys(t)).filter((function(e){return-1===f.indexOf(e)}));return r.forEach(l,c),n}},function(e,t,n){"use strict";function r(e){this.message=e}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,e.exports=r},function(e,t){var n="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof window.msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto);if(n){var r=new Uint8Array(16);e.exports=function(){return n(r),r}}else{var i=new Array(16);e.exports=function(){for(var e,t=0;t<16;t++)0==(3&t)&&(e=4294967296*Math.random()),i[t]=e>>>((3&t)<<3)&255;return i}}},function(e,t){for(var n=[],r=0;r<256;++r)n[r]=(r+256).toString(16).substr(1);e.exports=function(e,t){var r=t||0,i=n;return[i[e[r++]],i[e[r++]],i[e[r++]],i[e[r++]],"-",i[e[r++]],i[e[r++]],"-",i[e[r++]],i[e[r++]],"-",i[e[r++]],i[e[r++]],"-",i[e[r++]],i[e[r++]],i[e[r++]],i[e[r++]],i[e[r++]],i[e[r++]]].join("")}},function(e,t,n){"use strict";n.d(t,"a",(function(){return r}));var r=function(){function e(){}return e.createPredicateBuilder=function(t){var n=t.name,r=new Set(Object.keys(t.fields)),i=new Proxy({},{get:function(t,i,o){var s=i;if(!r.has(s))throw new Error("Invalid field for model. field: "+s+", model: "+n);return function(t){return e.sortPredicateGroupsMap.get(o).push({field:s,sortDirection:t}),o}}});return e.sortPredicateGroupsMap.set(i,[]),i},e.isValidPredicate=function(t){return e.sortPredicateGroupsMap.has(t)},e.getPredicates=function(t,n){if(void 0===n&&(n=!0),n&&!e.isValidPredicate(t))throw new Error("The predicate is not valid");return e.sortPredicateGroupsMap.get(t)},e.createFromExisting=function(t,n){if(n&&t)return n(e.createPredicateBuilder(t))},e.sortPredicateGroupsMap=new WeakMap,e}()},function(e,t,n){(function(e){var n,r,i,o;function s(e){return(s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}o=function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==s(e)&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(r,i,function(t){return e[t]}.bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";function r(e){for(var n in e)t.hasOwnProperty(n)||(t[n]=e[n])}Object.defineProperty(t,"__esModule",{value:!0}),r(n(1)),r(n(2)),r(n(3)),r(n(4)),r(n(5)),r(n(6)),r(n(7)),r(n(8)),r(n(9)),r(n(10)),r(n(11)),r(n(12)),r(n(13))},function(e,t,n){e.exports={a:"Anchor__a___1_Iz8"}},function(e,t,n){e.exports={button:"Button__button___vS7Mv",signInButton:"Button__signInButton___3bUH-",googleSignInButton:"Button__googleSignInButton___1YiCu",signInButtonIcon:"Button__signInButtonIcon___ihN75",auth0SignInButton:"Button__auth0SignInButton___znnCj",facebookSignInButton:"Button__facebookSignInButton___34Txh",amazonSignInButton:"Button__amazonSignInButton___2EMtl",oAuthSignInButton:"Button__oAuthSignInButton___3UGOl",signInButtonContent:"Button__signInButtonContent___xqTXJ"}},function(e,t,n){e.exports={formContainer:"Form__formContainer___1GA3x",formSection:"Form__formSection___1PPvW",formField:"Form__formField___38Ikl",formRow:"Form__formRow___2mwRs"}},function(e,t,n){e.exports={hint:"Hint__hint___2XngB"}},function(e,t,n){e.exports={input:"Input__input___3e_bf",inputLabel:"Input__inputLabel___3VF0S",label:"Input__label___23sO8",radio:"Input__radio___2hllK"}},function(e,t,n){e.exports={navBar:"Nav__navBar___xtCFA",navRight:"Nav__navRight___1QG2J",nav:"Nav__nav___2Dx2Y",navItem:"Nav__navItem___1LtFQ"}},function(e,t,n){e.exports={photoPickerButton:"PhotoPicker__photoPickerButton___2XdVn",photoPlaceholder:"PhotoPicker__photoPlaceholder___2JXO4",photoPlaceholderIcon:"PhotoPicker__photoPlaceholderIcon___3Et71"}},function(e,t,n){e.exports={container:"Section__container___3YYTG",actionRow:"Section__actionRow___2LWSU",sectionHeader:"Section__sectionHeader___2djyg",sectionHeaderHint:"Section__sectionHeaderHint___3Wxdc",sectionBody:"Section__sectionBody___ihqqd",sectionHeaderContent:"Section__sectionHeaderContent___1UCqa",sectionFooter:"Section__sectionFooter___1T54C",sectionFooterPrimaryContent:"Section__sectionFooterPrimaryContent___2r9ZX",sectionFooterSecondaryContent:"Section__sectionFooterSecondaryContent___Nj41Q"}},function(e,t,n){e.exports={selectInput:"SelectInput__selectInput___3efO4"}},function(e,t,n){e.exports={strike:"Strike__strike___1XV1b",strikeContent:"Strike__strikeContent___10gLb"}},function(e,t,n){e.exports={toast:"Toast__toast___XXr3v",toastClose:"Toast__toastClose___18lU4"}},function(e,t,n){e.exports={totpQrcode:"Totp__totpQrcode___1crLx"}},function(e,t,n){e.exports={sumerianSceneContainer:"XR__sumerianSceneContainer___3nVMt",sumerianScene:"XR__sumerianScene___2Tt7-",loadingOverlay:"XR__loadingOverlay___IbqcI",loadingContainer:"XR__loadingContainer___2Itxb",loadingLogo:"XR__loadingLogo___Ub7xQ",loadingSceneName:"XR__loadingSceneName___3__ne",loadingBar:"XR__loadingBar___2vcke",loadingBarFill:"XR__loadingBarFill___3M-D9",sceneErrorText:"XR__sceneErrorText___2y0tp",sceneBar:"XR__sceneBar___2ShrP",sceneName:"XR__sceneName___1ApHr",sceneActions:"XR__sceneActions___7plGs",actionButton:"XR__actionButton___2poIM",tooltip:"XR__tooltip___UYyhn",actionIcon:"XR__actionIcon___2qnd2",autoShowTooltip:"XR__autoShowTooltip___V1QH7"}}])},"object"==s(t)&&"object"==s(e)?e.exports=o():(r=[],void 0===(i="function"==typeof(n=o)?n.apply(t,r):n)||(e.exports=i))}).call(this,n(57)(e))},function(e,t,n){"use strict";n.d(t,"a",(function(){return d}));var r=n(52),i=n(63),o=n(89),s=n(19),a=n(146),u=n(258),c=function(){return(c=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},f=[i.a,o.a],l=[r.a,i.a,u.a];function d(e){void 0===e&&(e={});var t=e.modules,n=void 0===t?l:t,r=e.req,i=s.a.configure(),o=new s.b,u=new a.a({req:r});return f.forEach((function(e){n.includes(e)||o.register(new e.constructor)})),n.forEach((function(e){o.register(new e.constructor)})),o.configure(c(c({},i),{storage:u})),o}},function(e,t,n){"use strict";n.d(t,"b",(function(){return We})),n.d(t,"a",(function(){return $e}));var r=n(91),i={Name:[],Document:["definitions"],OperationDefinition:["name","variableDefinitions","directives","selectionSet"],VariableDefinition:["variable","type","defaultValue","directives"],Variable:["name"],SelectionSet:["selections"],Field:["alias","name","arguments","directives","selectionSet"],Argument:["name","value"],FragmentSpread:["name","directives"],InlineFragment:["typeCondition","directives","selectionSet"],FragmentDefinition:["name","variableDefinitions","typeCondition","directives","selectionSet"],IntValue:[],FloatValue:[],StringValue:[],BooleanValue:[],NullValue:[],EnumValue:[],ListValue:["values"],ObjectValue:["fields"],ObjectField:["name","value"],Directive:["name","arguments"],NamedType:["name"],ListType:["type"],NonNullType:["type"],SchemaDefinition:["directives","operationTypes"],OperationTypeDefinition:["type"],ScalarTypeDefinition:["description","name","directives"],ObjectTypeDefinition:["description","name","interfaces","directives","fields"],FieldDefinition:["description","name","arguments","type","directives"],InputValueDefinition:["description","name","type","defaultValue","directives"],InterfaceTypeDefinition:["description","name","directives","fields"],UnionTypeDefinition:["description","name","directives","types"],EnumTypeDefinition:["description","name","directives","values"],EnumValueDefinition:["description","name","directives"],InputObjectTypeDefinition:["description","name","directives","fields"],DirectiveDefinition:["description","name","arguments","locations"],SchemaExtension:["directives","operationTypes"],ScalarTypeExtension:["name","directives"],ObjectTypeExtension:["name","interfaces","directives","fields"],InterfaceTypeExtension:["name","directives","fields"],UnionTypeExtension:["name","directives","types"],EnumTypeExtension:["name","directives","values"],InputObjectTypeExtension:["name","directives","fields"]},o={};function s(e){return Boolean(e&&"string"==typeof e.kind)}function a(e,t,n){var r=e[t];if(r){if(!n&&"function"==typeof r)return r;var i=n?r.leave:r.enter;if("function"==typeof i)return i}else{var o=n?e.leave:e.enter;if(o){if("function"==typeof o)return o;var s=o[t];if("function"==typeof s)return s}}}function u(e){return function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:i,r=void 0,u=Array.isArray(e),c=[e],f=-1,l=[],d=void 0,h=void 0,p=void 0,v=[],g=[],m=e;do{var b=++f===c.length,y=b&&0!==l.length;if(b){if(h=0===g.length?void 0:v[v.length-1],d=p,p=g.pop(),y){if(u)d=d.slice();else{var w={};for(var _ in d)d.hasOwnProperty(_)&&(w[_]=d[_]);d=w}for(var S=0,E=0;E<l.length;E++){var M=l[E][0],A=l[E][1];u&&(M-=S),u&&null===A?(d.splice(M,1),S++):d[M]=A}}f=r.index,c=r.keys,l=r.edits,u=r.inArray,r=r.prev}else{if(h=p?u?f:c[f]:void 0,null==(d=p?p[h]:m))continue;p&&v.push(h)}var I=void 0;if(!Array.isArray(d)){if(!s(d))throw new Error("Invalid AST Node: "+JSON.stringify(d));var k=a(t,d.kind,b);if(k){if((I=k.call(t,d,h,p,v,g))===o)break;if(!1===I){if(!b){v.pop();continue}}else if(void 0!==I&&(l.push([h,I]),!b)){if(!s(I)){v.pop();continue}d=I}}}void 0===I&&y&&l.push([h,d]),b?v.pop():(r={inArray:u,index:f,keys:c,edits:l,prev:r},c=(u=Array.isArray(d))?d:n[d.kind]||[],f=-1,l=[],p&&g.push(p),p=d)}while(void 0!==r);return 0!==l.length&&(m=l[l.length-1][1]),m}(e,{leave:c})}var c={Name:function(e){return e.value},Variable:function(e){return"$"+e.name},Document:function(e){return l(e.definitions,"\n\n")+"\n"},OperationDefinition:function(e){var t=e.operation,n=e.name,r=h("(",l(e.variableDefinitions,", "),")"),i=l(e.directives," "),o=e.selectionSet;return n||i||r||"query"!==t?l([t,l([n,r]),i,o]," "):o},VariableDefinition:function(e){var t=e.variable,n=e.type,r=e.defaultValue,i=e.directives;return t+": "+n+h(" = ",r)+h(" ",l(i," "))},SelectionSet:function(e){return d(e.selections)},Field:function(e){var t=e.alias,n=e.name,r=e.arguments,i=e.directives,o=e.selectionSet;return l([h("",t,": ")+n+h("(",l(r,", "),")"),l(i," "),o]," ")},Argument:function(e){return e.name+": "+e.value},FragmentSpread:function(e){return"..."+e.name+h(" ",l(e.directives," "))},InlineFragment:function(e){var t=e.typeCondition,n=e.directives,r=e.selectionSet;return l(["...",h("on ",t),l(n," "),r]," ")},FragmentDefinition:function(e){var t=e.name,n=e.typeCondition,r=e.variableDefinitions,i=e.directives,o=e.selectionSet;return"fragment ".concat(t).concat(h("(",l(r,", "),")")," ")+"on ".concat(n," ").concat(h("",l(i," ")," "))+o},IntValue:function(e){return e.value},FloatValue:function(e){return e.value},StringValue:function(e,t){var n=e.value;return e.block?function(e,t){var n=e.replace(/"""/g,'\\"""');return" "!==e[0]&&"\t"!==e[0]||-1!==e.indexOf("\n")?'"""\n'.concat(t?n:p(n),'\n"""'):'"""'.concat(n.replace(/"$/,'"\n'),'"""')}(n,"description"===t):JSON.stringify(n)},BooleanValue:function(e){return e.value?"true":"false"},NullValue:function(){return"null"},EnumValue:function(e){return e.value},ListValue:function(e){return"["+l(e.values,", ")+"]"},ObjectValue:function(e){return"{"+l(e.fields,", ")+"}"},ObjectField:function(e){return e.name+": "+e.value},Directive:function(e){return"@"+e.name+h("(",l(e.arguments,", "),")")},NamedType:function(e){return e.name},ListType:function(e){return"["+e.type+"]"},NonNullType:function(e){return e.type+"!"},SchemaDefinition:function(e){var t=e.directives,n=e.operationTypes;return l(["schema",l(t," "),d(n)]," ")},OperationTypeDefinition:function(e){return e.operation+": "+e.type},ScalarTypeDefinition:f((function(e){return l(["scalar",e.name,l(e.directives," ")]," ")})),ObjectTypeDefinition:f((function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return l(["type",t,h("implements ",l(n," & ")),l(r," "),d(i)]," ")})),FieldDefinition:f((function(e){var t=e.name,n=e.arguments,r=e.type,i=e.directives;return t+(n.every((function(e){return-1===e.indexOf("\n")}))?h("(",l(n,", "),")"):h("(\n",p(l(n,"\n")),"\n)"))+": "+r+h(" ",l(i," "))})),InputValueDefinition:f((function(e){var t=e.name,n=e.type,r=e.defaultValue,i=e.directives;return l([t+": "+n,h("= ",r),l(i," ")]," ")})),InterfaceTypeDefinition:f((function(e){var t=e.name,n=e.directives,r=e.fields;return l(["interface",t,l(n," "),d(r)]," ")})),UnionTypeDefinition:f((function(e){var t=e.name,n=e.directives,r=e.types;return l(["union",t,l(n," "),r&&0!==r.length?"= "+l(r," | "):""]," ")})),EnumTypeDefinition:f((function(e){var t=e.name,n=e.directives,r=e.values;return l(["enum",t,l(n," "),d(r)]," ")})),EnumValueDefinition:f((function(e){return l([e.name,l(e.directives," ")]," ")})),InputObjectTypeDefinition:f((function(e){var t=e.name,n=e.directives,r=e.fields;return l(["input",t,l(n," "),d(r)]," ")})),DirectiveDefinition:f((function(e){var t=e.name,n=e.arguments,r=e.locations;return"directive @"+t+(n.every((function(e){return-1===e.indexOf("\n")}))?h("(",l(n,", "),")"):h("(\n",p(l(n,"\n")),"\n)"))+" on "+l(r," | ")})),SchemaExtension:function(e){var t=e.directives,n=e.operationTypes;return l(["extend schema",l(t," "),d(n)]," ")},ScalarTypeExtension:function(e){return l(["extend scalar",e.name,l(e.directives," ")]," ")},ObjectTypeExtension:function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return l(["extend type",t,h("implements ",l(n," & ")),l(r," "),d(i)]," ")},InterfaceTypeExtension:function(e){var t=e.name,n=e.directives,r=e.fields;return l(["extend interface",t,l(n," "),d(r)]," ")},UnionTypeExtension:function(e){var t=e.name,n=e.directives,r=e.types;return l(["extend union",t,l(n," "),r&&0!==r.length?"= "+l(r," | "):""]," ")},EnumTypeExtension:function(e){var t=e.name,n=e.directives,r=e.values;return l(["extend enum",t,l(n," "),d(r)]," ")},InputObjectTypeExtension:function(e){var t=e.name,n=e.directives,r=e.fields;return l(["extend input",t,l(n," "),d(r)]," ")}};function f(e){return function(t){return l([t.description,e(t)],"\n")}}function l(e,t){return e?e.filter((function(e){return e})).join(t||""):""}function d(e){return e&&0!==e.length?"{\n"+p(l(e,"\n"))+"\n}":""}function h(e,t,n){return t?e+t+(n||""):""}function p(e){return e&&" "+e.replace(/\n/g,"\n ")}function v(e){return(v="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function g(e){return e&&"object"===v(e)?"function"==typeof e.inspect?e.inspect():Array.isArray(e)?"["+e.map(g).join(", ")+"]":"{"+Object.keys(e).map((function(t){return"".concat(t,": ").concat(g(e[t]))})).join(", ")+"}":"string"==typeof e?'"'+e+'"':"function"==typeof e?"[function ".concat(e.name,"]"):String(e)}function m(e,t){if(!e)throw new Error(t)}function b(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var y,w=function(e,t,n){b(this,"body",void 0),b(this,"name",void 0),b(this,"locationOffset",void 0),this.body=e,this.name=t||"GraphQL request",this.locationOffset=n||{line:1,column:1},this.locationOffset.line>0||m(0,"line in locationOffset is 1-indexed and must be positive"),this.locationOffset.column>0||m(0,"column in locationOffset is 1-indexed and must be positive")};function _(e,t,n){return new r.a("Syntax Error: ".concat(n),void 0,e,[t])}function S(e){for(var t=e.split(/\r\n|[\n\r]/g),n=null,r=1;r<t.length;r++){var i=t[r],o=E(i);if(o<i.length&&(null===n||o<n)&&0===(n=o))break}if(n)for(var s=1;s<t.length;s++)t[s]=t[s].slice(n);for(;t.length>0&&M(t[0]);)t.shift();for(;t.length>0&&M(t[t.length-1]);)t.pop();return t.join("\n")}function E(e){for(var t=0;t<e.length&&(" "===e[t]||"\t"===e[t]);)t++;return t}function M(e){return E(e)===e.length}function A(e,t){var n=new P(O.SOF,0,0,0,0,null);return{source:e,options:t,lastToken:n,token:n,line:1,lineStart:0,advance:I,lookahead:k}}function I(){return this.lastToken=this.token,this.token=this.lookahead()}function k(){var e=this.token;if(e.kind!==O.EOF)do{e=e.next||(e.next=R(this,e))}while(e.kind===O.COMMENT);return e}y=w,"function"==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(y.prototype,Symbol.toStringTag,{get:function(){return this.constructor.name}});var O=Object.freeze({SOF:"<SOF>",EOF:"<EOF>",BANG:"!",DOLLAR:"$",AMP:"&",PAREN_L:"(",PAREN_R:")",SPREAD:"...",COLON:":",EQUALS:"=",AT:"@",BRACKET_L:"[",BRACKET_R:"]",BRACE_L:"{",PIPE:"|",BRACE_R:"}",NAME:"Name",INT:"Int",FLOAT:"Float",STRING:"String",BLOCK_STRING:"BlockString",COMMENT:"Comment"});function x(e){var t=e.value;return t?"".concat(e.kind,' "').concat(t,'"'):e.kind}var C=String.prototype.charCodeAt,T=String.prototype.slice;function P(e,t,n,r,i,o,s){this.kind=e,this.start=t,this.end=n,this.line=r,this.column=i,this.value=s,this.prev=o,this.next=null}function N(e){return isNaN(e)?O.EOF:e<127?JSON.stringify(String.fromCharCode(e)):'"\\u'.concat(("00"+e.toString(16).toUpperCase()).slice(-4),'"')}function R(e,t){var n=e.source,r=n.body,i=r.length,o=function(e,t,n){var r=e.length,i=t;for(;i<r;){var o=C.call(e,i);if(9===o||32===o||44===o||65279===o)++i;else if(10===o)++i,++n.line,n.lineStart=i;else{if(13!==o)break;10===C.call(e,i+1)?i+=2:++i,++n.line,n.lineStart=i}}return i}(r,t.end,e),s=e.line,a=1+o-e.lineStart;if(o>=i)return new P(O.EOF,i,i,s,a,t);var u=C.call(r,o);switch(u){case 33:return new P(O.BANG,o,o+1,s,a,t);case 35:return function(e,t,n,r,i){var o,s=e.body,a=t;do{o=C.call(s,++a)}while(null!==o&&(o>31||9===o));return new P(O.COMMENT,t,a,n,r,i,T.call(s,t+1,a))}(n,o,s,a,t);case 36:return new P(O.DOLLAR,o,o+1,s,a,t);case 38:return new P(O.AMP,o,o+1,s,a,t);case 40:return new P(O.PAREN_L,o,o+1,s,a,t);case 41:return new P(O.PAREN_R,o,o+1,s,a,t);case 46:if(46===C.call(r,o+1)&&46===C.call(r,o+2))return new P(O.SPREAD,o,o+3,s,a,t);break;case 58:return new P(O.COLON,o,o+1,s,a,t);case 61:return new P(O.EQUALS,o,o+1,s,a,t);case 64:return new P(O.AT,o,o+1,s,a,t);case 91:return new P(O.BRACKET_L,o,o+1,s,a,t);case 93:return new P(O.BRACKET_R,o,o+1,s,a,t);case 123:return new P(O.BRACE_L,o,o+1,s,a,t);case 124:return new P(O.PIPE,o,o+1,s,a,t);case 125:return new P(O.BRACE_R,o,o+1,s,a,t);case 65:case 66:case 67:case 68:case 69:case 70:case 71:case 72:case 73:case 74:case 75:case 76:case 77:case 78:case 79:case 80:case 81:case 82:case 83:case 84:case 85:case 86:case 87:case 88:case 89:case 90:case 95:case 97:case 98:case 99:case 100:case 101:case 102:case 103:case 104:case 105:case 106:case 107:case 108:case 109:case 110:case 111:case 112:case 113:case 114:case 115:case 116:case 117:case 118:case 119:case 120:case 121:case 122:return function(e,t,n,r,i){var o=e.body,s=o.length,a=t+1,u=0;for(;a!==s&&null!==(u=C.call(o,a))&&(95===u||u>=48&&u<=57||u>=65&&u<=90||u>=97&&u<=122);)++a;return new P(O.NAME,t,a,n,r,i,T.call(o,t,a))}(n,o,s,a,t);case 45:case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return function(e,t,n,r,i,o){var s=e.body,a=n,u=t,c=!1;45===a&&(a=C.call(s,++u));if(48===a){if((a=C.call(s,++u))>=48&&a<=57)throw _(e,u,"Invalid number, unexpected digit after 0: ".concat(N(a),"."))}else u=L(e,u,a),a=C.call(s,u);46===a&&(c=!0,a=C.call(s,++u),u=L(e,u,a),a=C.call(s,u));69!==a&&101!==a||(c=!0,43!==(a=C.call(s,++u))&&45!==a||(a=C.call(s,++u)),u=L(e,u,a));return new P(c?O.FLOAT:O.INT,t,u,r,i,o,T.call(s,t,u))}(n,o,u,s,a,t);case 34:return 34===C.call(r,o+1)&&34===C.call(r,o+2)?function(e,t,n,r,i){var o=e.body,s=t+3,a=s,u=0,c="";for(;s<o.length&&null!==(u=C.call(o,s));){if(34===u&&34===C.call(o,s+1)&&34===C.call(o,s+2))return c+=T.call(o,a,s),new P(O.BLOCK_STRING,t,s+3,n,r,i,S(c));if(u<32&&9!==u&&10!==u&&13!==u)throw _(e,s,"Invalid character within String: ".concat(N(u),"."));92===u&&34===C.call(o,s+1)&&34===C.call(o,s+2)&&34===C.call(o,s+3)?(c+=T.call(o,a,s)+'"""',a=s+=4):++s}throw _(e,s,"Unterminated string.")}(n,o,s,a,t):function(e,t,n,r,i){var o=e.body,s=t+1,a=s,u=0,c="";for(;s<o.length&&null!==(u=C.call(o,s))&&10!==u&&13!==u;){if(34===u)return c+=T.call(o,a,s),new P(O.STRING,t,s+1,n,r,i,c);if(u<32&&9!==u)throw _(e,s,"Invalid character within String: ".concat(N(u),"."));if(++s,92===u){switch(c+=T.call(o,a,s-1),u=C.call(o,s)){case 34:c+='"';break;case 47:c+="/";break;case 92:c+="\\";break;case 98:c+="\b";break;case 102:c+="\f";break;case 110:c+="\n";break;case 114:c+="\r";break;case 116:c+="\t";break;case 117:var f=(l=C.call(o,s+1),d=C.call(o,s+2),h=C.call(o,s+3),p=C.call(o,s+4),j(l)<<12|j(d)<<8|j(h)<<4|j(p));if(f<0)throw _(e,s,"Invalid character escape sequence: "+"\\u".concat(o.slice(s+1,s+5),"."));c+=String.fromCharCode(f),s+=4;break;default:throw _(e,s,"Invalid character escape sequence: \\".concat(String.fromCharCode(u),"."))}++s,a=s}}var l,d,h,p;throw _(e,s,"Unterminated string.")}(n,o,s,a,t)}throw _(n,o,function(e){if(e<32&&9!==e&&10!==e&&13!==e)return"Cannot contain the invalid character ".concat(N(e),".");if(39===e)return"Unexpected single quote character ('), did you mean to use a double quote (\")?";return"Cannot parse the unexpected character ".concat(N(e),".")}(u))}function L(e,t,n){var r=e.body,i=t,o=n;if(o>=48&&o<=57){do{o=C.call(r,++i)}while(o>=48&&o<=57);return i}throw _(e,i,"Invalid number, expected digit but got: ".concat(N(o),"."))}function j(e){return e>=48&&e<=57?e-48:e>=65&&e<=70?e-55:e>=97&&e<=102?e-87:-1}P.prototype.toJSON=P.prototype.inspect=function(){return{kind:this.kind,value:this.value,line:this.line,column:this.column}};var D=Object.freeze({NAME:"Name",DOCUMENT:"Document",OPERATION_DEFINITION:"OperationDefinition",VARIABLE_DEFINITION:"VariableDefinition",SELECTION_SET:"SelectionSet",FIELD:"Field",ARGUMENT:"Argument",FRAGMENT_SPREAD:"FragmentSpread",INLINE_FRAGMENT:"InlineFragment",FRAGMENT_DEFINITION:"FragmentDefinition",VARIABLE:"Variable",INT:"IntValue",FLOAT:"FloatValue",STRING:"StringValue",BOOLEAN:"BooleanValue",NULL:"NullValue",ENUM:"EnumValue",LIST:"ListValue",OBJECT:"ObjectValue",OBJECT_FIELD:"ObjectField",DIRECTIVE:"Directive",NAMED_TYPE:"NamedType",LIST_TYPE:"ListType",NON_NULL_TYPE:"NonNullType",SCHEMA_DEFINITION:"SchemaDefinition",OPERATION_TYPE_DEFINITION:"OperationTypeDefinition",SCALAR_TYPE_DEFINITION:"ScalarTypeDefinition",OBJECT_TYPE_DEFINITION:"ObjectTypeDefinition",FIELD_DEFINITION:"FieldDefinition",INPUT_VALUE_DEFINITION:"InputValueDefinition",INTERFACE_TYPE_DEFINITION:"InterfaceTypeDefinition",UNION_TYPE_DEFINITION:"UnionTypeDefinition",ENUM_TYPE_DEFINITION:"EnumTypeDefinition",ENUM_VALUE_DEFINITION:"EnumValueDefinition",INPUT_OBJECT_TYPE_DEFINITION:"InputObjectTypeDefinition",DIRECTIVE_DEFINITION:"DirectiveDefinition",SCHEMA_EXTENSION:"SchemaExtension",SCALAR_TYPE_EXTENSION:"ScalarTypeExtension",OBJECT_TYPE_EXTENSION:"ObjectTypeExtension",INTERFACE_TYPE_EXTENSION:"InterfaceTypeExtension",UNION_TYPE_EXTENSION:"UnionTypeExtension",ENUM_TYPE_EXTENSION:"EnumTypeExtension",INPUT_OBJECT_TYPE_EXTENSION:"InputObjectTypeExtension"}),U=Object.freeze({QUERY:"QUERY",MUTATION:"MUTATION",SUBSCRIPTION:"SUBSCRIPTION",FIELD:"FIELD",FRAGMENT_DEFINITION:"FRAGMENT_DEFINITION",FRAGMENT_SPREAD:"FRAGMENT_SPREAD",INLINE_FRAGMENT:"INLINE_FRAGMENT",VARIABLE_DEFINITION:"VARIABLE_DEFINITION",SCHEMA:"SCHEMA",SCALAR:"SCALAR",OBJECT:"OBJECT",FIELD_DEFINITION:"FIELD_DEFINITION",ARGUMENT_DEFINITION:"ARGUMENT_DEFINITION",INTERFACE:"INTERFACE",UNION:"UNION",ENUM:"ENUM",ENUM_VALUE:"ENUM_VALUE",INPUT_OBJECT:"INPUT_OBJECT",INPUT_FIELD_DEFINITION:"INPUT_FIELD_DEFINITION"});function B(e,t){var n="string"==typeof e?new w(e):e;if(!(n instanceof w))throw new TypeError("Must provide Source. Received: ".concat(g(n)));return function(e){var t=e.token;return{kind:D.DOCUMENT,definitions:Te(e,O.SOF,z,O.EOF),loc:Ee(e,t)}}(A(n,t||{}))}function F(e){var t=ke(e,O.NAME);return{kind:D.NAME,value:t.value,loc:Ee(e,t)}}function z(e){if(Ae(e,O.NAME))switch(e.token.value){case"query":case"mutation":case"subscription":case"fragment":return q(e);case"schema":case"scalar":case"type":case"interface":case"union":case"enum":case"input":case"directive":return ce(e);case"extend":return function(e){var t=e.lookahead();if(t.kind===O.NAME)switch(t.value){case"schema":return function(e){var t=e.token;Oe(e,"extend"),Oe(e,"schema");var n=oe(e,!0),r=Ae(e,O.BRACE_L)?Te(e,O.BRACE_L,de,O.BRACE_R):[];if(0===n.length&&0===r.length)throw xe(e);return{kind:D.SCHEMA_EXTENSION,directives:n,operationTypes:r,loc:Ee(e,t)}}(e);case"scalar":return function(e){var t=e.token;Oe(e,"extend"),Oe(e,"scalar");var n=F(e),r=oe(e,!0);if(0===r.length)throw xe(e);return{kind:D.SCALAR_TYPE_EXTENSION,name:n,directives:r,loc:Ee(e,t)}}(e);case"type":return function(e){var t=e.token;Oe(e,"extend"),Oe(e,"type");var n=F(e),r=he(e),i=oe(e,!0),o=pe(e);if(0===r.length&&0===i.length&&0===o.length)throw xe(e);return{kind:D.OBJECT_TYPE_EXTENSION,name:n,interfaces:r,directives:i,fields:o,loc:Ee(e,t)}}(e);case"interface":return function(e){var t=e.token;Oe(e,"extend"),Oe(e,"interface");var n=F(e),r=oe(e,!0),i=pe(e);if(0===r.length&&0===i.length)throw xe(e);return{kind:D.INTERFACE_TYPE_EXTENSION,name:n,directives:r,fields:i,loc:Ee(e,t)}}(e);case"union":return function(e){var t=e.token;Oe(e,"extend"),Oe(e,"union");var n=F(e),r=oe(e,!0),i=be(e);if(0===r.length&&0===i.length)throw xe(e);return{kind:D.UNION_TYPE_EXTENSION,name:n,directives:r,types:i,loc:Ee(e,t)}}(e);case"enum":return function(e){var t=e.token;Oe(e,"extend"),Oe(e,"enum");var n=F(e),r=oe(e,!0),i=ye(e);if(0===r.length&&0===i.length)throw xe(e);return{kind:D.ENUM_TYPE_EXTENSION,name:n,directives:r,values:i,loc:Ee(e,t)}}(e);case"input":return function(e){var t=e.token;Oe(e,"extend"),Oe(e,"input");var n=F(e),r=oe(e,!0),i=_e(e);if(0===r.length&&0===i.length)throw xe(e);return{kind:D.INPUT_OBJECT_TYPE_EXTENSION,name:n,directives:r,fields:i,loc:Ee(e,t)}}(e)}throw xe(e,t)}(e)}else{if(Ae(e,O.BRACE_L))return q(e);if(fe(e))return ce(e)}throw xe(e)}function q(e){if(Ae(e,O.NAME))switch(e.token.value){case"query":case"mutation":case"subscription":return K(e);case"fragment":return function(e){var t=e.token;if(Oe(e,"fragment"),e.options.experimentalFragmentVariables)return{kind:D.FRAGMENT_DEFINITION,name:Q(e),variableDefinitions:V(e),typeCondition:(Oe(e,"on"),ue(e)),directives:oe(e,!1),selectionSet:$(e),loc:Ee(e,t)};return{kind:D.FRAGMENT_DEFINITION,name:Q(e),typeCondition:(Oe(e,"on"),ue(e)),directives:oe(e,!1),selectionSet:$(e),loc:Ee(e,t)}}(e)}else if(Ae(e,O.BRACE_L))return K(e);throw xe(e)}function K(e){var t=e.token;if(Ae(e,O.BRACE_L))return{kind:D.OPERATION_DEFINITION,operation:"query",name:void 0,variableDefinitions:[],directives:[],selectionSet:$(e),loc:Ee(e,t)};var n,r=H(e);return Ae(e,O.NAME)&&(n=F(e)),{kind:D.OPERATION_DEFINITION,operation:r,name:n,variableDefinitions:V(e),directives:oe(e,!1),selectionSet:$(e),loc:Ee(e,t)}}function H(e){var t=ke(e,O.NAME);switch(t.value){case"query":return"query";case"mutation":return"mutation";case"subscription":return"subscription"}throw xe(e,t)}function V(e){return Ae(e,O.PAREN_L)?Te(e,O.PAREN_L,G,O.PAREN_R):[]}function G(e){var t=e.token;return e.options.experimentalVariableDefinitionDirectives?{kind:D.VARIABLE_DEFINITION,variable:W(e),type:(ke(e,O.COLON),ae(e)),defaultValue:Ie(e,O.EQUALS)?ee(e,!0):void 0,directives:oe(e,!0),loc:Ee(e,t)}:{kind:D.VARIABLE_DEFINITION,variable:W(e),type:(ke(e,O.COLON),ae(e)),defaultValue:Ie(e,O.EQUALS)?ee(e,!0):void 0,loc:Ee(e,t)}}function W(e){var t=e.token;return ke(e,O.DOLLAR),{kind:D.VARIABLE,name:F(e),loc:Ee(e,t)}}function $(e){var t=e.token;return{kind:D.SELECTION_SET,selections:Te(e,O.BRACE_L,Y,O.BRACE_R),loc:Ee(e,t)}}function Y(e){return Ae(e,O.SPREAD)?function(e){var t,n=e.token;if(ke(e,O.SPREAD),Ae(e,O.NAME)&&"on"!==e.token.value)return{kind:D.FRAGMENT_SPREAD,name:Q(e),directives:oe(e,!1),loc:Ee(e,n)};"on"===e.token.value&&(e.advance(),t=ue(e));return{kind:D.INLINE_FRAGMENT,typeCondition:t,directives:oe(e,!1),selectionSet:$(e),loc:Ee(e,n)}}(e):function(e){var t,n,r=e.token,i=F(e);Ie(e,O.COLON)?(t=i,n=F(e)):n=i;return{kind:D.FIELD,alias:t,name:n,arguments:J(e,!1),directives:oe(e,!1),selectionSet:Ae(e,O.BRACE_L)?$(e):void 0,loc:Ee(e,r)}}(e)}function J(e,t){var n=t?X:Z;return Ae(e,O.PAREN_L)?Te(e,O.PAREN_L,n,O.PAREN_R):[]}function Z(e){var t=e.token;return{kind:D.ARGUMENT,name:F(e),value:(ke(e,O.COLON),ee(e,!1)),loc:Ee(e,t)}}function X(e){var t=e.token;return{kind:D.ARGUMENT,name:F(e),value:(ke(e,O.COLON),ne(e)),loc:Ee(e,t)}}function Q(e){if("on"===e.token.value)throw xe(e);return F(e)}function ee(e,t){var n=e.token;switch(n.kind){case O.BRACKET_L:return function(e,t){var n=e.token,r=t?ne:re;return{kind:D.LIST,values:Ce(e,O.BRACKET_L,r,O.BRACKET_R),loc:Ee(e,n)}}(e,t);case O.BRACE_L:return function(e,t){var n=e.token;ke(e,O.BRACE_L);var r=[];for(;!Ie(e,O.BRACE_R);)r.push(ie(e,t));return{kind:D.OBJECT,fields:r,loc:Ee(e,n)}}(e,t);case O.INT:return e.advance(),{kind:D.INT,value:n.value,loc:Ee(e,n)};case O.FLOAT:return e.advance(),{kind:D.FLOAT,value:n.value,loc:Ee(e,n)};case O.STRING:case O.BLOCK_STRING:return te(e);case O.NAME:return"true"===n.value||"false"===n.value?(e.advance(),{kind:D.BOOLEAN,value:"true"===n.value,loc:Ee(e,n)}):"null"===n.value?(e.advance(),{kind:D.NULL,loc:Ee(e,n)}):(e.advance(),{kind:D.ENUM,value:n.value,loc:Ee(e,n)});case O.DOLLAR:if(!t)return W(e)}throw xe(e)}function te(e){var t=e.token;return e.advance(),{kind:D.STRING,value:t.value,block:t.kind===O.BLOCK_STRING,loc:Ee(e,t)}}function ne(e){return ee(e,!0)}function re(e){return ee(e,!1)}function ie(e,t){var n=e.token;return{kind:D.OBJECT_FIELD,name:F(e),value:(ke(e,O.COLON),ee(e,t)),loc:Ee(e,n)}}function oe(e,t){for(var n=[];Ae(e,O.AT);)n.push(se(e,t));return n}function se(e,t){var n=e.token;return ke(e,O.AT),{kind:D.DIRECTIVE,name:F(e),arguments:J(e,t),loc:Ee(e,n)}}function ae(e){var t,n=e.token;return Ie(e,O.BRACKET_L)?(t=ae(e),ke(e,O.BRACKET_R),t={kind:D.LIST_TYPE,type:t,loc:Ee(e,n)}):t=ue(e),Ie(e,O.BANG)?{kind:D.NON_NULL_TYPE,type:t,loc:Ee(e,n)}:t}function ue(e){var t=e.token;return{kind:D.NAMED_TYPE,name:F(e),loc:Ee(e,t)}}function ce(e){var t=fe(e)?e.lookahead():e.token;if(t.kind===O.NAME)switch(t.value){case"schema":return function(e){var t=e.token;Oe(e,"schema");var n=oe(e,!0),r=Te(e,O.BRACE_L,de,O.BRACE_R);return{kind:D.SCHEMA_DEFINITION,directives:n,operationTypes:r,loc:Ee(e,t)}}(e);case"scalar":return function(e){var t=e.token,n=le(e);Oe(e,"scalar");var r=F(e),i=oe(e,!0);return{kind:D.SCALAR_TYPE_DEFINITION,description:n,name:r,directives:i,loc:Ee(e,t)}}(e);case"type":return function(e){var t=e.token,n=le(e);Oe(e,"type");var r=F(e),i=he(e),o=oe(e,!0),s=pe(e);return{kind:D.OBJECT_TYPE_DEFINITION,description:n,name:r,interfaces:i,directives:o,fields:s,loc:Ee(e,t)}}(e);case"interface":return function(e){var t=e.token,n=le(e);Oe(e,"interface");var r=F(e),i=oe(e,!0),o=pe(e);return{kind:D.INTERFACE_TYPE_DEFINITION,description:n,name:r,directives:i,fields:o,loc:Ee(e,t)}}(e);case"union":return function(e){var t=e.token,n=le(e);Oe(e,"union");var r=F(e),i=oe(e,!0),o=be(e);return{kind:D.UNION_TYPE_DEFINITION,description:n,name:r,directives:i,types:o,loc:Ee(e,t)}}(e);case"enum":return function(e){var t=e.token,n=le(e);Oe(e,"enum");var r=F(e),i=oe(e,!0),o=ye(e);return{kind:D.ENUM_TYPE_DEFINITION,description:n,name:r,directives:i,values:o,loc:Ee(e,t)}}(e);case"input":return function(e){var t=e.token,n=le(e);Oe(e,"input");var r=F(e),i=oe(e,!0),o=_e(e);return{kind:D.INPUT_OBJECT_TYPE_DEFINITION,description:n,name:r,directives:i,fields:o,loc:Ee(e,t)}}(e);case"directive":return function(e){var t=e.token,n=le(e);Oe(e,"directive"),ke(e,O.AT);var r=F(e),i=ge(e);Oe(e,"on");var o=function(e){Ie(e,O.PIPE);var t=[];do{t.push(Se(e))}while(Ie(e,O.PIPE));return t}(e);return{kind:D.DIRECTIVE_DEFINITION,description:n,name:r,arguments:i,locations:o,loc:Ee(e,t)}}(e)}throw xe(e,t)}function fe(e){return Ae(e,O.STRING)||Ae(e,O.BLOCK_STRING)}function le(e){if(fe(e))return te(e)}function de(e){var t=e.token,n=H(e);ke(e,O.COLON);var r=ue(e);return{kind:D.OPERATION_TYPE_DEFINITION,operation:n,type:r,loc:Ee(e,t)}}function he(e){var t=[];if("implements"===e.token.value){e.advance(),Ie(e,O.AMP);do{t.push(ue(e))}while(Ie(e,O.AMP)||e.options.allowLegacySDLImplementsInterfaces&&Ae(e,O.NAME))}return t}function pe(e){return e.options.allowLegacySDLEmptyFields&&Ae(e,O.BRACE_L)&&e.lookahead().kind===O.BRACE_R?(e.advance(),e.advance(),[]):Ae(e,O.BRACE_L)?Te(e,O.BRACE_L,ve,O.BRACE_R):[]}function ve(e){var t=e.token,n=le(e),r=F(e),i=ge(e);ke(e,O.COLON);var o=ae(e),s=oe(e,!0);return{kind:D.FIELD_DEFINITION,description:n,name:r,arguments:i,type:o,directives:s,loc:Ee(e,t)}}function ge(e){return Ae(e,O.PAREN_L)?Te(e,O.PAREN_L,me,O.PAREN_R):[]}function me(e){var t=e.token,n=le(e),r=F(e);ke(e,O.COLON);var i,o=ae(e);Ie(e,O.EQUALS)&&(i=ne(e));var s=oe(e,!0);return{kind:D.INPUT_VALUE_DEFINITION,description:n,name:r,type:o,defaultValue:i,directives:s,loc:Ee(e,t)}}function be(e){var t=[];if(Ie(e,O.EQUALS)){Ie(e,O.PIPE);do{t.push(ue(e))}while(Ie(e,O.PIPE))}return t}function ye(e){return Ae(e,O.BRACE_L)?Te(e,O.BRACE_L,we,O.BRACE_R):[]}function we(e){var t=e.token,n=le(e),r=F(e),i=oe(e,!0);return{kind:D.ENUM_VALUE_DEFINITION,description:n,name:r,directives:i,loc:Ee(e,t)}}function _e(e){return Ae(e,O.BRACE_L)?Te(e,O.BRACE_L,me,O.BRACE_R):[]}function Se(e){var t=e.token,n=F(e);if(U.hasOwnProperty(n.value))return n;throw xe(e,t)}function Ee(e,t){if(!e.options.noLocation)return new Me(t,e.lastToken,e.source)}function Me(e,t,n){this.start=e.start,this.end=t.end,this.startToken=e,this.endToken=t,this.source=n}function Ae(e,t){return e.token.kind===t}function Ie(e,t){var n=e.token.kind===t;return n&&e.advance(),n}function ke(e,t){var n=e.token;if(n.kind===t)return e.advance(),n;throw _(e.source,n.start,"Expected ".concat(t,", found ").concat(x(n)))}function Oe(e,t){var n=e.token;if(n.kind===O.NAME&&n.value===t)return e.advance(),n;throw _(e.source,n.start,'Expected "'.concat(t,'", found ').concat(x(n)))}function xe(e,t){var n=t||e.token;return _(e.source,n.start,"Unexpected ".concat(x(n)))}function Ce(e,t,n,r){ke(e,t);for(var i=[];!Ie(e,r);)i.push(n(e));return i}function Te(e,t,n,r){ke(e,t);for(var i=[n(e)];!Ie(e,r);)i.push(n(e));return i}Me.prototype.toJSON=Me.prototype.inspect=function(){return{start:this.start,end:this.end}};var Pe=n(44),Ne=n(89),Re=n(5),Le=n(103),je=n(19),De=n(34),Ue=n(42),Be=n(26),Fe=n(254),ze=function(){return(ze=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},qe=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},Ke=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},He=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]])}return n},Ve=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},Ge=new Pe.a("GraphQLAPI"),We=function(e,t){return void 0===t&&(t={}),{query:e,variables:t}},$e=function(){function e(e){this._api=null,this.Auth=Ue.a,this.Cache=Be.a,this.Credentials=Ne.a,this._options=e,Ge.debug("API Options",this._options)}return e.prototype.getModuleName=function(){return"GraphQLAPI"},e.prototype.configure=function(e){var t=e||{},n=t.API,r=void 0===n?{}:n,i=He(t,["API"]),o=ze(ze({},i),r);return Ge.debug("configure GraphQL API",{opt:o}),o.aws_project_region&&(o=Object.assign({},o,{region:o.aws_project_region,header:{}})),void 0!==o.graphql_headers&&"function"!=typeof o.graphql_headers&&(Ge.warn("graphql_headers should be a function"),o.graphql_headers=void 0),this._options=Object.assign({},this._options,o),this.createInstance(),this._options},e.prototype.createInstance=function(){return Ge.debug("create Rest instance"),this._options?(this._api=new Fe.a(this._options),this._api.Credentials=this.Credentials,!0):Promise.reject("API not configured")},e.prototype._headerBasedAuth=function(e){return qe(this,void 0,void 0,(function(){var t,n,r,i,o,s,a,u;return Ke(this,(function(c){switch(c.label){case 0:switch(t=this._options,n=t.aws_appsync_authenticationType,r=t.aws_appsync_apiKey,i={},e||n||"AWS_IAM"){case"API_KEY":return[3,1];case"AWS_IAM":return[3,2];case"OPENID_CONNECT":return[3,4];case"AMAZON_COGNITO_USER_POOLS":return[3,9]}return[3,11];case 1:if(!r)throw new Error("No api-key configured");return i={Authorization:null,"X-Api-Key":r},[3,12];case 2:return[4,this._ensureCredentials()];case 3:if(!c.sent())throw new Error("No credentials");return[3,12];case 4:return o=void 0,[4,Be.a.getItem("federatedInfo")];case 5:return(s=c.sent())?(o=s.token,[3,8]):[3,6];case 6:return[4,Ue.a.currentAuthenticatedUser()];case 7:(a=c.sent())&&(o=a.token),c.label=8;case 8:if(!o)throw new Error("No federated jwt");return i={Authorization:o},[3,12];case 9:return[4,this.Auth.currentSession()];case 10:return u=c.sent(),i={Authorization:u.getAccessToken().getJwtToken()},[3,12];case 11:return i={Authorization:null},[3,12];case 12:return[2,i]}}))}))},e.prototype.getGraphqlOperationType=function(e){var t=B(e);return Ve(t.definitions,1)[0].operation},e.prototype.graphql=function(e,t){var n=e.query,r=e.variables,i=void 0===r?{}:r,o=e.authMode,s=B("string"==typeof n?n:u(n)),a=Ve(s.definitions.filter((function(e){return"OperationDefinition"===e.kind})),1)[0],c=(void 0===a?{}:a).operation;switch(c){case"query":case"mutation":var f=this._api.getCancellableToken(),l={cancellableToken:f},d=this._graphql({query:s,variables:i,authMode:o},t,l);return this._api.updateRequestToBeCancellable(d,f),d;case"subscription":return this._graphqlSubscribe({query:s,variables:i,authMode:o},t)}throw new Error("invalid operation type: "+c)},e.prototype._graphql=function(e,t,n){var i=e.query,o=e.variables,s=e.authMode;return void 0===t&&(t={}),void 0===n&&(n={}),qe(this,void 0,void 0,(function(){var e,a,c,f,l,d,h,p,v,g,m,b,y,w,_,S,E,M,A,I,k;return Ke(this,(function(O){switch(O.label){case 0:return this._api?[3,2]:[4,this.createInstance()];case 1:O.sent(),O.label=2;case 2:return e=this._options,a=e.aws_appsync_region,c=e.aws_appsync_graphqlEndpoint,f=e.graphql_headers,l=void 0===f?function(){return{}}:f,d=e.graphql_endpoint,h=e.graphql_endpoint_iam_region,v=[{}],(g=!d)?[4,this._headerBasedAuth(s)]:[3,4];case 3:g=O.sent(),O.label=4;case 4:return m=[ze.apply(void 0,v.concat([g]))],(b=d)?h?[4,this._headerBasedAuth(s)]:[3,6]:[3,8];case 5:return y=O.sent(),[3,7];case 6:y={Authorization:null},O.label=7;case 7:b=y,O.label=8;case 8:return w=[ze.apply(void 0,m.concat([b]))],[4,l({query:i,variables:o})];case 9:if(p=ze.apply(void 0,[ze.apply(void 0,[ze.apply(void 0,w.concat([O.sent()])),t]),!d&&(k={},k["x-amz-user-agent"]=Re.a.userAgent,k)]),_={query:u(i),variables:o},S=Object.assign({headers:p,body:_,signerServiceInfo:{service:d?"execute-api":"appsync",region:d?h:a}},n),!(E=d||c))throw{data:{},errors:[new r.a("No graphql endpoint provided.")]};O.label=10;case 10:return O.trys.push([10,12,,13]),[4,this._api.post(E,S)];case 11:return M=O.sent(),[3,13];case 12:if(A=O.sent(),this._api.isCancel(A))throw A;return M={data:{},errors:[new r.a(A.message)]},[3,13];case 13:if((I=M.errors)&&I.length)throw M;return[2,M]}}))}))},e.prototype.isCancel=function(e){return this._api.isCancel(e)},e.prototype.cancel=function(e,t){return this._api.cancel(e,t)},e.prototype._graphqlSubscribe=function(e,t){var n=e.query,r=e.variables,i=e.authMode;void 0===t&&(t={});var o=this._options,s=o.aws_appsync_region,a=o.aws_appsync_graphqlEndpoint,c=o.aws_appsync_authenticationType,f=o.aws_appsync_apiKey,l=o.graphql_headers,d=void 0===l?function(){return{}}:l,h=i||c||"AWS_IAM";if(De.b&&"function"==typeof De.b.subscribe)return De.b.subscribe("",{provider:Le.b,appSyncGraphqlEndpoint:a,authenticationType:h,apiKey:f,query:u(n),region:s,variables:r,graphql_headers:d,additionalHeaders:t});throw Ge.debug("No pubsub module applied for subscription"),new Error("No pubsub module applied for subscription")},e.prototype._ensureCredentials=function(){var e=this;return this.Credentials.get().then((function(t){if(!t)return!1;var n=e.Credentials.shear(t);return Ge.debug("set credentials for api",n),!0})).catch((function(e){return Ge.warn("ensure credentials error",e),!1}))},e}(),Ye=new $e(null);je.a.register(Ye)},function(e,t,n){"use strict";n.r(t),n.d(t,"fromUtf8",(function(){return r})),n.d(t,"toUtf8",(function(){return i}));var r=function(e){return"function"==typeof TextEncoder?function(e){return(new TextEncoder).encode(e)}(e):function(e){for(var t=[],n=0,r=e.length;n<r;n++){var i=e.charCodeAt(n);if(i<128)t.push(i);else if(i<2048)t.push(i>>6|192,63&i|128);else if(n+1<e.length&&55296==(64512&i)&&56320==(64512&e.charCodeAt(n+1))){var o=65536+((1023&i)<<10)+(1023&e.charCodeAt(++n));t.push(o>>18|240,o>>12&63|128,o>>6&63|128,63&o|128)}else t.push(i>>12|224,i>>6&63|128,63&i|128)}return Uint8Array.from(t)}(e)},i=function(e){return"function"==typeof TextDecoder?function(e){return new TextDecoder("utf-8").decode(e)}(e):function(e){for(var t="",n=0,r=e.length;n<r;n++){var i=e[n];if(i<128)t+=String.fromCharCode(i);else if(192<=i&&i<224){var o=e[++n];t+=String.fromCharCode((31&i)<<6|63&o)}else if(240<=i&&i<365){var s="%"+[i,e[++n],e[++n],e[++n]].map((function(e){return e.toString(16)})).join("%");t+=decodeURIComponent(s)}else t+=String.fromCharCode((15&i)<<12|(63&e[++n])<<6|63&e[++n])}return t}(e)}},function(e,t,n){"use strict";(function(e){var r;if(n.d(t,"a",(function(){return i})),"undefined"!=typeof window&&window.crypto&&(r=window.crypto),!r&&"undefined"!=typeof window&&window.msCrypto&&(r=window.msCrypto),!r&&void 0!==e&&e.crypto&&(r=e.crypto),!r)try{r=n(272)}catch(e){}function i(){if(r){if("function"==typeof r.getRandomValues)try{return r.getRandomValues(new Uint32Array(1))[0]}catch(e){}if("function"==typeof r.randomBytes)try{return r.randomBytes(4).readInt32LE()}catch(e){}}throw new Error("Native crypto module could not be used to get secure random number.")}}).call(this,n(31))},function(e,t,n){"use strict";n.d(t,"a",(function(){return r}));var r=function(e){return"function"==typeof ArrayBuffer&&e instanceof ArrayBuffer||"[object ArrayBuffer]"===Object.prototype.toString.call(e)}},function(e,t,n){var r=n(387);e.exports=function(e,t,n){var i=null==e?void 0:r(e,t);return void 0===i?n:i}},function(e,t,n){"use strict";const r=n(459),i=n(102),o=n(102),s=n(54).buildOptions,a=n(461);t.parse=function(e,t,n){if(n){!0===n&&(n={});const t=a.validate(e,n);if(!0!==t)throw Error(t.err.msg)}t=s(t,o.defaultOptions,o.props);const u=i.getTraversalObj(e,t);return r.convertToJson(u,t)},t.convertTonimn=n(462).convert2nimn,t.getTraversalObj=i.getTraversalObj,t.convertToJson=r.convertToJson,t.convertToJsonString=n(463).convertToJsonString,t.validate=a.validate,t.j2xParser=n(464),t.parseToNimn=function(e,n,r){return t.convertTonimn(t.getTraversalObj(e,r),n,r)}},function(e,t,n){"use strict";n.d(t,"a",(function(){return g}));var r=n(44),i=n(89),o=n(50),s=n(77),a=n(104),u=n(64),c=n.n(u),f=n(16),l=function(){return(l=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},d=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},h=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},p=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]])}return n},v=new r.a("RestClient"),g=function(){function e(e){this._region="us-east-1",this._service="execute-api",this._custom_header=void 0,this._cancelTokenMap=null,this.Credentials=i.a,this._options=e,v.debug("API Options",this._options),null==this._cancelTokenMap&&(this._cancelTokenMap=new WeakMap)}return e.prototype.ajax=function(e,t,n){return d(this,void 0,void 0,(function(){var r,i,a,u,c,d,g,m,b,y,w,_,S,E,M=this;return h(this,(function(h){switch(h.label){case 0:return v.debug(t,e),a="us-east-1",u="execute-api",c=void 0,"string"==typeof e?(r=this._parseUrl(e),i=e):(i=e.endpoint,c=e.custom_header,a=e.region,u=e.service,r=this._parseUrl(e.endpoint)),d={method:t,url:i,host:r.host,path:r.path,headers:{},data:null,responseType:"json",timeout:0,cancelToken:null},g={},o.a.isReactNative&&(m=o.a.userAgent||"aws-amplify/0.1.x",g={"User-Agent":m}),b=Object.assign({},n),y=b.response,b.body&&("function"==typeof FormData&&b.body instanceof FormData?(g["Content-Type"]="multipart/form-data",d.data=b.body):(g["Content-Type"]="application/json; charset=UTF-8",d.data=JSON.stringify(b.body))),b.responseType&&(d.responseType=b.responseType),b.withCredentials&&(d.withCredentials=b.withCredentials),b.timeout&&(d.timeout=b.timeout),b.cancellableToken&&(d.cancelToken=b.cancellableToken.token),d.signerServiceInfo=b.signerServiceInfo,"function"!=typeof c?[3,2]:[4,c()];case 1:return _=h.sent(),[3,3];case 2:_=void 0,h.label=3;case 3:return w=_,d.headers=l(l(l({},g),w),b.headers),S=Object(f.parse)(i,!0,!0),S.search,E=p(S,["search"]),d.url=Object(f.format)(l(l({},E),{query:l(l({},E.query),b.queryStringParameters||{})})),void 0!==d.headers.Authorization?(d.headers=Object.keys(d.headers).reduce((function(e,t){return d.headers[t]&&(e[t]=d.headers[t]),e}),{}),[2,this._request(d,y)]):[2,this.Credentials.get().then((function(r){return M._signed(l({},d),r,y,{region:a,service:u}).catch((function(r){if(s.a.isClockSkewError(r)){var i=r.response.headers,o=i&&(i.date||i.Date),a=new Date(o),u=s.a.getDateFromHeaderString(d.headers["x-amz-date"]);if(s.a.isClockSkewed(u,a))return s.a.setClockOffset(a.getTime()-u.getTime()),M.ajax(e,t,n)}throw r}))}),(function(e){return v.debug("No credentials available, the request will be unsigned"),M._request(d,y)}))]}}))}))},e.prototype.get=function(e,t){return this.ajax(e,"GET",t)},e.prototype.put=function(e,t){return this.ajax(e,"PUT",t)},e.prototype.patch=function(e,t){return this.ajax(e,"PATCH",t)},e.prototype.post=function(e,t){return this.ajax(e,"POST",t)},e.prototype.del=function(e,t){return this.ajax(e,"DELETE",t)},e.prototype.head=function(e,t){return this.ajax(e,"HEAD",t)},e.prototype.cancel=function(e,t){var n=this._cancelTokenMap.get(e);return n&&n.cancel(t),!0},e.prototype.isCancel=function(e){return c.a.isCancel(e)},e.prototype.getCancellableToken=function(){return c.a.CancelToken.source()},e.prototype.updateRequestToBeCancellable=function(e,t){this._cancelTokenMap.set(e,t)},e.prototype.endpoint=function(e){var t=this,n=this._options.endpoints,r="";return Array.isArray(n)?(n.forEach((function(n){n.name===e&&(r=n.endpoint,"string"==typeof n.region?t._region=n.region:"string"==typeof t._options.region&&(t._region=t._options.region),"string"==typeof n.service?t._service=n.service||"execute-api":t._service="execute-api","function"==typeof n.custom_header?t._custom_header=n.custom_header:t._custom_header=void 0)})),r):r},e.prototype._signed=function(e,t,n,r){var i=r.service,o=r.region,s=e.signerServiceInfo,u=p(e,["signerServiceInfo"]),f=o||this._region||this._options.region,l=i||this._service||this._options.service,d={secret_key:t.secretAccessKey,access_key:t.accessKeyId,session_token:t.sessionToken},h={region:f,service:l},g=Object.assign(h,s),m=a.a.sign(u,d,g);return m.data&&(m.body=m.data),v.debug("Signed Request: ",m),delete m.headers.host,c()(m).then((function(e){return n?e:e.data})).catch((function(e){throw v.debug(e),e}))},e.prototype._request=function(e,t){return void 0===t&&(t=!1),c()(e).then((function(e){return t?e:e.data})).catch((function(e){throw v.debug(e),e}))},e.prototype._parseUrl=function(e){var t=e.split("/");return{host:t[2],path:"/"+t.slice(3).join("/")}},e}()},function(e,t,n){e.exports=n(482).Observable},function(e,t,n){(function(t){var n;n=function(){return function(e){var t,n=e.localStorage||(t={},{setItem:function(e,n){t[e]=n},getItem:function(e){return t[e]},removeItem:function(e){delete t[e]}}),r=1,i=2,o=3,s=4,a=5,u=6,c=7,f=8,l=9,d=10,h=11,p=12,v=13,g=14,m=function(e,t){for(var n in e)if(e.hasOwnProperty(n)){if(!t.hasOwnProperty(n)){var r="Unknown property, "+n+". Valid properties are:";for(var i in t)t.hasOwnProperty(i)&&(r=r+" "+i);throw new Error(r)}if(typeof e[n]!==t[n])throw new Error(_(y.INVALID_TYPE,[typeof e[n],n]))}},b=function(e,t){return function(){return e.apply(t,arguments)}},y={OK:{code:0,text:"AMQJSC0000I OK."},CONNECT_TIMEOUT:{code:1,text:"AMQJSC0001E Connect timed out."},SUBSCRIBE_TIMEOUT:{code:2,text:"AMQJS0002E Subscribe timed out."},UNSUBSCRIBE_TIMEOUT:{code:3,text:"AMQJS0003E Unsubscribe timed out."},PING_TIMEOUT:{code:4,text:"AMQJS0004E Ping timed out."},INTERNAL_ERROR:{code:5,text:"AMQJS0005E Internal error. Error Message: {0}, Stack trace: {1}"},CONNACK_RETURNCODE:{code:6,text:"AMQJS0006E Bad Connack return code:{0} {1}."},SOCKET_ERROR:{code:7,text:"AMQJS0007E Socket error:{0}."},SOCKET_CLOSE:{code:8,text:"AMQJS0008I Socket closed."},MALFORMED_UTF:{code:9,text:"AMQJS0009E Malformed UTF data:{0} {1} {2}."},UNSUPPORTED:{code:10,text:"AMQJS0010E {0} is not supported by this browser."},INVALID_STATE:{code:11,text:"AMQJS0011E Invalid state {0}."},INVALID_TYPE:{code:12,text:"AMQJS0012E Invalid type {0} for {1}."},INVALID_ARGUMENT:{code:13,text:"AMQJS0013E Invalid argument {0} for {1}."},UNSUPPORTED_OPERATION:{code:14,text:"AMQJS0014E Unsupported operation."},INVALID_STORED_DATA:{code:15,text:"AMQJS0015E Invalid data in local storage key={0} value={1}."},INVALID_MQTT_MESSAGE_TYPE:{code:16,text:"AMQJS0016E Invalid MQTT message type {0}."},MALFORMED_UNICODE:{code:17,text:"AMQJS0017E Malformed Unicode string:{0} {1}."},BUFFER_FULL:{code:18,text:"AMQJS0018E Message buffer is full, maximum buffer size: {0}."}},w={0:"Connection Accepted",1:"Connection Refused: unacceptable protocol version",2:"Connection Refused: identifier rejected",3:"Connection Refused: server unavailable",4:"Connection Refused: bad user name or password",5:"Connection Refused: not authorized"},_=function(e,t){var n=e.text;if(t)for(var r,i,o=0;o<t.length;o++)if(r="{"+o+"}",(i=n.indexOf(r))>0){var s=n.substring(0,i),a=n.substring(i+r.length);n=s+t[o]+a}return n},S=[0,6,77,81,73,115,100,112,3],E=[0,4,77,81,84,84,4],M=function(e,t){for(var n in this.type=e,t)t.hasOwnProperty(n)&&(this[n]=t[n])};function A(e,t){var n,r=t,f=e[t],d=f>>4,p=f&=15;t+=1;var v=0,g=1;do{if(t==e.length)return[null,r];v+=(127&(n=e[t++]))*g,g*=128}while(0!=(128&n));var m=t+v;if(m>e.length)return[null,r];var b=new M(d);switch(d){case i:1&e[t++]&&(b.sessionPresent=!0),b.returnCode=e[t++];break;case o:var y=p>>1&3,w=O(e,t),_=T(e,t+=2,w);t+=w,y>0&&(b.messageIdentifier=O(e,t),t+=2);var S=new L(e.subarray(t,m));1==(1&p)&&(S.retained=!0),8==(8&p)&&(S.duplicate=!0),S.qos=y,S.destinationName=_,b.payloadMessage=S;break;case s:case a:case u:case c:case h:b.messageIdentifier=O(e,t);break;case l:b.messageIdentifier=O(e,t),t+=2,b.returnCode=e.subarray(t,m)}return[b,m]}function I(e,t,n){return t[n++]=e>>8,t[n++]=e%256,n}function k(e,t,n,r){return C(e,n,r=I(t,n,r)),r+t}function O(e,t){return 256*e[t]+e[t+1]}function x(e){for(var t=0,n=0;n<e.length;n++){var r=e.charCodeAt(n);r>2047?(55296<=r&&r<=56319&&(n++,t++),t+=3):r>127?t+=2:t++}return t}function C(e,t,n){for(var r=n,i=0;i<e.length;i++){var o=e.charCodeAt(i);if(55296<=o&&o<=56319){var s=e.charCodeAt(++i);if(isNaN(s))throw new Error(_(y.MALFORMED_UNICODE,[o,s]));o=s-56320+(o-55296<<10)+65536}o<=127?t[r++]=o:o<=2047?(t[r++]=o>>6&31|192,t[r++]=63&o|128):o<=65535?(t[r++]=o>>12&15|224,t[r++]=o>>6&63|128,t[r++]=63&o|128):(t[r++]=o>>18&7|240,t[r++]=o>>12&63|128,t[r++]=o>>6&63|128,t[r++]=63&o|128)}return t}function T(e,t,n){for(var r,i="",o=t;o<t+n;){var s=e[o++];if(s<128)r=s;else{var a=e[o++]-128;if(a<0)throw new Error(_(y.MALFORMED_UTF,[s.toString(16),a.toString(16),""]));if(s<224)r=64*(s-192)+a;else{var u=e[o++]-128;if(u<0)throw new Error(_(y.MALFORMED_UTF,[s.toString(16),a.toString(16),u.toString(16)]));if(s<240)r=4096*(s-224)+64*a+u;else{var c=e[o++]-128;if(c<0)throw new Error(_(y.MALFORMED_UTF,[s.toString(16),a.toString(16),u.toString(16),c.toString(16)]));if(!(s<248))throw new Error(_(y.MALFORMED_UTF,[s.toString(16),a.toString(16),u.toString(16),c.toString(16)]));r=262144*(s-240)+4096*a+64*u+c}}}r>65535&&(r-=65536,i+=String.fromCharCode(55296+(r>>10)),r=56320+(1023&r)),i+=String.fromCharCode(r)}return i}M.prototype.encode=function(){var e,t=(15&this.type)<<4,n=0,i=[],s=0;switch(void 0!==this.messageIdentifier&&(n+=2),this.type){case r:switch(this.mqttVersion){case 3:n+=S.length+3;break;case 4:n+=E.length+3}n+=x(this.clientId)+2,void 0!==this.willMessage&&(n+=x(this.willMessage.destinationName)+2,(e=this.willMessage.payloadBytes)instanceof Uint8Array||(e=new Uint8Array(c)),n+=e.byteLength+2),void 0!==this.userName&&(n+=x(this.userName)+2),void 0!==this.password&&(n+=x(this.password)+2);break;case f:t|=2;for(var a=0;a<this.topics.length;a++)i[a]=x(this.topics[a]),n+=i[a]+2;n+=this.requestedQos.length;break;case d:for(t|=2,a=0;a<this.topics.length;a++)i[a]=x(this.topics[a]),n+=i[a]+2;break;case u:t|=2;break;case o:this.payloadMessage.duplicate&&(t|=8),t=t|=this.payloadMessage.qos<<1,this.payloadMessage.retained&&(t|=1),n+=(s=x(this.payloadMessage.destinationName))+2;var c=this.payloadMessage.payloadBytes;n+=c.byteLength,c instanceof ArrayBuffer?c=new Uint8Array(c):c instanceof Uint8Array||(c=new Uint8Array(c.buffer))}var l=function(e){var t=new Array(1),n=0;do{var r=e%128;(e>>=7)>0&&(r|=128),t[n++]=r}while(e>0&&n<4);return t}(n),h=l.length+1,p=new ArrayBuffer(n+h),v=new Uint8Array(p);if(v[0]=t,v.set(l,1),this.type==o)h=k(this.payloadMessage.destinationName,s,v,h);else if(this.type==r){switch(this.mqttVersion){case 3:v.set(S,h),h+=S.length;break;case 4:v.set(E,h),h+=E.length}var g=0;this.cleanSession&&(g=2),void 0!==this.willMessage&&(g|=4,g|=this.willMessage.qos<<3,this.willMessage.retained&&(g|=32)),void 0!==this.userName&&(g|=128),void 0!==this.password&&(g|=64),v[h++]=g,h=I(this.keepAliveInterval,v,h)}switch(void 0!==this.messageIdentifier&&(h=I(this.messageIdentifier,v,h)),this.type){case r:h=k(this.clientId,x(this.clientId),v,h),void 0!==this.willMessage&&(h=k(this.willMessage.destinationName,x(this.willMessage.destinationName),v,h),h=I(e.byteLength,v,h),v.set(e,h),h+=e.byteLength),void 0!==this.userName&&(h=k(this.userName,x(this.userName),v,h)),void 0!==this.password&&(h=k(this.password,x(this.password),v,h));break;case o:v.set(c,h);break;case f:for(a=0;a<this.topics.length;a++)h=k(this.topics[a],i[a],v,h),v[h++]=this.requestedQos[a];break;case d:for(a=0;a<this.topics.length;a++)h=k(this.topics[a],i[a],v,h)}return p};var P=function(e,t){this._client=e,this._keepAliveInterval=1e3*t,this.isReset=!1;var n=new M(p).encode(),r=function(e){return function(){return i.apply(e)}},i=function(){this.isReset?(this.isReset=!1,this._client._trace("Pinger.doPing","send PINGREQ"),this._client.socket.send(n),this.timeout=setTimeout(r(this),this._keepAliveInterval)):(this._client._trace("Pinger.doPing","Timed out"),this._client._disconnected(y.PING_TIMEOUT.code,_(y.PING_TIMEOUT)))};this.reset=function(){this.isReset=!0,clearTimeout(this.timeout),this._keepAliveInterval>0&&(this.timeout=setTimeout(r(this),this._keepAliveInterval))},this.cancel=function(){clearTimeout(this.timeout)}},N=function(e,t,n,r){t||(t=30),this.timeout=setTimeout(function(e,t,n){return function(){return e.apply(t,n)}}(n,e,r),1e3*t),this.cancel=function(){clearTimeout(this.timeout)}},R=function(t,r,i,o,s){if(!("WebSocket"in e)||null===e.WebSocket)throw new Error(_(y.UNSUPPORTED,["WebSocket"]));if(!("ArrayBuffer"in e)||null===e.ArrayBuffer)throw new Error(_(y.UNSUPPORTED,["ArrayBuffer"]));for(var a in this._trace("Paho.Client",t,r,i,o,s),this.host=r,this.port=i,this.path=o,this.uri=t,this.clientId=s,this._wsuri=null,this._localKey=r+":"+i+("/mqtt"!=o?":"+o:"")+":"+s+":",this._msg_queue=[],this._buffered_msg_queue=[],this._sentMessages={},this._receivedMessages={},this._notify_msg_sent={},this._message_identifier=1,this._sequence=0,n)0!==a.indexOf("Sent:"+this._localKey)&&0!==a.indexOf("Received:"+this._localKey)||this.restore(a)};R.prototype.host=null,R.prototype.port=null,R.prototype.path=null,R.prototype.uri=null,R.prototype.clientId=null,R.prototype.socket=null,R.prototype.connected=!1,R.prototype.maxMessageIdentifier=65536,R.prototype.connectOptions=null,R.prototype.hostIndex=null,R.prototype.onConnected=null,R.prototype.onConnectionLost=null,R.prototype.onMessageDelivered=null,R.prototype.onMessageArrived=null,R.prototype.traceFunction=null,R.prototype._msg_queue=null,R.prototype._buffered_msg_queue=null,R.prototype._connectTimeout=null,R.prototype.sendPinger=null,R.prototype.receivePinger=null,R.prototype._reconnectInterval=1,R.prototype._reconnecting=!1,R.prototype._reconnectTimeout=null,R.prototype.disconnectedPublishing=!1,R.prototype.disconnectedBufferSize=5e3,R.prototype.receiveBuffer=null,R.prototype._traceBuffer=null,R.prototype._MAX_TRACE_ENTRIES=100,R.prototype.connect=function(e){var t=this._traceMask(e,"password");if(this._trace("Client.connect",t,this.socket,this.connected),this.connected)throw new Error(_(y.INVALID_STATE,["already connected"]));if(this.socket)throw new Error(_(y.INVALID_STATE,["already connected"]));this._reconnecting&&(this._reconnectTimeout.cancel(),this._reconnectTimeout=null,this._reconnecting=!1),this.connectOptions=e,this._reconnectInterval=1,this._reconnecting=!1,e.uris?(this.hostIndex=0,this._doConnect(e.uris[0])):this._doConnect(this.uri)},R.prototype.subscribe=function(e,t){if(this._trace("Client.subscribe",e,t),!this.connected)throw new Error(_(y.INVALID_STATE,["not connected"]));var n=new M(f);n.topics=e.constructor===Array?e:[e],void 0===t.qos&&(t.qos=0),n.requestedQos=[];for(var r=0;r<n.topics.length;r++)n.requestedQos[r]=t.qos;t.onSuccess&&(n.onSuccess=function(e){t.onSuccess({invocationContext:t.invocationContext,grantedQos:e})}),t.onFailure&&(n.onFailure=function(e){t.onFailure({invocationContext:t.invocationContext,errorCode:e,errorMessage:_(e)})}),t.timeout&&(n.timeOut=new N(this,t.timeout,t.onFailure,[{invocationContext:t.invocationContext,errorCode:y.SUBSCRIBE_TIMEOUT.code,errorMessage:_(y.SUBSCRIBE_TIMEOUT)}])),this._requires_ack(n),this._schedule_message(n)},R.prototype.unsubscribe=function(e,t){if(this._trace("Client.unsubscribe",e,t),!this.connected)throw new Error(_(y.INVALID_STATE,["not connected"]));var n=new M(d);n.topics=e.constructor===Array?e:[e],t.onSuccess&&(n.callback=function(){t.onSuccess({invocationContext:t.invocationContext})}),t.timeout&&(n.timeOut=new N(this,t.timeout,t.onFailure,[{invocationContext:t.invocationContext,errorCode:y.UNSUBSCRIBE_TIMEOUT.code,errorMessage:_(y.UNSUBSCRIBE_TIMEOUT)}])),this._requires_ack(n),this._schedule_message(n)},R.prototype.send=function(e){this._trace("Client.send",e);var t=new M(o);if(t.payloadMessage=e,this.connected)e.qos>0?this._requires_ack(t):this.onMessageDelivered&&(this._notify_msg_sent[t]=this.onMessageDelivered(t.payloadMessage)),this._schedule_message(t);else{if(!this._reconnecting||!this.disconnectedPublishing)throw new Error(_(y.INVALID_STATE,["not connected"]));if(Object.keys(this._sentMessages).length+this._buffered_msg_queue.length>this.disconnectedBufferSize)throw new Error(_(y.BUFFER_FULL,[this.disconnectedBufferSize]));e.qos>0?this._requires_ack(t):(t.sequence=++this._sequence,this._buffered_msg_queue.unshift(t))}},R.prototype.disconnect=function(){if(this._trace("Client.disconnect"),this._reconnecting&&(this._reconnectTimeout.cancel(),this._reconnectTimeout=null,this._reconnecting=!1),!this.socket)throw new Error(_(y.INVALID_STATE,["not connecting or connected"]));var e=new M(g);this._notify_msg_sent[e]=b(this._disconnected,this),this._schedule_message(e)},R.prototype.getTraceLog=function(){if(null!==this._traceBuffer){for(var e in this._trace("Client.getTraceLog",new Date),this._trace("Client.getTraceLog in flight messages",this._sentMessages.length),this._sentMessages)this._trace("_sentMessages ",e,this._sentMessages[e]);for(var e in this._receivedMessages)this._trace("_receivedMessages ",e,this._receivedMessages[e]);return this._traceBuffer}},R.prototype.startTrace=function(){null===this._traceBuffer&&(this._traceBuffer=[]),this._trace("Client.startTrace",new Date,"@VERSION@-@BUILDLEVEL@")},R.prototype.stopTrace=function(){delete this._traceBuffer},R.prototype._doConnect=function(e){if(this.connectOptions.useSSL){var t=e.split(":");t[0]="wss",e=t.join(":")}this._wsuri=e,this.connected=!1,this.connectOptions.mqttVersion<4?this.socket=new WebSocket(e,["mqttv3.1"]):this.socket=new WebSocket(e,["mqtt"]),this.socket.binaryType="arraybuffer",this.socket.onopen=b(this._on_socket_open,this),this.socket.onmessage=b(this._on_socket_message,this),this.socket.onerror=b(this._on_socket_error,this),this.socket.onclose=b(this._on_socket_close,this),this.sendPinger=new P(this,this.connectOptions.keepAliveInterval),this.receivePinger=new P(this,this.connectOptions.keepAliveInterval),this._connectTimeout&&(this._connectTimeout.cancel(),this._connectTimeout=null),this._connectTimeout=new N(this,this.connectOptions.timeout,this._disconnected,[y.CONNECT_TIMEOUT.code,_(y.CONNECT_TIMEOUT)])},R.prototype._schedule_message=function(e){this._msg_queue.unshift(e),this.connected&&this._process_queue()},R.prototype.store=function(e,t){var r={type:t.type,messageIdentifier:t.messageIdentifier,version:1};switch(t.type){case o:t.pubRecReceived&&(r.pubRecReceived=!0),r.payloadMessage={};for(var i="",s=t.payloadMessage.payloadBytes,a=0;a<s.length;a++)s[a]<=15?i=i+"0"+s[a].toString(16):i+=s[a].toString(16);r.payloadMessage.payloadHex=i,r.payloadMessage.qos=t.payloadMessage.qos,r.payloadMessage.destinationName=t.payloadMessage.destinationName,t.payloadMessage.duplicate&&(r.payloadMessage.duplicate=!0),t.payloadMessage.retained&&(r.payloadMessage.retained=!0),0===e.indexOf("Sent:")&&(void 0===t.sequence&&(t.sequence=++this._sequence),r.sequence=t.sequence);break;default:throw Error(_(y.INVALID_STORED_DATA,[e+this._localKey+t.messageIdentifier,r]))}n.setItem(e+this._localKey+t.messageIdentifier,JSON.stringify(r))},R.prototype.restore=function(e){var t=n.getItem(e),r=JSON.parse(t),i=new M(r.type,r);switch(r.type){case o:for(var s=r.payloadMessage.payloadHex,a=new ArrayBuffer(s.length/2),u=new Uint8Array(a),c=0;s.length>=2;){var f=parseInt(s.substring(0,2),16);s=s.substring(2,s.length),u[c++]=f}var l=new L(u);l.qos=r.payloadMessage.qos,l.destinationName=r.payloadMessage.destinationName,r.payloadMessage.duplicate&&(l.duplicate=!0),r.payloadMessage.retained&&(l.retained=!0),i.payloadMessage=l;break;default:throw Error(_(y.INVALID_STORED_DATA,[e,t]))}0===e.indexOf("Sent:"+this._localKey)?(i.payloadMessage.duplicate=!0,this._sentMessages[i.messageIdentifier]=i):0===e.indexOf("Received:"+this._localKey)&&(this._receivedMessages[i.messageIdentifier]=i)},R.prototype._process_queue=function(){for(var e=null;e=this._msg_queue.pop();)this._socket_send(e),this._notify_msg_sent[e]&&(this._notify_msg_sent[e](),delete this._notify_msg_sent[e])},R.prototype._requires_ack=function(e){var t=Object.keys(this._sentMessages).length;if(t>this.maxMessageIdentifier)throw Error("Too many messages:"+t);for(;void 0!==this._sentMessages[this._message_identifier];)this._message_identifier++;e.messageIdentifier=this._message_identifier,this._sentMessages[e.messageIdentifier]=e,e.type===o&&this.store("Sent:",e),this._message_identifier===this.maxMessageIdentifier&&(this._message_identifier=1)},R.prototype._on_socket_open=function(){var e=new M(r,this.connectOptions);e.clientId=this.clientId,this._socket_send(e)},R.prototype._on_socket_message=function(e){this._trace("Client._on_socket_message",e.data);for(var t=this._deframeMessages(e.data),n=0;n<t.length;n+=1)this._handleMessage(t[n])},R.prototype._deframeMessages=function(e){var t=new Uint8Array(e),n=[];if(this.receiveBuffer){var r=new Uint8Array(this.receiveBuffer.length+t.length);r.set(this.receiveBuffer),r.set(t,this.receiveBuffer.length),t=r,delete this.receiveBuffer}try{for(var i=0;i<t.length;){var o=A(t,i),s=o[0];if(i=o[1],null===s)break;n.push(s)}i<t.length&&(this.receiveBuffer=t.subarray(i))}catch(e){var a="undefined"==e.hasOwnProperty("stack")?e.stack.toString():"No Error Stack Available";return void this._disconnected(y.INTERNAL_ERROR.code,_(y.INTERNAL_ERROR,[e.message,a]))}return n},R.prototype._handleMessage=function(e){this._trace("Client._handleMessage",e);try{switch(e.type){case i:if(this._connectTimeout.cancel(),this._reconnectTimeout&&this._reconnectTimeout.cancel(),this.connectOptions.cleanSession){for(var t in this._sentMessages){var r=this._sentMessages[t];n.removeItem("Sent:"+this._localKey+r.messageIdentifier)}for(var t in this._sentMessages={},this._receivedMessages){var f=this._receivedMessages[t];n.removeItem("Received:"+this._localKey+f.messageIdentifier)}this._receivedMessages={}}if(0!==e.returnCode){this._disconnected(y.CONNACK_RETURNCODE.code,_(y.CONNACK_RETURNCODE,[e.returnCode,w[e.returnCode]]));break}this.connected=!0,this.connectOptions.uris&&(this.hostIndex=this.connectOptions.uris.length);var d=[];for(var p in this._sentMessages)this._sentMessages.hasOwnProperty(p)&&d.push(this._sentMessages[p]);if(this._buffered_msg_queue.length>0)for(var m=null;m=this._buffered_msg_queue.pop();)d.push(m),this.onMessageDelivered&&(this._notify_msg_sent[m]=this.onMessageDelivered(m.payloadMessage));d=d.sort((function(e,t){return e.sequence-t.sequence}));for(var b=0,S=d.length;b<S;b++)if((r=d[b]).type==o&&r.pubRecReceived){var E=new M(u,{messageIdentifier:r.messageIdentifier});this._schedule_message(E)}else this._schedule_message(r);this.connectOptions.onSuccess&&this.connectOptions.onSuccess({invocationContext:this.connectOptions.invocationContext});var A=!1;this._reconnecting&&(A=!0,this._reconnectInterval=1,this._reconnecting=!1),this._connected(A,this._wsuri),this._process_queue();break;case o:this._receivePublish(e);break;case s:(r=this._sentMessages[e.messageIdentifier])&&(delete this._sentMessages[e.messageIdentifier],n.removeItem("Sent:"+this._localKey+e.messageIdentifier),this.onMessageDelivered&&this.onMessageDelivered(r.payloadMessage));break;case a:(r=this._sentMessages[e.messageIdentifier])&&(r.pubRecReceived=!0,E=new M(u,{messageIdentifier:e.messageIdentifier}),this.store("Sent:",r),this._schedule_message(E));break;case u:f=this._receivedMessages[e.messageIdentifier],n.removeItem("Received:"+this._localKey+e.messageIdentifier),f&&(this._receiveMessage(f),delete this._receivedMessages[e.messageIdentifier]);var I=new M(c,{messageIdentifier:e.messageIdentifier});this._schedule_message(I);break;case c:r=this._sentMessages[e.messageIdentifier],delete this._sentMessages[e.messageIdentifier],n.removeItem("Sent:"+this._localKey+e.messageIdentifier),this.onMessageDelivered&&this.onMessageDelivered(r.payloadMessage);break;case l:(r=this._sentMessages[e.messageIdentifier])&&(r.timeOut&&r.timeOut.cancel(),128===e.returnCode[0]?r.onFailure&&r.onFailure(e.returnCode):r.onSuccess&&r.onSuccess(e.returnCode),delete this._sentMessages[e.messageIdentifier]);break;case h:(r=this._sentMessages[e.messageIdentifier])&&(r.timeOut&&r.timeOut.cancel(),r.callback&&r.callback(),delete this._sentMessages[e.messageIdentifier]);break;case v:this.sendPinger.reset();break;case g:this._disconnected(y.INVALID_MQTT_MESSAGE_TYPE.code,_(y.INVALID_MQTT_MESSAGE_TYPE,[e.type]));break;default:this._disconnected(y.INVALID_MQTT_MESSAGE_TYPE.code,_(y.INVALID_MQTT_MESSAGE_TYPE,[e.type]))}}catch(e){var k="undefined"==e.hasOwnProperty("stack")?e.stack.toString():"No Error Stack Available";return void this._disconnected(y.INTERNAL_ERROR.code,_(y.INTERNAL_ERROR,[e.message,k]))}},R.prototype._on_socket_error=function(e){this._reconnecting||this._disconnected(y.SOCKET_ERROR.code,_(y.SOCKET_ERROR,[e.data]))},R.prototype._on_socket_close=function(){this._reconnecting||this._disconnected(y.SOCKET_CLOSE.code,_(y.SOCKET_CLOSE))},R.prototype._socket_send=function(e){if(1==e.type){var t=this._traceMask(e,"password");this._trace("Client._socket_send",t)}else this._trace("Client._socket_send",e);this.socket.send(e.encode()),this.sendPinger.reset()},R.prototype._receivePublish=function(e){switch(e.payloadMessage.qos){case"undefined":case 0:this._receiveMessage(e);break;case 1:var t=new M(s,{messageIdentifier:e.messageIdentifier});this._schedule_message(t),this._receiveMessage(e);break;case 2:this._receivedMessages[e.messageIdentifier]=e,this.store("Received:",e);var n=new M(a,{messageIdentifier:e.messageIdentifier});this._schedule_message(n);break;default:throw Error("Invaild qos="+e.payloadMessage.qos)}},R.prototype._receiveMessage=function(e){this.onMessageArrived&&this.onMessageArrived(e.payloadMessage)},R.prototype._connected=function(e,t){this.onConnected&&this.onConnected(e,t)},R.prototype._reconnect=function(){this._trace("Client._reconnect"),this.connected||(this._reconnecting=!0,this.sendPinger.cancel(),this.receivePinger.cancel(),this._reconnectInterval<128&&(this._reconnectInterval=2*this._reconnectInterval),this.connectOptions.uris?(this.hostIndex=0,this._doConnect(this.connectOptions.uris[0])):this._doConnect(this.uri))},R.prototype._disconnected=function(e,t){if(this._trace("Client._disconnected",e,t),void 0!==e&&this._reconnecting)this._reconnectTimeout=new N(this,this._reconnectInterval,this._reconnect);else if(this.sendPinger.cancel(),this.receivePinger.cancel(),this._connectTimeout&&(this._connectTimeout.cancel(),this._connectTimeout=null),this._msg_queue=[],this._buffered_msg_queue=[],this._notify_msg_sent={},this.socket&&(this.socket.onopen=null,this.socket.onmessage=null,this.socket.onerror=null,this.socket.onclose=null,1===this.socket.readyState&&this.socket.close(),delete this.socket),this.connectOptions.uris&&this.hostIndex<this.connectOptions.uris.length-1)this.hostIndex++,this._doConnect(this.connectOptions.uris[this.hostIndex]);else if(void 0===e&&(e=y.OK.code,t=_(y.OK)),this.connected){if(this.connected=!1,this.onConnectionLost&&this.onConnectionLost({errorCode:e,errorMessage:t,reconnect:this.connectOptions.reconnect,uri:this._wsuri}),e!==y.OK.code&&this.connectOptions.reconnect)return this._reconnectInterval=1,void this._reconnect()}else 4===this.connectOptions.mqttVersion&&!1===this.connectOptions.mqttVersionExplicit?(this._trace("Failed to connect V4, dropping back to V3"),this.connectOptions.mqttVersion=3,this.connectOptions.uris?(this.hostIndex=0,this._doConnect(this.connectOptions.uris[0])):this._doConnect(this.uri)):this.connectOptions.onFailure&&this.connectOptions.onFailure({invocationContext:this.connectOptions.invocationContext,errorCode:e,errorMessage:t})},R.prototype._trace=function(){if(this.traceFunction){var e=Array.prototype.slice.call(arguments);for(var t in e)void 0!==e[t]&&e.splice(t,1,JSON.stringify(e[t]));var n=e.join("");this.traceFunction({severity:"Debug",message:n})}if(null!==this._traceBuffer){t=0;for(var r=arguments.length;t<r;t++)this._traceBuffer.length==this._MAX_TRACE_ENTRIES&&this._traceBuffer.shift(),0===t||void 0===arguments[t]?this._traceBuffer.push(arguments[t]):this._traceBuffer.push(" "+JSON.stringify(arguments[t]))}},R.prototype._traceMask=function(e,t){var n={};for(var r in e)e.hasOwnProperty(r)&&(n[r]=r==t?"******":e[r]);return n};var L=function(e){var t,n;if(!("string"==typeof e||e instanceof ArrayBuffer||ArrayBuffer.isView(e)&&!(e instanceof DataView)))throw _(y.INVALID_ARGUMENT,[e,"newPayload"]);t=e;var r=0,i=!1,o=!1;Object.defineProperties(this,{payloadString:{enumerable:!0,get:function(){return"string"==typeof t?t:T(t,0,t.length)}},payloadBytes:{enumerable:!0,get:function(){if("string"==typeof t){var e=new ArrayBuffer(x(t)),n=new Uint8Array(e);return C(t,n,0),n}return t}},destinationName:{enumerable:!0,get:function(){return n},set:function(e){if("string"!=typeof e)throw new Error(_(y.INVALID_ARGUMENT,[e,"newDestinationName"]));n=e}},qos:{enumerable:!0,get:function(){return r},set:function(e){if(0!==e&&1!==e&&2!==e)throw new Error("Invalid argument:"+e);r=e}},retained:{enumerable:!0,get:function(){return i},set:function(e){if("boolean"!=typeof e)throw new Error(_(y.INVALID_ARGUMENT,[e,"newRetained"]));i=e}},topic:{enumerable:!0,get:function(){return n},set:function(e){n=e}},duplicate:{enumerable:!0,get:function(){return o},set:function(e){o=e}}})};return{Client:function(e,t,n,r){var i;if("string"!=typeof e)throw new Error(_(y.INVALID_TYPE,[typeof e,"host"]));if(2==arguments.length){r=t;var o=(i=e).match(/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/);if(!o)throw new Error(_(y.INVALID_ARGUMENT,[e,"host"]));e=o[4]||o[2],t=parseInt(o[7]),n=o[8]}else{if(3==arguments.length&&(r=n,n="/mqtt"),"number"!=typeof t||t<0)throw new Error(_(y.INVALID_TYPE,[typeof t,"port"]));if("string"!=typeof n)throw new Error(_(y.INVALID_TYPE,[typeof n,"path"]));var s=-1!==e.indexOf(":")&&"["!==e.slice(0,1)&&"]"!==e.slice(-1);i="ws://"+(s?"["+e+"]":e)+":"+t+n}for(var a=0,u=0;u<r.length;u++){var c=r.charCodeAt(u);55296<=c&&c<=56319&&u++,a++}if("string"!=typeof r||a>65535)throw new Error(_(y.INVALID_ARGUMENT,[r,"clientId"]));var f=new R(i,e,t,n,r);Object.defineProperties(this,{host:{get:function(){return e},set:function(){throw new Error(_(y.UNSUPPORTED_OPERATION))}},port:{get:function(){return t},set:function(){throw new Error(_(y.UNSUPPORTED_OPERATION))}},path:{get:function(){return n},set:function(){throw new Error(_(y.UNSUPPORTED_OPERATION))}},uri:{get:function(){return i},set:function(){throw new Error(_(y.UNSUPPORTED_OPERATION))}},clientId:{get:function(){return f.clientId},set:function(){throw new Error(_(y.UNSUPPORTED_OPERATION))}},onConnected:{get:function(){return f.onConnected},set:function(e){if("function"!=typeof e)throw new Error(_(y.INVALID_TYPE,[typeof e,"onConnected"]));f.onConnected=e}},disconnectedPublishing:{get:function(){return f.disconnectedPublishing},set:function(e){f.disconnectedPublishing=e}},disconnectedBufferSize:{get:function(){return f.disconnectedBufferSize},set:function(e){f.disconnectedBufferSize=e}},onConnectionLost:{get:function(){return f.onConnectionLost},set:function(e){if("function"!=typeof e)throw new Error(_(y.INVALID_TYPE,[typeof e,"onConnectionLost"]));f.onConnectionLost=e}},onMessageDelivered:{get:function(){return f.onMessageDelivered},set:function(e){if("function"!=typeof e)throw new Error(_(y.INVALID_TYPE,[typeof e,"onMessageDelivered"]));f.onMessageDelivered=e}},onMessageArrived:{get:function(){return f.onMessageArrived},set:function(e){if("function"!=typeof e)throw new Error(_(y.INVALID_TYPE,[typeof e,"onMessageArrived"]));f.onMessageArrived=e}},trace:{get:function(){return f.traceFunction},set:function(e){if("function"!=typeof e)throw new Error(_(y.INVALID_TYPE,[typeof e,"onTrace"]));f.traceFunction=e}}}),this.connect=function(e){if(m(e=e||{},{timeout:"number",userName:"string",password:"string",willMessage:"object",keepAliveInterval:"number",cleanSession:"boolean",useSSL:"boolean",invocationContext:"object",onSuccess:"function",onFailure:"function",hosts:"object",ports:"object",reconnect:"boolean",mqttVersion:"number",mqttVersionExplicit:"boolean",uris:"object"}),void 0===e.keepAliveInterval&&(e.keepAliveInterval=60),e.mqttVersion>4||e.mqttVersion<3)throw new Error(_(y.INVALID_ARGUMENT,[e.mqttVersion,"connectOptions.mqttVersion"]));if(void 0===e.mqttVersion?(e.mqttVersionExplicit=!1,e.mqttVersion=4):e.mqttVersionExplicit=!0,void 0!==e.password&&void 0===e.userName)throw new Error(_(y.INVALID_ARGUMENT,[e.password,"connectOptions.password"]));if(e.willMessage){if(!(e.willMessage instanceof L))throw new Error(_(y.INVALID_TYPE,[e.willMessage,"connectOptions.willMessage"]));if(e.willMessage.stringPayload=null,void 0===e.willMessage.destinationName)throw new Error(_(y.INVALID_TYPE,[typeof e.willMessage.destinationName,"connectOptions.willMessage.destinationName"]))}if(void 0===e.cleanSession&&(e.cleanSession=!0),e.hosts){if(!(e.hosts instanceof Array))throw new Error(_(y.INVALID_ARGUMENT,[e.hosts,"connectOptions.hosts"]));if(e.hosts.length<1)throw new Error(_(y.INVALID_ARGUMENT,[e.hosts,"connectOptions.hosts"]));for(var t=!1,r=0;r<e.hosts.length;r++){if("string"!=typeof e.hosts[r])throw new Error(_(y.INVALID_TYPE,[typeof e.hosts[r],"connectOptions.hosts["+r+"]"]));if(/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/.test(e.hosts[r])){if(0===r)t=!0;else if(!t)throw new Error(_(y.INVALID_ARGUMENT,[e.hosts[r],"connectOptions.hosts["+r+"]"]))}else if(t)throw new Error(_(y.INVALID_ARGUMENT,[e.hosts[r],"connectOptions.hosts["+r+"]"]))}if(t)e.uris=e.hosts;else{if(!e.ports)throw new Error(_(y.INVALID_ARGUMENT,[e.ports,"connectOptions.ports"]));if(!(e.ports instanceof Array))throw new Error(_(y.INVALID_ARGUMENT,[e.ports,"connectOptions.ports"]));if(e.hosts.length!==e.ports.length)throw new Error(_(y.INVALID_ARGUMENT,[e.ports,"connectOptions.ports"]));for(e.uris=[],r=0;r<e.hosts.length;r++){if("number"!=typeof e.ports[r]||e.ports[r]<0)throw new Error(_(y.INVALID_TYPE,[typeof e.ports[r],"connectOptions.ports["+r+"]"]));var o=e.hosts[r],s=e.ports[r],a=-1!==o.indexOf(":");i="ws://"+(a?"["+o+"]":o)+":"+s+n,e.uris.push(i)}}}f.connect(e)},this.subscribe=function(e,t){if("string"!=typeof e&&e.constructor!==Array)throw new Error("Invalid argument:"+e);if(m(t=t||{},{qos:"number",invocationContext:"object",onSuccess:"function",onFailure:"function",timeout:"number"}),t.timeout&&!t.onFailure)throw new Error("subscribeOptions.timeout specified with no onFailure callback.");if(void 0!==t.qos&&0!==t.qos&&1!==t.qos&&2!==t.qos)throw new Error(_(y.INVALID_ARGUMENT,[t.qos,"subscribeOptions.qos"]));f.subscribe(e,t)},this.unsubscribe=function(e,t){if("string"!=typeof e&&e.constructor!==Array)throw new Error("Invalid argument:"+e);if(m(t=t||{},{invocationContext:"object",onSuccess:"function",onFailure:"function",timeout:"number"}),t.timeout&&!t.onFailure)throw new Error("unsubscribeOptions.timeout specified with no onFailure callback.");f.unsubscribe(e,t)},this.send=function(e,t,n,r){var i;if(0===arguments.length)throw new Error("Invalid argument.length");if(1==arguments.length){if(!(e instanceof L)&&"string"!=typeof e)throw new Error("Invalid argument:"+typeof e);if(void 0===(i=e).destinationName)throw new Error(_(y.INVALID_ARGUMENT,[i.destinationName,"Message.destinationName"]));f.send(i)}else(i=new L(t)).destinationName=e,arguments.length>=3&&(i.qos=n),arguments.length>=4&&(i.retained=r),f.send(i)},this.publish=function(e,t,n,r){var i;if(0===arguments.length)throw new Error("Invalid argument.length");if(1==arguments.length){if(!(e instanceof L)&&"string"!=typeof e)throw new Error("Invalid argument:"+typeof e);if(void 0===(i=e).destinationName)throw new Error(_(y.INVALID_ARGUMENT,[i.destinationName,"Message.destinationName"]));f.send(i)}else(i=new L(t)).destinationName=e,arguments.length>=3&&(i.qos=n),arguments.length>=4&&(i.retained=r),f.send(i)},this.disconnect=function(){f.disconnect()},this.getTraceLog=function(){return f.getTraceLog()},this.startTrace=function(){f.startTrace()},this.stopTrace=function(){f.stopTrace()},this.isConnected=function(){return f.connected}},Message:L}}(void 0!==t?t:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},e.exports=n()}).call(this,n(31))},function(e,t,n){"use strict";var r,i,o=n(486);function s(e,t,n){if(e._observer)a(e._observer,t,n);else if(e._observers){var r=[];e._observers.forEach((function(e){r.push(e)})),r.forEach((function(e){a(e,t,n)}))}}function a(e,t,n){if(!e.closed)switch(t){case"next":return e.next(n);case"error":return e.error(n);case"complete":return e.complete(n)}}function u(e){return e._observer||e._observers&&e._observers.size>0}function c(e){var t=this;this._observer=null,this._observers=null,this._observable=new o((function(n){return function(e,t){!u(e)&&t&&t.start&&t.start()}(t,e),function(e,t){e._observers?e._observers.add(t):e._observer?(e._observers=new Set,e._observers.add(e._observer),e._observers.add(t),e._observer=null):e._observer=t}(t,n),function(){!function(e,t){e._observers?e._observers.delete(t):e._observer===t&&(e._observer=null)}(t,n),function(e,t){!u(e)&&t&&t.pause&&t.pause()}(t,e)}}))}r=c.prototype,i={get observable(){return this._observable},get observed(){return u(this)},next:function(e){s(this,"next",e)},error:function(e){s(this,"error",e)},complete:function(e){s(this,"complete",e)}},Object.keys(i).forEach((function(e){var t=Object.getOwnPropertyDescriptor(i,e);t.enumerable=!1,Object.defineProperty(r,e,t)})),e.exports=c},function(e,t,n){"use strict";n.d(t,"a",(function(){return Nt}));var r=n(44),i=n(33),o=n(88),s=n(19);function a(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];throw Error("[Immer] minified error nr: "+e+(n.length?" "+n.map((function(e){return"'"+e+"'"})).join(","):"")+". Find the full error at: https://bit.ly/3cXEKWf")}function u(e){return!!e&&!!e[Z]}function c(e){return!!e&&(function(e){if(!e||"object"!=typeof e)return!1;var t=Object.getPrototypeOf(e);return!t||t===Object.prototype}(e)||Array.isArray(e)||!!e[J]||!!e.constructor[J]||g(e)||m(e))}function f(e,t,n){void 0===n&&(n=!1),0===l(e)?(n?Object.keys:X)(e).forEach((function(r){n&&"symbol"==typeof r||t(r,e[r],e)})):e.forEach((function(n,r){return t(r,n,e)}))}function l(e){var t=e[Z];return t?t.i>3?t.i-4:t.i:Array.isArray(e)?1:g(e)?2:m(e)?3:0}function d(e,t){return 2===l(e)?e.has(t):Object.prototype.hasOwnProperty.call(e,t)}function h(e,t){return 2===l(e)?e.get(t):e[t]}function p(e,t,n){var r=l(e);2===r?e.set(t,n):3===r?(e.delete(t),e.add(n)):e[t]=n}function v(e,t){return e===t?0!==e||1/e==1/t:e!=e&&t!=t}function g(e){return G&&e instanceof Map}function m(e){return W&&e instanceof Set}function b(e){return e.o||e.t}function y(e){if(Array.isArray(e))return Array.prototype.slice.call(e);var t=Q(e);delete t[Z];for(var n=X(t),r=0;r<n.length;r++){var i=n[r],o=t[i];!1===o.writable&&(o.writable=!0,o.configurable=!0),(o.get||o.set)&&(t[i]={configurable:!0,writable:!0,enumerable:o.enumerable,value:e[i]})}return Object.create(Object.getPrototypeOf(e),t)}function w(e,t){return void 0===t&&(t=!1),S(e)||u(e)||!c(e)||(l(e)>1&&(e.set=e.add=e.clear=e.delete=_),Object.freeze(e),t&&f(e,(function(e,t){return w(t,!0)}),!0)),e}function _(){a(2)}function S(e){return null==e||"object"!=typeof e||Object.isFrozen(e)}function E(e){var t=ee[e];return t||a(18,e),t}function M(e,t){ee[e]||(ee[e]=t)}function A(){return H}function I(e,t){t&&(E("Patches"),e.u=[],e.s=[],e.v=t)}function k(e){O(e),e.p.forEach(C),e.p=null}function O(e){e===H&&(H=e.l)}function x(e){return H={p:[],l:H,h:e,m:!0,_:0}}function C(e){var t=e[Z];0===t.i||1===t.i?t.j():t.g=!0}function T(e,t){t._=t.p.length;var n=t.p[0],r=void 0!==e&&e!==n;return t.h.O||E("ES5").S(t,e,r),r?(n[Z].P&&(k(t),a(4)),c(e)&&(e=P(t,e),t.l||R(t,e)),t.u&&E("Patches").M(n[Z],e,t.u,t.s)):e=P(t,n,[]),k(t),t.u&&t.v(t.u,t.s),e!==Y?e:void 0}function P(e,t,n){if(S(t))return t;var r=t[Z];if(!r)return f(t,(function(i,o){return N(e,r,t,i,o,n)}),!0),t;if(r.A!==e)return t;if(!r.P)return R(e,r.t,!0),r.t;if(!r.I){r.I=!0,r.A._--;var i=4===r.i||5===r.i?r.o=y(r.k):r.o;f(3===r.i?new Set(i):i,(function(t,o){return N(e,r,i,t,o,n)})),R(e,i,!1),n&&e.u&&E("Patches").R(r,n,e.u,e.s)}return r.o}function N(e,t,n,r,i,o){if(u(i)){var s=P(e,i,o&&t&&3!==t.i&&!d(t.D,r)?o.concat(r):void 0);if(p(n,r,s),!u(s))return;e.m=!1}if(c(i)&&!S(i)){if(!e.h.N&&e._<1)return;P(e,i),t&&t.A.l||R(e,i)}}function R(e,t,n){void 0===n&&(n=!1),e.h.N&&e.m&&w(t,n)}function L(e,t){var n=e[Z];return(n?b(n):e)[t]}function j(e,t){if(t in e)for(var n=Object.getPrototypeOf(e);n;){var r=Object.getOwnPropertyDescriptor(n,t);if(r)return r;n=Object.getPrototypeOf(n)}}function D(e){e.P||(e.P=!0,e.l&&D(e.l))}function U(e){e.o||(e.o=y(e.t))}function B(e,t,n){var r=g(t)?E("MapSet").T(t,n):m(t)?E("MapSet").F(t,n):e.O?function(e,t){var n=Array.isArray(e),r={i:n?1:0,A:t?t.A:A(),P:!1,I:!1,D:{},l:t,t:e,k:null,o:null,j:null,C:!1},i=r,o=te;n&&(i=[r],o=ne);var s=Proxy.revocable(i,o),a=s.revoke,u=s.proxy;return r.k=u,r.j=a,u}(t,n):E("ES5").J(t,n);return(n?n.A:A()).p.push(r),r}function F(e){return u(e)||a(22,e),function e(t){if(!c(t))return t;var n,r=t[Z],i=l(t);if(r){if(!r.P&&(r.i<4||!E("ES5").K(r)))return r.t;r.I=!0,n=z(t,i),r.I=!1}else n=z(t,i);return f(n,(function(t,i){r&&h(r.t,t)===i||p(n,t,e(i))})),3===i?new Set(n):n}(e)}function z(e,t){switch(t){case 2:return new Map(e);case 3:return Array.from(e)}return y(e)}function q(){function e(t){if(!c(t))return t;if(Array.isArray(t))return t.map(e);if(g(t))return new Map(Array.from(t.entries()).map((function(t){return[t[0],e(t[1])]})));if(m(t))return new Set(Array.from(t).map(e));var n=Object.create(Object.getPrototypeOf(t));for(var r in t)n[r]=e(t[r]);return n}function t(t){return u(t)?e(t):t}var n="add";M("Patches",{$:function(t,r){return r.forEach((function(r){for(var i=r.path,o=r.op,s=t,u=0;u<i.length-1;u++){var c=l(s),f=i[u];0!==c&&1!==c||"__proto__"!==f&&"constructor"!==f||a(24),"function"==typeof s&&"prototype"===f&&a(24),"object"!=typeof(s=h(s,f))&&a(15,i.join("/"))}var d=l(s),p=e(r.value),v=i[i.length-1];switch(o){case"replace":switch(d){case 2:return s.set(v,p);case 3:a(16);default:return s[v]=p}case n:switch(d){case 1:return s.splice(v,0,p);case 2:return s.set(v,p);case 3:return s.add(p);default:return s[v]=p}case"remove":switch(d){case 1:return s.splice(v,1);case 2:return s.delete(v);case 3:return s.delete(r.value);default:return delete s[v]}default:a(17,o)}})),t},R:function(e,r,i,o){switch(e.i){case 0:case 4:case 2:return function(e,r,i,o){var s=e.t,a=e.o;f(e.D,(function(e,u){var c=h(s,e),f=h(a,e),l=u?d(s,e)?"replace":n:"remove";if(c!==f||"replace"!==l){var p=r.concat(e);i.push("remove"===l?{op:l,path:p}:{op:l,path:p,value:f}),o.push(l===n?{op:"remove",path:p}:"remove"===l?{op:n,path:p,value:t(c)}:{op:"replace",path:p,value:t(c)})}}))}(e,r,i,o);case 5:case 1:return function(e,r,i,o){var s=e.t,a=e.D,u=e.o;if(u.length<s.length){var c=[u,s];s=c[0],u=c[1];var f=[o,i];i=f[0],o=f[1]}for(var l=0;l<s.length;l++)if(a[l]&&u[l]!==s[l]){var d=r.concat([l]);i.push({op:"replace",path:d,value:t(u[l])}),o.push({op:"replace",path:d,value:t(s[l])})}for(var h=s.length;h<u.length;h++){var p=r.concat([h]);i.push({op:n,path:p,value:t(u[h])})}s.length<u.length&&o.push({op:"replace",path:r.concat(["length"]),value:s.length})}(e,r,i,o);case 3:return function(e,t,r,i){var o=e.t,s=e.o,a=0;o.forEach((function(e){if(!s.has(e)){var o=t.concat([a]);r.push({op:"remove",path:o,value:e}),i.unshift({op:n,path:o,value:e})}a++})),a=0,s.forEach((function(e){if(!o.has(e)){var s=t.concat([a]);r.push({op:n,path:s,value:e}),i.unshift({op:"remove",path:s,value:e})}a++}))}(e,r,i,o)}},M:function(e,t,n,r){n.push({op:"replace",path:[],value:t}),r.push({op:"replace",path:[],value:e.t})}})}var K,H,V="undefined"!=typeof Symbol&&"symbol"==typeof Symbol("x"),G="undefined"!=typeof Map,W="undefined"!=typeof Set,$="undefined"!=typeof Proxy&&void 0!==Proxy.revocable&&"undefined"!=typeof Reflect,Y=V?Symbol.for("immer-nothing"):((K={})["immer-nothing"]=!0,K),J=V?Symbol.for("immer-draftable"):"__$immer_draftable",Z=V?Symbol.for("immer-state"):"__$immer_state",X=("undefined"!=typeof Symbol&&Symbol.iterator,"undefined"!=typeof Reflect&&Reflect.ownKeys?Reflect.ownKeys:void 0!==Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:Object.getOwnPropertyNames),Q=Object.getOwnPropertyDescriptors||function(e){var t={};return X(e).forEach((function(n){t[n]=Object.getOwnPropertyDescriptor(e,n)})),t},ee={},te={get:function(e,t){if(t===Z)return e;var n=b(e);if(!d(n,t))return function(e,t,n){var r,i=j(t,n);return i?"value"in i?i.value:null===(r=i.get)||void 0===r?void 0:r.call(e.k):void 0}(e,n,t);var r=n[t];return e.I||!c(r)?r:r===L(e.t,t)?(U(e),e.o[t]=B(e.A.h,r,e)):r},has:function(e,t){return t in b(e)},ownKeys:function(e){return Reflect.ownKeys(b(e))},set:function(e,t,n){var r=j(b(e),t);if(null==r?void 0:r.set)return r.set.call(e.k,n),!0;if(!e.P){var i=L(b(e),t),o=null==i?void 0:i[Z];if(o&&o.t===n)return e.o[t]=n,e.D[t]=!1,!0;if(v(n,i)&&(void 0!==n||d(e.t,t)))return!0;U(e),D(e)}return e.o[t]=n,e.D[t]=!0,!0},deleteProperty:function(e,t){return void 0!==L(e.t,t)||t in e.t?(e.D[t]=!1,U(e),D(e)):delete e.D[t],e.o&&delete e.o[t],!0},getOwnPropertyDescriptor:function(e,t){var n=b(e),r=Reflect.getOwnPropertyDescriptor(n,t);return r?{writable:!0,configurable:1!==e.i||"length"!==t,enumerable:r.enumerable,value:n[t]}:r},defineProperty:function(){a(11)},getPrototypeOf:function(e){return Object.getPrototypeOf(e.t)},setPrototypeOf:function(){a(12)}},ne={};f(te,(function(e,t){ne[e]=function(){return arguments[0]=arguments[0][0],t.apply(this,arguments)}})),ne.deleteProperty=function(e,t){return te.deleteProperty.call(this,e[0],t)},ne.set=function(e,t,n){return te.set.call(this,e[0],t,n,e[0])};var re,ie=new(function(){function e(e){this.O=$,this.N=!0,"boolean"==typeof(null==e?void 0:e.useProxies)&&this.setUseProxies(e.useProxies),"boolean"==typeof(null==e?void 0:e.autoFreeze)&&this.setAutoFreeze(e.autoFreeze),this.produce=this.produce.bind(this),this.produceWithPatches=this.produceWithPatches.bind(this)}var t=e.prototype;return t.produce=function(e,t,n){if("function"==typeof e&&"function"!=typeof t){var r=t;t=e;var i=this;return function(e){var n=this;void 0===e&&(e=r);for(var o=arguments.length,s=Array(o>1?o-1:0),a=1;a<o;a++)s[a-1]=arguments[a];return i.produce(e,(function(e){var r;return(r=t).call.apply(r,[n,e].concat(s))}))}}var o;if("function"!=typeof t&&a(6),void 0!==n&&"function"!=typeof n&&a(7),c(e)){var s=x(this),u=B(this,e,void 0),f=!0;try{o=t(u),f=!1}finally{f?k(s):O(s)}return"undefined"!=typeof Promise&&o instanceof Promise?o.then((function(e){return I(s,n),T(e,s)}),(function(e){throw k(s),e})):(I(s,n),T(o,s))}if(!e||"object"!=typeof e){if((o=t(e))===Y)return;return void 0===o&&(o=e),this.N&&w(o,!0),o}a(21,e)},t.produceWithPatches=function(e,t){var n,r,i=this;return"function"==typeof e?function(t){for(var n=arguments.length,r=Array(n>1?n-1:0),o=1;o<n;o++)r[o-1]=arguments[o];return i.produceWithPatches(t,(function(t){return e.apply(void 0,[t].concat(r))}))}:[this.produce(e,t,(function(e,t){n=e,r=t})),n,r]},t.createDraft=function(e){c(e)||a(8),u(e)&&(e=F(e));var t=x(this),n=B(this,e,void 0);return n[Z].C=!0,O(t),n},t.finishDraft=function(e,t){var n=(e&&e[Z]).A;return I(n,t),T(void 0,n)},t.setAutoFreeze=function(e){this.N=e},t.setUseProxies=function(e){e&&!$&&a(20),this.O=e},t.applyPatches=function(e,t){var n;for(n=t.length-1;n>=0;n--){var r=t[n];if(0===r.path.length&&"replace"===r.op){e=r.value;break}}var i=E("Patches").$;return u(e)?i(e,t):this.produce(e,(function(e){return i(e,t.slice(n+1))}))},e}()),oe=(ie.produce,ie.produceWithPatches.bind(ie),ie.setAutoFreeze.bind(ie)),se=(ie.setUseProxies.bind(ie),ie.applyPatches.bind(ie),ie.createDraft.bind(ie),ie.finishDraft.bind(ie),n(109),n(14)),ae=n(9),ue=n(245),ce=function(){function e(){this._queue=[],this._pending=!1}return e.prototype.isLocked=function(){return this._pending},e.prototype.acquire=function(){var e=this,t=new Promise((function(t){return e._queue.push(t)}));return this._pending||this._dispatchNext(),t},e.prototype.runExclusive=function(e){return this.acquire().then((function(t){var n;try{n=e()}catch(e){throw t(),e}return Promise.resolve(n).then((function(e){return t(),e}),(function(e){throw t(),e}))}))},e.prototype._dispatchNext=function(){this._queue.length>0?(this._pending=!0,this._queue.shift()(this._dispatchNext.bind(this))):this._pending=!1},e}(),fe=n(257),le=n.n(fe),de=n(4),he=n(3),pe=function(){return Object(i.b)().isBrowser&&window.indexedDB||Object(i.c)()&&self.indexedDB?n(495).default:new(0,n(496).AsyncStorageAdapter)},ve=function(){return(ve=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},ge=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},me=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},be=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]])}return n},ye=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},we=new r.a("DataStore"),_e=function(){function e(e,t,n,r,i,o){this.schema=e,this.namespaceResolver=t,this.getModelConstructorByModelName=n,this.modelInstanceCreator=r,this.adapter=i,this.sessionId=o,this.adapter=pe(),this.pushStream=new le.a}return e.getNamespace=function(){return{name:he.b,relationships:{},enums:{},models:{},nonModels:{}}},e.prototype.init=function(){return ge(this,void 0,void 0,(function(){var e,t;return me(this,(function(n){switch(n.label){case 0:return void 0===this.initialized?[3,2]:[4,this.initialized];case 1:return n.sent(),[2];case 2:return we.debug("Starting Storage"),this.initialized=new Promise((function(n,r){e=n,t=r})),this.adapter.setUp(this.schema,this.namespaceResolver,this.modelInstanceCreator,this.getModelConstructorByModelName,this.sessionId).then(e,t),[4,this.initialized];case 3:return n.sent(),[2]}}))}))},e.prototype.save=function(e,t,n,r){return ge(this,void 0,void 0,(function(){var i,o=this;return me(this,(function(s){switch(s.label){case 0:return[4,this.init()];case 1:return s.sent(),[4,this.adapter.save(e,t)];case 2:return(i=s.sent()).forEach((function(e){var i,s=ye(e,2),a=s[0],u=s[1];if(u===de.c.UPDATE&&r&&r.length){i={},r.map((function(e){return e.path&&e.path[0]})).forEach((function(e){i[e]=a[e]}));var c=a.id,f=a._version,l=a._lastChangedAt,d=a._deleted;i=ve(ve({},i),{id:c,_version:f,_lastChangedAt:l,_deleted:d})}var h=i||a,p=Object.getPrototypeOf(a).constructor;o.pushStream.next({model:p,opType:u,element:h,mutator:n,condition:ae.a.getPredicates(t,!1)})})),[2,i]}}))}))},e.prototype.delete=function(e,t,n){return ge(this,void 0,void 0,(function(){var r,i,o,s,a=this;return me(this,(function(u){switch(u.label){case 0:return[4,this.init()];case 1:return u.sent(),[4,this.adapter.delete(e,t)];case 2:return s=ye.apply(void 0,[u.sent(),2]),i=s[0],r=s[1],o=new Set(i.map((function(e){return e.id}))),Object(he.s)(e)||Array.isArray(r)||(r=[r]),r.forEach((function(r){var i,s=Object.getPrototypeOf(r).constructor;Object(he.s)(e)||(i=o.has(r.id)?ae.a.getPredicates(t,!1):void 0),a.pushStream.next({model:s,opType:de.c.DELETE,element:r,mutator:n,condition:i})})),[2,[i,r]]}}))}))},e.prototype.query=function(e,t,n){return ge(this,void 0,void 0,(function(){return me(this,(function(r){switch(r.label){case 0:return[4,this.init()];case 1:return r.sent(),[4,this.adapter.query(e,t,n)];case 2:return[2,r.sent()]}}))}))},e.prototype.queryOne=function(e,t){return void 0===t&&(t=de.d.FIRST),ge(this,void 0,void 0,(function(){return me(this,(function(n){switch(n.label){case 0:return[4,this.init()];case 1:return n.sent(),[4,this.adapter.queryOne(e,t)];case 2:return[2,n.sent()]}}))}))},e.prototype.observe=function(e,t,n){var r=!e,i=ae.a.getPredicates(t,!1)||{},o=i.predicates,s=i.type,a=!!o,u=this.pushStream.observable.filter((function(e){var t=e.mutator;return!n||t!==n})).map((function(e){e.mutator;return be(e,["mutator"])}));return r||(u=u.filter((function(t){var n=t.model,r=t.element;return e===n&&(!a||Object(he.y)(r,s,o))}))),u},e.prototype.clear=function(e){return void 0===e&&(e=!0),ge(this,void 0,void 0,(function(){return me(this,(function(t){switch(t.label){case 0:return this.initialized=void 0,[4,this.adapter.clear()];case 1:return t.sent(),e&&this.pushStream.complete(),[2]}}))}))},e.prototype.batchSave=function(e,t,n){return ge(this,void 0,void 0,(function(){var r,i=this;return me(this,(function(o){switch(o.label){case 0:return[4,this.init()];case 1:return o.sent(),[4,this.adapter.batchSave(e,t)];case 2:return(r=o.sent()).forEach((function(t){var r=ye(t,2),o=r[0],s=r[1];i.pushStream.next({model:e,opType:s,element:o,mutator:n,condition:void 0})})),[2,r]}}))}))},e}(),Se=function(){function e(e,t,n,r,i,o){this.mutex=new ce,this.storage=new _e(e,t,n,r,i,o)}return e.prototype.runExclusive=function(e){return this.mutex.runExclusive(e.bind(this,this.storage))},e.prototype.save=function(e,t,n,r){return ge(this,void 0,void 0,(function(){return me(this,(function(i){return[2,this.runExclusive((function(i){return i.save(e,t,n,r)}))]}))}))},e.prototype.delete=function(e,t,n){return ge(this,void 0,void 0,(function(){return me(this,(function(r){return[2,this.runExclusive((function(r){if(Object(he.s)(e)){var i=e;return r.delete(i,t,n)}var o=e;return r.delete(o,t,n)}))]}))}))},e.prototype.query=function(e,t,n){return ge(this,void 0,void 0,(function(){return me(this,(function(r){return[2,this.runExclusive((function(r){return r.query(e,t,n)}))]}))}))},e.prototype.queryOne=function(e,t){return void 0===t&&(t=de.d.FIRST),ge(this,void 0,void 0,(function(){return me(this,(function(n){return[2,this.runExclusive((function(n){return n.queryOne(e,t)}))]}))}))},e.getNamespace=function(){return _e.getNamespace()},e.prototype.observe=function(e,t,n){return this.storage.observe(e,t,n)},e.prototype.clear=function(){return ge(this,void 0,void 0,(function(){return me(this,(function(e){switch(e.label){case 0:return[4,this.storage.clear()];case 1:return e.sent(),[2]}}))}))},e.prototype.batchSave=function(e,t){return this.storage.batchSave(e,t)},e.prototype.init=function(){return ge(this,void 0,void 0,(function(){return me(this,(function(e){return[2,this.storage.init()]}))}))},e}(),Ee=n(34),Me=function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},Ae=(new(function(){function e(){}return e.prototype.networkMonitor=function(t){if(Object(i.b)().isNode)return se.a.from([{online:!0}]);var n=Object(i.c)()?self:window;return new se.a((function(t){t.next({online:n.navigator.onLine});var r=function(){return t.next({online:!0})},i=function(){return t.next({online:!1})};return n.addEventListener("online",r),n.addEventListener("offline",i),e._observers.push(t),function(){n.removeEventListener("online",r),n.removeEventListener("offline",i),e._observers=e._observers.filter((function(e){return e!==t}))}}))},e._observerOverride=function(t){var n,r,i=function(n){if(n.closed)return e._observers=e._observers.filter((function(e){return e!==n})),"continue";n.next(t)};try{for(var o=Me(e._observers),s=o.next();!s.done;s=o.next()){i(s.value)}}catch(e){n={error:e}}finally{try{s&&!s.done&&(r=o.return)&&r.call(o)}finally{if(n)throw n.error}}},e._observers=[],e}())).networkMonitor(),Ie=function(){return(Ie=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},ke=(new r.a("DataStore"),function(){function e(){this.connectionStatus={online:!1}}return e.prototype.status=function(){var e=this;if(this.observer)throw new Error("Subscriber already exists");return new se.a((function(t){return e.observer=t,e.subscription=Ae.subscribe((function(n){var r=n.online;e.connectionStatus.online=r;var i=Ie({},e.connectionStatus);t.next(i)})),function(){e.unsubscribe()}}))},e.prototype.unsubscribe=function(){this.subscription&&this.subscription.unsubscribe()},e.prototype.socketDisconnected=function(){var e=this;this.observer&&"function"==typeof this.observer.next&&(this.observer.next({online:!1}),setTimeout((function(){var t=Ie({},e.connectionStatus);e.observer.next(t)}),5e3))},e}()),Oe=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},xe=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},Ce=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},Te=function(){function e(e,t){this.outbox=e,this.ownSymbol=t}return e.prototype.merge=function(e,t){return Oe(this,void 0,void 0,(function(){var n,r,i,o,s;return xe(this,(function(a){switch(a.label){case 0:return[4,this.outbox.getForModel(e,t)];case 1:return r=a.sent(),i=t._deleted,0!==r.length?[3,5]:i?(n=de.c.DELETE,[4,e.delete(t,void 0,this.ownSymbol)]):[3,3];case 2:return a.sent(),[3,5];case 3:return[4,e.save(t,void 0,this.ownSymbol)];case 4:o=Ce.apply(void 0,[a.sent(),1]),s=Ce(o[0],2),n=s[1],a.label=5;case 5:return[2,n]}}))}))},e.prototype.mergePage=function(e,t,n){return Oe(this,void 0,void 0,(function(){return xe(this,(function(r){switch(r.label){case 0:return[4,e.batchSave(t,n,this.ownSymbol)];case 1:return[2,r.sent()]}}))}))},e}(),Pe=n(13),Ne=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},Re=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},Le=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},je=function(){function e(e,t,n,r){this.schema=e,this.namespaceResolver=t,this.MutationEvent=n,this.ownSymbol=r}return e.prototype.enqueue=function(e,t){return Ne(this,void 0,void 0,(function(){var n=this;return Re(this,(function(r){return e.runExclusive((function(e){return Ne(n,void 0,void 0,(function(){var n,r,i,o,s,a,u,c=this;return Re(this,(function(f){switch(f.label){case 0:return n=this.schema.namespaces[he.c].models.MutationEvent,r=ae.a.createFromExisting(n,(function(e){return e.modelId("eq",t.modelId).id("ne",c.inProgressMutationEventId)})),[4,e.query(this.MutationEvent,r)];case 1:return i=Le.apply(void 0,[f.sent(),1]),void 0!==(o=i[0])?[3,3]:[4,e.save(t,void 0,this.ownSymbol)];case 2:return f.sent(),[2];case 3:return s=t.operation,o.operation!==Pe.a.CREATE?[3,8]:s!==Pe.a.DELETE?[3,5]:[4,e.delete(this.MutationEvent,r)];case 4:return f.sent(),[3,7];case 5:return[4,e.save(this.MutationEvent.copyOf(o,(function(e){e.data=t.data})),void 0,this.ownSymbol)];case 6:f.sent(),f.label=7;case 7:return[3,12];case 8:return a=t.condition,u=JSON.parse(a),0!==Object.keys(u).length?[3,10]:[4,e.delete(this.MutationEvent,r)];case 9:f.sent(),f.label=10;case 10:return[4,e.save(t,void 0,this.ownSymbol)];case 11:f.sent(),f.label=12;case 12:return[2]}}))}))})),[2]}))}))},e.prototype.dequeue=function(e){return Ne(this,void 0,void 0,(function(){var t;return Re(this,(function(n){switch(n.label){case 0:return[4,this.peek(e)];case 1:return t=n.sent(),[4,e.delete(t)];case 2:return n.sent(),this.inProgressMutationEventId=void 0,[2,t]}}))}))},e.prototype.peek=function(e){return Ne(this,void 0,void 0,(function(){var t;return Re(this,(function(n){switch(n.label){case 0:return[4,e.queryOne(this.MutationEvent,de.d.FIRST)];case 1:return t=n.sent(),this.inProgressMutationEventId=t?t.id:void 0,[2,t]}}))}))},e.prototype.getForModel=function(e,t){return Ne(this,void 0,void 0,(function(){var n;return Re(this,(function(r){switch(r.label){case 0:return n=this.schema.namespaces[he.c].models.MutationEvent,[4,e.query(this.MutationEvent,ae.a.createFromExisting(n,(function(e){return e.modelId("eq",t.id)})))];case 1:return[2,r.sent()]}}))}))},e.prototype.getModelIds=function(e){return Ne(this,void 0,void 0,(function(){var t,n;return Re(this,(function(r){switch(r.label){case 0:return[4,e.query(this.MutationEvent)];case 1:return t=r.sent(),n=new Set,t.forEach((function(e){var t=e.modelId;return n.add(t)})),[2,n]}}))}))},e}(),De=n(52),Ue=n(514),Be=function(){return(Be=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},Fe=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},ze=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},qe=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]])}return n},Ke=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},He=new r.a("DataStore"),Ve=function(){function e(e,t,n,r,i,o,s,a){this.schema=e,this.storage=t,this.userClasses=n,this.outbox=r,this.modelInstanceCreator=i,this.MutationEvent=o,this.conflictHandler=s,this.errorHandler=a,this.typeQuery=new WeakMap,this.processing=!1,this.generateQueries()}return e.prototype.generateQueries=function(){var e=this;Object.values(this.schema.namespaces).forEach((function(t){Object.values(t.models).filter((function(e){return e.syncable})).forEach((function(n){var r=Ke(Object(Pe.b)(t,n,"CREATE"),1)[0],i=Ke(Object(Pe.b)(t,n,"UPDATE"),1)[0],o=Ke(Object(Pe.b)(t,n,"DELETE"),1)[0];e.typeQuery.set(n,[r,i,o])}))}))},e.prototype.isReady=function(){return void 0!==this.observer},e.prototype.start=function(){var e=this;return new se.a((function(t){return e.observer=t,e.resume(),function(){e.pause()}}))},e.prototype.resume=function(){return Fe(this,void 0,void 0,(function(){var e,t,n,r,i,o,s,a,u,c,f,l,d,h,p;return ze(this,(function(v){switch(v.label){case 0:if(this.processing||!this.isReady())return[2];this.processing=!0,t=he.d,v.label=1;case 1:return(n=this.processing)?[4,this.outbox.peek(this.storage)]:[3,3];case 2:n=void 0!==(e=v.sent()),v.label=3;case 3:if(!n)return[3,12];r=e.model,i=e.operation,o=e.data,s=e.condition,a=this.userClasses[r],u=void 0,c=void 0,f=void 0,v.label=4;case 4:return v.trys.push([4,6,,7]),[4,this.jitteredRetry(t,r,i,o,s,a,this.MutationEvent,e)];case 5:return p=Ke.apply(void 0,[v.sent(),3]),u=p[0],c=p[1],f=p[2],[3,7];case 6:return"Offline"===(l=v.sent()).message||"RetryMutation"===l.message?[3,1]:[3,7];case 7:return void 0!==u?[3,9]:(He.debug("done retrying"),[4,this.outbox.dequeue(this.storage)]);case 8:return v.sent(),[3,1];case 9:return d=u.data[c],[4,this.outbox.dequeue(this.storage)];case 10:return v.sent(),[4,this.outbox.peek(this.storage)];case 11:return h=void 0!==v.sent(),this.observer.next({operation:i,modelDefinition:f,model:d,hasMore:h}),[3,1];case 12:return this.pause(),[2]}}))}))},e.prototype.jitteredRetry=function(e,t,n,r,i,o,s,a){return Fe(this,void 0,void 0,(function(){var u=this;return ze(this,(function(c){switch(c.label){case 0:return[4,Object(Ue.b)((function(t,n,r,i,o,s,a){return Fe(u,void 0,void 0,(function(){var u,c,f,l,d,h,p,v,g,m,b,y,w,_,S,E,M,A,I,k,O;return ze(this,(function(x){switch(x.label){case 0:u=Ke(this.createQueryVariables(e,t,n,r,i),5),c=u[0],f=u[1],l=u[2],d=u[3],h=u[4],p={query:c,variables:f},v=0,g=this.opTypeFromTransformerOperation(n),x.label=1;case 1:return x.trys.push([1,3,,13]),[4,De.a.graphql(p)];case 2:return[2,[x.sent(),d,h]];case 3:if(!((m=x.sent()).errors&&m.errors.length>0))return[3,12];if(b=Ke(m.errors,1),"Network Error"===(y=b[0]).message){if(!this.processing)throw new Ue.a("Offline");throw new Error("Network Error")}return"ConflictUnhandled"!==y.errorType?[3,11]:(v++,w=void 0,v>10?(w=de.a,[3,7]):[3,4]);case 4:return x.trys.push([4,6,,7]),[4,this.conflictHandler({modelConstructor:o,localModel:this.modelInstanceCreator(o,f.input),remoteModel:this.modelInstanceCreator(o,y.data),operation:g,attempts:v})];case 5:return w=x.sent(),[3,7];case 6:return _=x.sent(),He.warn("conflict trycatch",_),[3,13];case 7:return w!==de.a?[3,9]:(S=Ke(Object(Pe.b)(this.schema.namespaces[e],h,"GET"),1),E=Ke(S[0],3),M=E[1],A=E[2],[4,De.a.graphql({query:A,variables:{id:f.input.id}})]);case 8:return[2,[x.sent(),M,h]];case 9:return I=this.schema.namespaces[e],k=Object(Pe.d)(I.relationships,h,g,o,w,l,s,this.modelInstanceCreator,a.id),[4,this.storage.save(k)];case 10:throw x.sent(),new Ue.a("RetryMutation");case 11:try{this.errorHandler({localModel:this.modelInstanceCreator(o,f.input),message:y.message,operation:n,errorType:y.errorType,errorInfo:y.errorInfo,remoteModel:y.data?this.modelInstanceCreator(o,y.data):null})}catch(e){He.warn("failed to execute errorHandler",e)}finally{return[2,y.data?[{data:(O={},O[d]=y.data,O)},d,h]:[]]}x.label=12;case 12:return[3,13];case 13:if(p)return[3,1];x.label=14;case 14:return[2]}}))}))}),[t,n,r,i,o,s,a])];case 1:return[2,c.sent()]}}))}))},e.prototype.createQueryVariables=function(e,t,n,r,i){var o=this.schema.namespaces[e].models[t],s=this.typeQuery.get(o),a=Ke(s.find((function(e){return Ke(e,1)[0]===n})),3),u=a[1],c=a[2],f=JSON.parse(r),l=f._version,d=qe(f,["_version"]),h=n===Pe.a.DELETE?{id:d.id}:Object.values(o.fields).filter((function(e){var t=e.name,r=e.type,i=e.association;return Object(de.h)(r)?!(!Object(de.m)(i)||"BELONGS_TO"!==i.connectionType):n!==Pe.a.UPDATE||d.hasOwnProperty(t)})).map((function(e){var t=e.name,n=e.type,r=e.association,i=t,o=d[t];return Object(de.h)(n)&&Object(de.m)(r)&&(i=r.targetName,o=d[i]),[i,o]})).reduce((function(e,t){var n=Ke(t,2),r=n[0],i=n[1];return e[r]=i,e}),{}),p=Be(Be({},h),{_version:l}),v=JSON.parse(i);return[c,Be({input:p},n===Pe.a.CREATE?{}:{condition:Object.keys(v).length>0?v:null}),v,u,o]},e.prototype.opTypeFromTransformerOperation=function(e){switch(e){case Pe.a.CREATE:return de.c.INSERT;case Pe.a.DELETE:return de.c.DELETE;case Pe.a.UPDATE:return de.c.UPDATE;case Pe.a.GET:break;default:Object(he.f)(e)}},e.prototype.pause=function(){this.processing=!1},e}(),Ge=n(154),We=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},$e=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},Ye=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},Je=function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},Ze=new r.a("DataStore"),Xe=function(){function e(e,t,n,r){void 0===t&&(t=1e4),void 0===n&&(n=1e3),this.schema=e,this.maxRecordsToSync=t,this.syncPageSize=n,this.syncPredicates=r,this.typeQuery=new WeakMap,this.generateQueries()}return e.prototype.generateQueries=function(){var e=this;Object.values(this.schema.namespaces).forEach((function(t){Object.values(t.models).filter((function(e){return e.syncable})).forEach((function(n){var r=Ye(Object(Pe.b)(t,n,"LIST"),1),i=Ye(r[0]).slice(1);e.typeQuery.set(n,i)}))}))},e.prototype.graphqlFilterFromPredicate=function(e){if(!this.syncPredicates)return null;var t=ae.a.getPredicates(this.syncPredicates.get(e),!1);return t?Object(Pe.h)(t):null},e.prototype.retrievePage=function(e,t,n,r,i){return void 0===r&&(r=null),We(this,void 0,void 0,(function(){var o,s,a,u,c,f,l,d,h;return $e(this,(function(p){switch(p.label){case 0:return o=Ye(this.typeQuery.get(e),2),s=o[0],a=o[1],u={limit:r,nextToken:n,lastSync:t,filter:i},[4,this.jitteredRetry(a,u,s)];case 1:return c=p.sent().data,f=c[s],l=f.items,d=f.nextToken,h=f.startedAt,[2,{nextToken:d,startedAt:h,items:l}]}}))}))},e.prototype.jitteredRetry=function(e,t,n){return We(this,void 0,void 0,(function(){var r=this;return $e(this,(function(i){switch(i.label){case 0:return[4,Object(Ue.b)((function(e,t){return We(r,void 0,void 0,(function(){var r,i;return $e(this,(function(o){switch(o.label){case 0:return o.trys.push([0,2,,3]),[4,De.a.graphql({query:e,variables:t})];case 1:return[2,o.sent()];case 2:if(r=o.sent(),r.errors.some((function(e){return"Unauthorized"===e.errorType})))return(i=r).data[n].items=i.data[n].items.filter((function(e){return null!==e})),Ze.warn("queryError","User is unauthorized, some items could not be returned."),[2,i];throw r;case 3:return[2]}}))}))}),[e,t])];case 1:return[2,i.sent()]}}))}))},e.prototype.start=function(e){var t=this,n=!0,r=void 0!==this.maxRecordsToSync?this.maxRecordsToSync:1e4,i=void 0!==this.syncPageSize?this.syncPageSize:1e3,o=new Map;return new se.a((function(s){var a=Object.values(t.schema.namespaces).reduce((function(t,n){var r,i;try{for(var o=Je(Array.from(n.modelTopologicalOrdering.keys())),s=o.next();!s.done;s=o.next()){var a=s.value,u=e.get(n.models[a]);t.set(n.models[a],u)}}catch(e){r={error:e}}finally{try{s&&!s.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return t}),new Map),u=Array.from(a.entries()).filter((function(e){return Ye(e,1)[0].syncable})).map((function(e){var a=Ye(e,2),u=a[0],c=Ye(a[1],2),f=c[0],l=c[1];return We(t,void 0,void 0,(function(){var e,t,a,c,d,h,p,v,g,m=this;return $e(this,(function(b){switch(b.label){case 0:return e=!1,t=null,a=null,c=null,d=0,h=this.graphqlFilterFromPredicate(u),p=this.schema.namespaces[f].modelTopologicalOrdering.get(u.name),v=p.map((function(e){return o.get(f+"_"+e)})),g=new Promise((function(o){return We(m,void 0,void 0,(function(){var p,g;return $e(this,(function(m){switch(m.label){case 0:return[4,Promise.all(v)];case 1:m.sent(),m.label=2;case 2:return n?(p=Math.min(r-d,i),[4,this.retrievePage(u,l,t,p,h)]):[2];case 3:g=m.sent(),c=g.items,t=g.nextToken,a=g.startedAt,d+=c.length,e=null===t||d>=r,s.next({namespace:f,modelDefinition:u,items:c,done:e,startedAt:a,isFullSync:!l}),m.label=4;case 4:if(!e)return[3,2];m.label=5;case 5:return o(),[2]}}))}))})),o.set(f+"_"+u.name,g),[4,g];case 1:return b.sent(),[2]}}))}))}));return Promise.all(u).then((function(){s.complete()})),function(){n=!1}}))},e}(),Qe=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},et=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},tt=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},nt=function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},rt=function(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(tt(arguments[t]));return e},it=Object(i.b)().isNode,ot=new r.a("DataStore"),st=Symbol("sync");!function(e){e.SYNC_ENGINE_STORAGE_SUBSCRIBED="storageSubscribed",e.SYNC_ENGINE_SUBSCRIPTIONS_ESTABLISHED="subscriptionsEstablished",e.SYNC_ENGINE_SYNC_QUERIES_STARTED="syncQueriesStarted",e.SYNC_ENGINE_SYNC_QUERIES_READY="syncQueriesReady",e.SYNC_ENGINE_MODEL_SYNCED="modelSynced",e.SYNC_ENGINE_OUTBOX_MUTATION_ENQUEUED="outboxMutationEnqueued",e.SYNC_ENGINE_OUTBOX_MUTATION_PROCESSED="outboxMutationProcessed",e.SYNC_ENGINE_OUTBOX_STATUS="outboxStatus",e.SYNC_ENGINE_NETWORK_STATUS="networkStatus",e.SYNC_ENGINE_READY="ready"}(re||(re={}));var at=function(){function e(e,t,n,r,i,o,s,a,u,c,f,l){void 0===l&&(l={}),this.schema=e,this.namespaceResolver=t,this.modelClasses=n,this.userModelClasses=r,this.storage=i,this.modelInstanceCreator=o,this.maxRecordsToSync=s,this.syncPageSize=a,this.syncPredicates=f,this.amplifyConfig=l,this.online=!1;var d=this.modelClasses.MutationEvent;this.outbox=new je(this.schema,this.namespaceResolver,d,st),this.modelMerger=new Te(this.outbox,st),this.syncQueriesProcessor=new Xe(this.schema,this.maxRecordsToSync,this.syncPageSize,this.syncPredicates),this.subscriptionsProcessor=new Ge.b(this.schema,this.syncPredicates,this.amplifyConfig),this.mutationsProcessor=new Ve(this.schema,this.storage,this.userModelClasses,this.outbox,this.modelInstanceCreator,d,u,c),this.datastoreConnectivity=new ke}return e.prototype.start=function(e){var t=this;return new se.a((function(n){ot.log("starting sync engine...");var r=[];return Qe(t,void 0,void 0,(function(){var t,i,o,s=this;return et(this,(function(a){switch(a.label){case 0:return a.trys.push([0,2,,3]),[4,this.setupModels(e)];case 1:return a.sent(),[3,3];case 2:return t=a.sent(),n.error(t),[2];case 3:return i=new Promise((function(e){s.datastoreConnectivity.status().subscribe((function(t){var i=t.online;return Qe(s,void 0,void 0,(function(){var t,o,s,a,u,c=this;return et(this,(function(f){switch(f.label){case 0:return!i||this.online?[3,10]:(this.online=i,n.next({type:re.SYNC_ENGINE_NETWORK_STATUS,data:{active:this.online}}),o=void 0,it?(ot.warn("Realtime disabled when in a server-side environment"),[3,6]):[3,1]);case 1:u=tt(this.subscriptionsProcessor.start(),2),t=u[0],o=u[1],f.label=2;case 2:return f.trys.push([2,4,,5]),[4,new Promise((function(e,n){var i=t.subscribe({next:function(t){t===Ge.a.CONNECTED&&e()},error:function(e){n(e),c.disconnectionHandler()(e)}});r.push(i)}))];case 3:return f.sent(),[3,5];case 4:return s=f.sent(),n.error(s),[2];case 5:ot.log("Realtime ready"),n.next({type:re.SYNC_ENGINE_SUBSCRIPTIONS_ESTABLISHED}),f.label=6;case 6:return f.trys.push([6,8,,9]),[4,new Promise((function(e,t){var i=c.syncQueriesObservable().subscribe({next:function(t){t.type===re.SYNC_ENGINE_SYNC_QUERIES_READY&&e(),n.next(t)},complete:function(){e()},error:function(e){t(e)}});i&&r.push(i)}))];case 7:return f.sent(),[3,9];case 8:return a=f.sent(),n.error(a),[2];case 9:return r.push(this.mutationsProcessor.start().subscribe((function(e){var t=e.modelDefinition,r=e.model,i=e.hasMore,o=c.userModelClasses[t.name],s=c.modelInstanceCreator(o,r);c.storage.runExclusive((function(e){return c.modelMerger.merge(e,s)})),n.next({type:re.SYNC_ENGINE_OUTBOX_MUTATION_PROCESSED,data:{model:o,element:s}}),n.next({type:re.SYNC_ENGINE_OUTBOX_STATUS,data:{isEmpty:!i}})}))),it||r.push(o.subscribe((function(e){var t=tt(e,3),n=(t[0],t[1]),r=t[2],i=c.userModelClasses[n.name],o=c.modelInstanceCreator(i,r);c.storage.runExclusive((function(e){return c.modelMerger.merge(e,o)}))}))),[3,11];case 10:i||(this.online=i,n.next({type:re.SYNC_ENGINE_NETWORK_STATUS,data:{active:this.online}}),r.forEach((function(e){return e.unsubscribe()})),r=[]),f.label=11;case 11:return e(),[2]}}))}))}))})),this.storage.observe(null,null,st).filter((function(e){var t=e.model;return!0===s.getModelDefinition(t).syncable})).subscribe({next:function(e){var t=e.opType,r=e.model,o=e.element,a=e.condition;return Qe(s,void 0,void 0,(function(){var e,s,u,c;return et(this,(function(f){switch(f.label){case 0:return e=this.schema.namespaces[this.namespaceResolver(r)],s=this.modelClasses.MutationEvent,u=Object(Pe.g)(a),c=Object(Pe.d)(e.relationships,this.getModelDefinition(r),t,r,o,u,s,this.modelInstanceCreator),[4,this.outbox.enqueue(this.storage,c)];case 1:return f.sent(),n.next({type:re.SYNC_ENGINE_OUTBOX_MUTATION_ENQUEUED,data:{model:r,element:o}}),n.next({type:re.SYNC_ENGINE_OUTBOX_STATUS,data:{isEmpty:!1}}),[4,i];case 2:return f.sent(),this.online&&this.mutationsProcessor.resume(),[2]}}))}))}}),n.next({type:re.SYNC_ENGINE_STORAGE_SUBSCRIBED}),[4,this.outbox.peek(this.storage)];case 4:return o=void 0===a.sent(),n.next({type:re.SYNC_ENGINE_OUTBOX_STATUS,data:{isEmpty:o}}),[4,i];case 5:return a.sent(),n.next({type:re.SYNC_ENGINE_READY}),[2]}}))})),function(){r.forEach((function(e){return e.unsubscribe()}))}}))},e.prototype.getModelsMetadataWithNextFullSync=function(e){return Qe(this,void 0,void 0,(function(){var t,n=this;return et(this,(function(r){switch(r.label){case 0:return t=Map.bind,[4,this.getModelsMetadata()];case 1:return[2,new(t.apply(Map,[void 0,r.sent().map((function(t){var r=t.namespace,i=t.model,o=t.lastSync,s=t.lastFullSync,a=t.fullSyncInterval,u=(t.lastSyncPredicate,!s||s+a<e?0:o);return[n.schema.namespaces[r].models[i],[r,u]]}))]))]}}))}))},e.prototype.syncQueriesObservable=function(){var e=this;return this.online?new se.a((function(t){var n,r;return Qe(e,void 0,void 0,(function(){var e,i,o=this;return et(this,(function(s){switch(s.label){case 0:e=function(){var e,s,a,u,c,f,l,d,h;return et(this,(function(p){switch(p.label){case 0:return e=new WeakMap,[4,i.getModelsMetadataWithNextFullSync(Date.now())];case 1:return s=p.sent(),a=new Set(s.keys()),[4,new Promise((function(r){n=o.syncQueriesProcessor.start(s).subscribe({next:function(i){var s=i.namespace,h=i.modelDefinition,p=i.items,v=i.done,g=i.startedAt,m=i.isFullSync;return Qe(o,void 0,void 0,(function(){var i,o,b,y,w,_,S=this;return et(this,(function(E){switch(E.label){case 0:return i=this.userModelClasses[h.name],e.has(i)||(e.set(i,{new:0,updated:0,deleted:0}),f=Object(he.i)(),d=void 0===d?g:Math.max(d,g)),[4,this.storage.runExclusive((function(t){return Qe(S,void 0,void 0,(function(){var n,r,o,s,a,u,c,f,l,d,h,v,g,m,b;return et(this,(function(y){switch(y.label){case 0:return[4,this.outbox.getModelIds(t)];case 1:n=y.sent(),r=[],o=p.filter((function(e){return!n.has(e.id)||(r.push(e),!1)})),s=[],y.label=2;case 2:y.trys.push([2,7,8,9]),a=nt(r),u=a.next(),y.label=3;case 3:return u.done?[3,6]:(c=u.value,[4,this.modelMerger.merge(t,c)]);case 4:void 0!==(f=y.sent())&&s.push([c,f]),y.label=5;case 5:return u=a.next(),[3,3];case 6:return[3,9];case 7:return l=y.sent(),m={error:l},[3,9];case 8:try{u&&!u.done&&(b=a.return)&&b.call(a)}finally{if(m)throw m.error}return[7];case 9:return h=(d=s.push).apply,v=[s],[4,this.modelMerger.mergePage(t,i,o)];case 10:return h.apply(d,v.concat([rt.apply(void 0,[y.sent()])])),g=e.get(i),s.forEach((function(e){var t=tt(e,2)[1];switch(t){case de.c.INSERT:g.new++;break;case de.c.UPDATE:g.updated++;break;case de.c.DELETE:g.deleted++;break;default:Object(he.f)(t)}})),[2]}}))}))}))];case 1:return E.sent(),v?(o=h.name,[4,this.getModelMetadata(s,o)]):[3,4];case 2:return b=E.sent(),y=b.lastFullSync,w=b.fullSyncInterval,c=w,u=void 0===u?y:Math.max(u,m?g:y),b=this.modelClasses.ModelMetadata.copyOf(b,(function(e){e.lastSync=g,e.lastFullSync=m?g:b.lastFullSync})),[4,this.storage.save(b,void 0,st)];case 3:E.sent(),_=e.get(i),t.next({type:re.SYNC_ENGINE_MODEL_SYNCED,data:{model:i,isFullSync:m,isDeltaSync:!m,counts:_}}),a.delete(h),0===a.size&&(l=Object(he.i)()-f,r(),t.next({type:re.SYNC_ENGINE_SYNC_QUERIES_READY}),n.unsubscribe()),E.label=4;case 4:return[2]}}))}))},error:function(e){t.error(e)}}),t.next({type:re.SYNC_ENGINE_SYNC_QUERIES_STARTED,data:{models:Array.from(a).map((function(e){return e.name}))}})}))];case 2:return p.sent(),h=u+c-(d+l),ot.debug("Next fullSync in "+h/1e3+" seconds. ("+new Date(Date.now()+h)+")"),[4,new Promise((function(e){r=setTimeout(e,h)}))];case 3:return p.sent(),[2]}}))},i=this,s.label=1;case 1:return t.closed?[3,3]:[5,e()];case 2:return s.sent(),[3,1];case 3:return[2]}}))})),function(){n&&n.unsubscribe(),r&&clearTimeout(r)}})):se.a.of()},e.prototype.disconnectionHandler=function(){var e=this;return function(t){Ee.a.CONNECTION_CLOSED!==t&&Ee.a.TIMEOUT_DISCONNECT!==t||e.datastoreConnectivity.socketDisconnected()}},e.prototype.unsubscribeConnectivity=function(){this.datastoreConnectivity.unsubscribe()},e.prototype.setupModels=function(e){return Qe(this,void 0,void 0,(function(){var t,n,r,i,o,s,a,u,c,f,l,d,h,p=this;return et(this,(function(v){switch(v.label){case 0:t=e.fullSyncInterval,n=this.modelClasses.ModelMetadata,r=[],Object.values(this.schema.namespaces).forEach((function(e){Object.values(e.models).filter((function(e){return e.syncable})).forEach((function(t){r.push([e.name,t])}))})),o=r.map((function(e){var r=tt(e,2),o=r[0],s=r[1];return Qe(p,void 0,void 0,(function(){var e,r,a,u,c,f,l,d,h;return et(this,(function(p){switch(p.label){case 0:return[4,this.getModelMetadata(o,s.name)];case 1:return e=p.sent(),r=ae.a.getPredicates(this.syncPredicates.get(s),!1),a=r?JSON.stringify(r):null,void 0!==e?[3,3]:[4,this.storage.save(this.modelInstanceCreator(n,{model:s.name,namespace:o,lastSync:null,fullSyncInterval:t,lastFullSync:null,lastSyncPredicate:a}),void 0,st)];case 2:return f=tt.apply(void 0,[p.sent(),1]),l=tt(f[0],1),i=l[0],[3,5];case 3:return u=e.lastSyncPredicate?e.lastSyncPredicate:null,c=u!==a,[4,this.storage.save(this.modelClasses.ModelMetadata.copyOf(e,(function(e){e.fullSyncInterval=t,c&&(e.lastSync=null,e.lastFullSync=null,e.lastSyncPredicate=a)})))];case 4:d=tt.apply(void 0,[p.sent(),1]),h=tt(d[0],1),i=h[0],p.label=5;case 5:return[2,i]}}))}))})),s={},v.label=1;case 1:return v.trys.push([1,6,7,8]),[4,Promise.all(o)];case 2:a=nt.apply(void 0,[v.sent()]),u=a.next(),v.label=3;case 3:if(u.done)return[3,5];c=u.value,f=c.model,s[f]=c,v.label=4;case 4:return u=a.next(),[3,3];case 5:return[3,8];case 6:return l=v.sent(),d={error:l},[3,8];case 7:try{u&&!u.done&&(h=a.return)&&h.call(a)}finally{if(d)throw d.error}return[7];case 8:return[2,s]}}))}))},e.prototype.getModelsMetadata=function(){return Qe(this,void 0,void 0,(function(){var e;return et(this,(function(t){switch(t.label){case 0:return e=this.modelClasses.ModelMetadata,[4,this.storage.query(e)];case 1:return[2,t.sent()]}}))}))},e.prototype.getModelMetadata=function(e,t){return Qe(this,void 0,void 0,(function(){var n,r,i;return et(this,(function(o){switch(o.label){case 0:return n=this.modelClasses.ModelMetadata,r=ae.a.createFromExisting(this.schema.namespaces[he.c].models[n.name],(function(n){return n.namespace("eq",e).model("eq",t)})),[4,this.storage.query(n,r,{page:0,limit:1})];case 1:return i=tt.apply(void 0,[o.sent(),1]),[2,i[0]]}}))}))},e.prototype.getModelDefinition=function(e){var t=this.namespaceResolver(e);return this.schema.namespaces[t].models[e.name]},e.getNamespace=function(){return{name:he.c,relationships:{},enums:{OperationType:{name:"OperationType",values:["CREATE","UPDATE","DELETE"]}},nonModels:{},models:{MutationEvent:{name:"MutationEvent",pluralName:"MutationEvents",syncable:!1,fields:{id:{name:"id",type:"ID",isRequired:!0,isArray:!1},model:{name:"model",type:"String",isRequired:!0,isArray:!1},data:{name:"data",type:"String",isRequired:!0,isArray:!1},modelId:{name:"modelId",type:"String",isRequired:!0,isArray:!1},operation:{name:"operation",type:{enum:"Operationtype"},isArray:!1,isRequired:!0},condition:{name:"condition",type:"String",isArray:!1,isRequired:!0}}},ModelMetadata:{name:"ModelMetadata",pluralName:"ModelsMetadata",syncable:!1,fields:{id:{name:"id",type:"ID",isRequired:!0,isArray:!1},namespace:{name:"namespace",type:"String",isRequired:!0,isArray:!1},model:{name:"model",type:"String",isRequired:!0,isArray:!1},lastSync:{name:"lastSync",type:"Int",isRequired:!1,isArray:!1},lastFullSync:{name:"lastFullSync",type:"Int",isRequired:!1,isArray:!1},fullSyncInterval:{name:"fullSyncInterval",type:"Int",isRequired:!0,isArray:!1}}}}}},e}();var ut=function(){return(ut=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},ct=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},ft=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},lt=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]])}return n},dt=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s};oe(!0),q();var ht,pt,vt,gt,mt,bt=new r.a("DataStore"),yt=(Object(he.v)(Date.now()),i.a.browserOrNode().isNode),wt=new WeakMap,_t=new WeakMap,St=function(e){var t=wt.get(e);return ht.namespaces[t].models[e.name]},Et=function(e){return Object(he.s)(e)&&wt.has(e)},Mt=function(e){return wt.get(e)},At=new WeakSet;function It(e,t){return At.add(t),new e(t)}var kt;function Ot(e){return"string"==typeof e}function xt(e){var t=e.localModel,n=e.modelConstructor,r=e.remoteModel._version;return It(n,ut(ut({},t),{_version:r}))}function Ct(e){bt.warn(e)}function Tt(e,t){var n;switch(e){case he.a:n=pt[t];break;case he.d:n=vt[t];break;case he.c:n=gt[t];break;case he.b:n=mt[t];break;default:Object(he.f)(e)}if(Et(n))return n;var r="Model name is not valid for namespace. modelName: "+t+", namespace: "+e;throw bt.error(r),new Error(r)}function Pt(e,t){return ct(this,void 0,void 0,(function(){var n,r,i=this;return ft(this,(function(o){switch(o.label){case 0:return n=pt.Setting,r=ht.namespaces[he.a].models.Setting,[4,e.runExclusive((function(e){return ct(i,void 0,void 0,(function(){var i,o;return ft(this,(function(s){switch(s.label){case 0:return[4,e.query(n,ae.a.createFromExisting(r,(function(e){return e.key("eq","schemaVersion")})),{page:0,limit:1})];case 1:return i=dt.apply(void 0,[s.sent(),1]),void 0===(o=i[0])||void 0===o.value?[3,4]:JSON.parse(o.value)===t?[3,3]:[4,e.clear(!1)];case 2:s.sent(),s.label=3;case 3:return[3,6];case 4:return[4,e.save(It(n,{key:"schemaVersion",value:JSON.stringify(t)}))];case 5:s.sent(),s.label=6;case 6:return[2]}}))}))}))];case 1:return o.sent(),[2]}}))}))}var Nt=new(function(){function e(){var e=this;this.amplifyConfig={},this.syncPredicates=new WeakMap,this.start=function(){return ct(e,void 0,void 0,(function(){var e,t,n,r=this;return ft(this,(function(i){switch(i.label){case 0:return void 0!==this.initialized?[3,1]:(bt.debug("Starting DataStore"),this.initialized=new Promise((function(e,t){r.initResolve=e,r.initReject=t})),[3,3]);case 1:return[4,this.initialized];case 2:return i.sent(),[2];case 3:return this.storage=new Se(ht,Mt,Tt,It,void 0,this.sessionId),[4,this.storage.init()];case 4:return i.sent(),[4,Pt(this.storage,ht.version)];case 5:return i.sent(),(e=this.amplifyConfig.aws_appsync_graphqlEndpoint)?(bt.debug("GraphQL endpoint available",e),t=this,[4,this.processSyncExpressions()]):[3,7];case 6:return t.syncPredicates=i.sent(),this.sync=new at(ht,Mt,gt,vt,this.storage,It,this.maxRecordsToSync,this.syncPageSize,this.conflictHandler,this.errorHandler,this.syncPredicates,this.amplifyConfig),n=1e3*this.fullSyncInterval*60,kt=this.sync.start({fullSyncInterval:n}).subscribe({next:function(e){var t=e.type,n=e.data;t===(yt?re.SYNC_ENGINE_SYNC_QUERIES_READY:re.SYNC_ENGINE_STORAGE_SUBSCRIBED)&&r.initResolve(),o.a.dispatch("datastore",{event:t,data:n})},error:function(e){bt.warn("Sync error",e),r.initReject()}}),[3,8];case 7:bt.warn("Data won't be synchronized. No GraphQL endpoint configured. Did you forget `Amplify.configure(awsconfig)`?",{config:this.amplifyConfig}),this.initResolve(),i.label=8;case 8:return[4,this.initialized];case 9:return i.sent(),[2]}}))}))},this.query=function(t,n,r){return ct(e,void 0,void 0,(function(){var e,i,o,s,a;return ft(this,(function(u){switch(u.label){case 0:return[4,this.start()];case 1:if(u.sent(),!Et(t))throw e="Constructor is not for a valid model",bt.error(e,{modelConstructor:t}),new Error(e);return"string"==typeof n&&void 0!==r&&bt.warn("Pagination is ignored when querying by id"),i=St(t),o=Ot(n)?ae.a.createForId(i,n):Object(ae.c)(n)?void 0:ae.a.createFromExisting(i,n),s=this.processPagination(i,r),bt.debug("params ready",{modelConstructor:t,predicate:ae.a.getPredicates(o,!1),pagination:ut(ut({},s),{sort:ue.a.getPredicates(s.sort,!1)})}),[4,this.storage.query(t,o,s)];case 2:return a=u.sent(),[2,Ot(n)?a[0]:a]}}))}))},this.save=function(t,n){return ct(e,void 0,void 0,(function(){var e,r,i,o,s,a,u=this;return ft(this,(function(c){switch(c.label){case 0:return[4,this.start()];case 1:if(c.sent(),e=_t.get(t),r=t?t.constructor:void 0,!Et(r))throw i="Object is not an instance of a valid model",bt.error(i,{model:t}),new Error(i);return o=St(r),s=ae.a.createFromExisting(o,n),[4,this.storage.runExclusive((function(n){return ct(u,void 0,void 0,(function(){return ft(this,(function(i){switch(i.label){case 0:return[4,n.save(t,s,void 0,e)];case 1:return i.sent(),[2,n.query(r,ae.a.createForId(o,t.id))]}}))}))}))];case 2:return a=dt.apply(void 0,[c.sent(),1]),[2,a[0]]}}))}))},this.setConflictHandler=function(t){var n=t.DataStore;return n?n.conflictHandler:e.conflictHandler===xt&&t.conflictHandler?t.conflictHandler:e.conflictHandler||xt},this.setErrorHandler=function(t){var n=t.DataStore;return n?n.errorHandler:e.errorHandler===Ct&&t.errorHandler?t.errorHandler:e.errorHandler||Ct},this.delete=function(t,n){return ct(e,void 0,void 0,(function(){var e,r,i,o,s,a,u,c,f;return ft(this,(function(l){switch(l.label){case 0:return[4,this.start()];case 1:if(l.sent(),!t)throw u="Model or Model Constructor required",bt.error(u,{modelOrConstructor:t}),new Error(u);if(!Et(t))return[3,3];if(o=t,!n)throw u="Id to delete or criteria required. Do you want to delete all? Pass Predicates.ALL",bt.error(u,{idOrCriteria:n}),new Error(u);if("string"==typeof n)e=ae.a.createForId(St(o),n);else if(!(e=ae.a.createFromExisting(St(o),n))||!ae.a.isValidPredicate(e))throw u="Criteria required. Do you want to delete all? Pass Predicates.ALL",bt.error(u,{condition:e}),new Error(u);return[4,this.storage.delete(o,e)];case 2:return r=dt.apply(void 0,[l.sent(),1]),[2,r[0]];case 3:if(i=t,o=Object.getPrototypeOf(i||{}).constructor,!Et(o))throw u="Object is not an instance of a valid model",bt.error(u,{model:i}),new Error(u);if(s=St(o),a=ae.a.createForId(s,i.id),n){if("function"!=typeof n)throw u="Invalid criteria",bt.error(u,{idOrCriteria:n}),new Error(u);e=n(a)}else e=a;return[4,this.storage.delete(i,e)];case 4:return c=dt.apply(void 0,[l.sent(),1]),f=dt(c[0],1),[2,f[0]]}}))}))},this.observe=function(t,n){var r,i=t&&Et(t)?t:void 0;if(t&&void 0===i){var o=t,s=o&&Object.getPrototypeOf(o).constructor;if(Et(s))return n&&bt.warn("idOrCriteria is ignored when using a model instance",{model:o,idOrCriteria:n}),e.observe(s,o.id);var a="The model is not an instance of a PersistentModelConstructor";throw bt.error(a,{model:o}),new Error(a)}if(void 0!==n&&void 0===i){a="Cannot provide criteria without a modelConstructor";throw bt.error(a,n),new Error(a)}if(i&&!Et(i)){a="Constructor is not for a valid model";throw bt.error(a,{modelConstructor:i}),new Error(a)}return r="string"==typeof n?ae.a.createForId(St(i),n):i&&ae.a.createFromExisting(St(i),n),new se.a((function(t){var n;return ct(e,void 0,void 0,(function(){return ft(this,(function(e){switch(e.label){case 0:return[4,this.start()];case 1:return e.sent(),n=this.storage.observe(i,r).filter((function(e){var t=e.model;return Mt(t)===he.d})).subscribe(t),[2]}}))})),function(){n&&n.unsubscribe()}}))},this.configure=function(t){void 0===t&&(t={});var n=t.DataStore,r=(t.conflictHandler,t.errorHandler,t.maxRecordsToSync),i=t.syncPageSize,o=t.fullSyncInterval,s=t.syncExpressions,a=lt(t,["DataStore","conflictHandler","errorHandler","maxRecordsToSync","syncPageSize","fullSyncInterval","syncExpressions"]);e.amplifyConfig=ut(ut({},a),e.amplifyConfig),e.conflictHandler=e.setConflictHandler(t),e.errorHandler=e.setErrorHandler(t),e.syncExpressions=n&&n.syncExpressions||e.syncExpressions||s,e.maxRecordsToSync=n&&n.maxRecordsToSync||e.maxRecordsToSync||r,e.syncPageSize=n&&n.syncPageSize||e.syncPageSize||i,e.fullSyncInterval=n&&n.fullSyncInterval||e.fullSyncInterval||o||1440,e.sessionId=e.retrieveSessionId()},this.clear=function(){return ct(this,void 0,void 0,(function(){return ft(this,(function(e){switch(e.label){case 0:return void 0===this.storage?[2]:(kt&&!kt.closed&&kt.unsubscribe(),[4,this.storage.clear()]);case 1:return e.sent(),this.sync&&this.sync.unsubscribeConnectivity(),this.initialized=void 0,this.storage=void 0,this.sync=void 0,this.syncPredicates=new WeakMap,[2]}}))}))},this.stop=function(){return ct(this,void 0,void 0,(function(){return ft(this,(function(e){switch(e.label){case 0:return void 0===this.initialized?[3,2]:[4,this.start()];case 1:e.sent(),e.label=2;case 2:return kt&&!kt.closed&&kt.unsubscribe(),this.sync&&this.sync.unsubscribeConnectivity(),this.initialized=void 0,this.sync=void 0,[2]}}))}))}}return e.prototype.getModuleName=function(){return"DataStore"},e.prototype.processPagination=function(e,t){var n,r=t||{},i=r.limit,o=r.page,s=r.sort;if(void 0!==o&&void 0===i)throw new Error("Limit is required when requesting a page");if(void 0!==o){if("number"!=typeof o)throw new Error("Page should be a number");if(o<0)throw new Error("Page can't be negative")}if(void 0!==i){if("number"!=typeof i)throw new Error("Limit should be a number");if(i<0)throw new Error("Limit can't be negative")}return s&&(n=ue.a.createFromExisting(e,t.sort)),{limit:i,page:o,sort:n}},e.prototype.processSyncExpressions=function(){return ct(this,void 0,void 0,(function(){var e,t=this;return ft(this,(function(n){switch(n.label){case 0:return this.syncExpressions&&this.syncExpressions.length?[4,Promise.all(this.syncExpressions.map((function(e){return ct(t,void 0,void 0,(function(){var t,n,r,i,o,s;return ft(this,(function(a){switch(a.label){case 0:return[4,e];case 1:return t=a.sent(),n=t.modelConstructor,r=t.conditionProducer,i=St(n),[4,this.unwrapPromise(r)];case 2:return o=a.sent(),Object(ae.c)(o)?[2,[i,null]]:(s=this.createFromCondition(i,o),[2,[i,s]])}}))}))})))]:[2,new WeakMap];case 1:return e=n.sent(),[2,this.weakMapFromEntries(e)]}}))}))},e.prototype.createFromCondition=function(e,t){try{return ae.a.createFromExisting(e,t)}catch(e){throw bt.error("Error creating Sync Predicate"),e}},e.prototype.unwrapPromise=function(e){return ct(this,void 0,void 0,(function(){var t;return ft(this,(function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,e()];case 1:return[2,n.sent()];case 2:if((t=n.sent())instanceof TypeError)return[2,e];throw t;case 3:return[2]}}))}))},e.prototype.weakMapFromEntries=function(e){return e.reduce((function(e,t){var n=dt(t,2),r=n[0],i=n[1];if(e.has(r)){var o=r.name;return bt.warn("You can only utilize one Sync Expression per model.\n Subsequent sync expressions for the "+o+" model will be ignored."),e}return i&&e.set(r,i),e}),new WeakMap)},e.prototype.retrieveSessionId=function(){try{var e=sessionStorage.getItem("datastoreSessionId");if(e){var t=this.amplifyConfig.aws_appsync_graphqlEndpoint.split("/")[2];return e+"-"+dt(t.split("."),1)[0]}}catch(e){return}},e}());s.a.register(Nt)},,,,,,,,,,,function(e,t,n){"use strict";t.byteLength=function(e){var t=c(e),n=t[0],r=t[1];return 3*(n+r)/4-r},t.toByteArray=function(e){var t,n,r=c(e),s=r[0],a=r[1],u=new o(function(e,t,n){return 3*(t+n)/4-n}(0,s,a)),f=0,l=a>0?s-4:s;for(n=0;n<l;n+=4)t=i[e.charCodeAt(n)]<<18|i[e.charCodeAt(n+1)]<<12|i[e.charCodeAt(n+2)]<<6|i[e.charCodeAt(n+3)],u[f++]=t>>16&255,u[f++]=t>>8&255,u[f++]=255&t;2===a&&(t=i[e.charCodeAt(n)]<<2|i[e.charCodeAt(n+1)]>>4,u[f++]=255&t);1===a&&(t=i[e.charCodeAt(n)]<<10|i[e.charCodeAt(n+1)]<<4|i[e.charCodeAt(n+2)]>>2,u[f++]=t>>8&255,u[f++]=255&t);return u},t.fromByteArray=function(e){for(var t,n=e.length,i=n%3,o=[],s=0,a=n-i;s<a;s+=16383)o.push(f(e,s,s+16383>a?a:s+16383));1===i?(t=e[n-1],o.push(r[t>>2]+r[t<<4&63]+"==")):2===i&&(t=(e[n-2]<<8)+e[n-1],o.push(r[t>>10]+r[t>>4&63]+r[t<<2&63]+"="));return o.join("")};for(var r=[],i=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0,u=s.length;a<u;++a)r[a]=s[a],i[s.charCodeAt(a)]=a;function c(e){var t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function f(e,t,n){for(var i,o,s=[],a=t;a<n;a+=3)i=(e[a]<<16&16711680)+(e[a+1]<<8&65280)+(255&e[a+2]),s.push(r[(o=i)>>18&63]+r[o>>12&63]+r[o>>6&63]+r[63&o]);return s.join("")}i["-".charCodeAt(0)]=62,i["_".charCodeAt(0)]=63},function(e,t){ -/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */ -t.read=function(e,t,n,r,i){var o,s,a=8*i-r-1,u=(1<<a)-1,c=u>>1,f=-7,l=n?i-1:0,d=n?-1:1,h=e[t+l];for(l+=d,o=h&(1<<-f)-1,h>>=-f,f+=a;f>0;o=256*o+e[t+l],l+=d,f-=8);for(s=o&(1<<-f)-1,o>>=-f,f+=r;f>0;s=256*s+e[t+l],l+=d,f-=8);if(0===o)o=1-c;else{if(o===u)return s?NaN:1/0*(h?-1:1);s+=Math.pow(2,r),o-=c}return(h?-1:1)*s*Math.pow(2,o-r)},t.write=function(e,t,n,r,i,o){var s,a,u,c=8*o-i-1,f=(1<<c)-1,l=f>>1,d=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,h=r?0:o-1,p=r?1:-1,v=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,s=f):(s=Math.floor(Math.log(t)/Math.LN2),t*(u=Math.pow(2,-s))<1&&(s--,u*=2),(t+=s+l>=1?d/u:d*Math.pow(2,1-l))*u>=2&&(s++,u/=2),s+l>=f?(a=0,s=f):s+l>=1?(a=(t*u-1)*Math.pow(2,i),s+=l):(a=t*Math.pow(2,l-1)*Math.pow(2,i),s=0));i>=8;e[n+h]=255&a,h+=p,a/=256,i-=8);for(s=s<<i|a,c+=i;c>0;e[n+h]=255&s,h+=p,s/=256,c-=8);e[n+h-p]|=128*v}},function(e,t,n){var r,i,o,s;e.exports=(r=n(32),o=(i=r).lib.Base,s=i.enc.Utf8,void(i.algo.HMAC=o.extend({init:function(e,t){e=this._hasher=new e.init,"string"==typeof t&&(t=s.parse(t));var n=e.blockSize,r=4*n;t.sigBytes>r&&(t=e.finalize(t)),t.clamp();for(var i=this._oKey=t.clone(),o=this._iKey=t.clone(),a=i.words,u=o.words,c=0;c<n;c++)a[c]^=1549556828,u[c]^=909522486;i.sigBytes=o.sigBytes=r,this.reset()},reset:function(){var e=this._hasher;e.reset(),e.update(this._iKey)},update:function(e){return this._hasher.update(e),this},finalize:function(e){var t=this._hasher,n=t.finalize(e);return t.reset(),t.finalize(this._oKey.clone().concat(n))}})))},function(e,t,n){"use strict";t.randomBytes=t.rng=t.pseudoRandomBytes=t.prng=n(66),t.createHash=t.Hash=n(79),t.createHmac=t.Hmac=n(175);var r=n(297),i=Object.keys(r),o=["sha1","sha224","sha256","sha384","sha512","md5","rmd160"].concat(i);t.getHashes=function(){return o};var s=n(178);t.pbkdf2=s.pbkdf2,t.pbkdf2Sync=s.pbkdf2Sync;var a=n(299);t.Cipher=a.Cipher,t.createCipher=a.createCipher,t.Cipheriv=a.Cipheriv,t.createCipheriv=a.createCipheriv,t.Decipher=a.Decipher,t.createDecipher=a.createDecipher,t.Decipheriv=a.Decipheriv,t.createDecipheriv=a.createDecipheriv,t.getCiphers=a.getCiphers,t.listCiphers=a.listCiphers;var u=n(314);t.DiffieHellmanGroup=u.DiffieHellmanGroup,t.createDiffieHellmanGroup=u.createDiffieHellmanGroup,t.getDiffieHellman=u.getDiffieHellman,t.createDiffieHellman=u.createDiffieHellman,t.DiffieHellman=u.DiffieHellman;var c=n(319);t.createSign=c.createSign,t.Sign=c.Sign,t.createVerify=c.createVerify,t.Verify=c.Verify,t.createECDH=n(360);var f=n(361);t.publicEncrypt=f.publicEncrypt,t.privateEncrypt=f.privateEncrypt,t.publicDecrypt=f.publicDecrypt,t.privateDecrypt=f.privateDecrypt;var l=n(364);t.randomFill=l.randomFill,t.randomFillSync=l.randomFillSync,t.createCredentials=function(){throw new Error(["sorry, createCredentials is not implemented yet","we accept pull requests","https://github.com/crypto-browserify/crypto-browserify"].join("\n"))},t.constants={DH_CHECK_P_NOT_SAFE_PRIME:2,DH_CHECK_P_NOT_PRIME:1,DH_UNABLE_TO_CHECK_GENERATOR:4,DH_NOT_SUITABLE_GENERATOR:8,NPN_ENABLED:1,ALPN_ENABLED:1,RSA_PKCS1_PADDING:1,RSA_SSLV23_PADDING:2,RSA_NO_PADDING:3,RSA_PKCS1_OAEP_PADDING:4,RSA_X931_PADDING:5,RSA_PKCS1_PSS_PADDING:6,POINT_CONVERSION_COMPRESSED:2,POINT_CONVERSION_UNCOMPRESSED:4,POINT_CONVERSION_HYBRID:6}},function(e,t,n){(t=e.exports=n(163)).Stream=t,t.Readable=t,t.Writable=n(167),t.Duplex=n(68),t.Transform=n(168),t.PassThrough=n(279),t.finished=n(115),t.pipeline=n(280)},function(e,t){},function(e,t,n){"use strict";function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}var s=n(6).Buffer,a=n(276).inspect,u=a&&a.custom||"inspect";e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}var t,n,c;return t=e,(n=[{key:"push",value:function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length}},{key:"unshift",value:function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length}},{key:"shift",value:function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}}},{key:"clear",value:function(){this.head=this.tail=null,this.length=0}},{key:"join",value:function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n}},{key:"concat",value:function(e){if(0===this.length)return s.alloc(0);for(var t,n,r,i=s.allocUnsafe(e>>>0),o=this.head,a=0;o;)t=o.data,n=i,r=a,s.prototype.copy.call(t,n,r),a+=o.data.length,o=o.next;return i}},{key:"consume",value:function(e,t){var n;return e<this.head.data.length?(n=this.head.data.slice(0,e),this.head.data=this.head.data.slice(e)):n=e===this.head.data.length?this.shift():t?this._getString(e):this._getBuffer(e),n}},{key:"first",value:function(){return this.head.data}},{key:"_getString",value:function(e){var t=this.head,n=1,r=t.data;for(e-=r.length;t=t.next;){var i=t.data,o=e>i.length?i.length:e;if(o===i.length?r+=i:r+=i.slice(0,e),0==(e-=o)){o===i.length?(++n,t.next?this.head=t.next:this.head=this.tail=null):(this.head=t,t.data=i.slice(o));break}++n}return this.length-=n,r}},{key:"_getBuffer",value:function(e){var t=s.allocUnsafe(e),n=this.head,r=1;for(n.data.copy(t),e-=n.data.length;n=n.next;){var i=n.data,o=e>i.length?i.length:e;if(i.copy(t,t.length-e,0,o),0==(e-=o)){o===i.length?(++r,n.next?this.head=n.next:this.head=this.tail=null):(this.head=n,n.data=i.slice(o));break}++r}return this.length-=r,t}},{key:u,value:function(e,t){return a(this,function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}({},t,{depth:0,customInspect:!1}))}}])&&o(t.prototype,n),c&&o(t,c),e}()},function(e,t){},function(e,t,n){"use strict";(function(t){var r;function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var o=n(115),s=Symbol("lastResolve"),a=Symbol("lastReject"),u=Symbol("error"),c=Symbol("ended"),f=Symbol("lastPromise"),l=Symbol("handlePromise"),d=Symbol("stream");function h(e,t){return{value:e,done:t}}function p(e){var t=e[s];if(null!==t){var n=e[d].read();null!==n&&(e[f]=null,e[s]=null,e[a]=null,t(h(n,!1)))}}function v(e){t.nextTick(p,e)}var g=Object.getPrototypeOf((function(){})),m=Object.setPrototypeOf((i(r={get stream(){return this[d]},next:function(){var e=this,n=this[u];if(null!==n)return Promise.reject(n);if(this[c])return Promise.resolve(h(void 0,!0));if(this[d].destroyed)return new Promise((function(n,r){t.nextTick((function(){e[u]?r(e[u]):n(h(void 0,!0))}))}));var r,i=this[f];if(i)r=new Promise(function(e,t){return function(n,r){e.then((function(){t[c]?n(h(void 0,!0)):t[l](n,r)}),r)}}(i,this));else{var o=this[d].read();if(null!==o)return Promise.resolve(h(o,!1));r=new Promise(this[l])}return this[f]=r,r}},Symbol.asyncIterator,(function(){return this})),i(r,"return",(function(){var e=this;return new Promise((function(t,n){e[d].destroy(null,(function(e){e?n(e):t(h(void 0,!0))}))}))})),r),g);e.exports=function(e){var t,n=Object.create(m,(i(t={},d,{value:e,writable:!0}),i(t,s,{value:null,writable:!0}),i(t,a,{value:null,writable:!0}),i(t,u,{value:null,writable:!0}),i(t,c,{value:e._readableState.endEmitted,writable:!0}),i(t,l,{value:function(e,t){var r=n[d].read();r?(n[f]=null,n[s]=null,n[a]=null,e(h(r,!1))):(n[s]=e,n[a]=t)},writable:!0}),t));return n[f]=null,o(e,(function(e){if(e&&"ERR_STREAM_PREMATURE_CLOSE"!==e.code){var t=n[a];return null!==t&&(n[f]=null,n[s]=null,n[a]=null,t(e)),void(n[u]=e)}var r=n[s];null!==r&&(n[f]=null,n[s]=null,n[a]=null,r(h(void 0,!0))),n[c]=!0})),e.on("readable",v.bind(null,n)),n}}).call(this,n(20))},function(e,t){e.exports=function(){throw new Error("Readable.from is not available in the browser")}},function(e,t,n){"use strict";e.exports=i;var r=n(168);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}n(7)(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){"use strict";var r;var i=n(67).codes,o=i.ERR_MISSING_ARGS,s=i.ERR_STREAM_DESTROYED;function a(e){if(e)throw e}function u(e,t,i,o){o=function(e){var t=!1;return function(){t||(t=!0,e.apply(void 0,arguments))}}(o);var a=!1;e.on("close",(function(){a=!0})),void 0===r&&(r=n(115)),r(e,{readable:t,writable:i},(function(e){if(e)return o(e);a=!0,o()}));var u=!1;return function(t){if(!a&&!u)return u=!0,function(e){return e.setHeader&&"function"==typeof e.abort}(e)?e.abort():"function"==typeof e.destroy?e.destroy():void o(t||new s("pipe"))}}function c(e){e()}function f(e,t){return e.pipe(t)}function l(e){return e.length?"function"!=typeof e[e.length-1]?a:e.pop():a}e.exports=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];var r,i=l(t);if(Array.isArray(t[0])&&(t=t[0]),t.length<2)throw new o("streams");var s=t.map((function(e,n){var o=n<t.length-1;return u(e,o,n>0,(function(e){r||(r=e),e&&s.forEach(c),o||(s.forEach(c),i(r))}))}));return t.reduce(f)}},function(e,t,n){var r=n(7),i=n(69),o=n(8).Buffer,s=[1518500249,1859775393,-1894007588,-899497514],a=new Array(80);function u(){this.init(),this._w=a,i.call(this,64,56)}function c(e){return e<<30|e>>>2}function f(e,t,n,r){return 0===e?t&n|~t&r:2===e?t&n|t&r|n&r:t^n^r}r(u,i),u.prototype.init=function(){return this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520,this},u.prototype._update=function(e){for(var t,n=this._w,r=0|this._a,i=0|this._b,o=0|this._c,a=0|this._d,u=0|this._e,l=0;l<16;++l)n[l]=e.readInt32BE(4*l);for(;l<80;++l)n[l]=n[l-3]^n[l-8]^n[l-14]^n[l-16];for(var d=0;d<80;++d){var h=~~(d/20),p=0|((t=r)<<5|t>>>27)+f(h,i,o,a)+u+n[d]+s[h];u=a,a=o,o=c(i),i=r,r=p}this._a=r+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=a+this._d|0,this._e=u+this._e|0},u.prototype._hash=function(){var e=o.allocUnsafe(20);return e.writeInt32BE(0|this._a,0),e.writeInt32BE(0|this._b,4),e.writeInt32BE(0|this._c,8),e.writeInt32BE(0|this._d,12),e.writeInt32BE(0|this._e,16),e},e.exports=u},function(e,t,n){var r=n(7),i=n(69),o=n(8).Buffer,s=[1518500249,1859775393,-1894007588,-899497514],a=new Array(80);function u(){this.init(),this._w=a,i.call(this,64,56)}function c(e){return e<<5|e>>>27}function f(e){return e<<30|e>>>2}function l(e,t,n,r){return 0===e?t&n|~t&r:2===e?t&n|t&r|n&r:t^n^r}r(u,i),u.prototype.init=function(){return this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520,this},u.prototype._update=function(e){for(var t,n=this._w,r=0|this._a,i=0|this._b,o=0|this._c,a=0|this._d,u=0|this._e,d=0;d<16;++d)n[d]=e.readInt32BE(4*d);for(;d<80;++d)n[d]=(t=n[d-3]^n[d-8]^n[d-14]^n[d-16])<<1|t>>>31;for(var h=0;h<80;++h){var p=~~(h/20),v=c(r)+l(p,i,o,a)+u+n[h]+s[p]|0;u=a,a=o,o=f(i),i=r,r=v}this._a=r+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=a+this._d|0,this._e=u+this._e|0},u.prototype._hash=function(){var e=o.allocUnsafe(20);return e.writeInt32BE(0|this._a,0),e.writeInt32BE(0|this._b,4),e.writeInt32BE(0|this._c,8),e.writeInt32BE(0|this._d,12),e.writeInt32BE(0|this._e,16),e},e.exports=u},function(e,t,n){var r=n(7),i=n(169),o=n(69),s=n(8).Buffer,a=new Array(64);function u(){this.init(),this._w=a,o.call(this,64,56)}r(u,i),u.prototype.init=function(){return this._a=3238371032,this._b=914150663,this._c=812702999,this._d=4144912697,this._e=4290775857,this._f=1750603025,this._g=1694076839,this._h=3204075428,this},u.prototype._hash=function(){var e=s.allocUnsafe(28);return e.writeInt32BE(this._a,0),e.writeInt32BE(this._b,4),e.writeInt32BE(this._c,8),e.writeInt32BE(this._d,12),e.writeInt32BE(this._e,16),e.writeInt32BE(this._f,20),e.writeInt32BE(this._g,24),e},e.exports=u},function(e,t,n){var r=n(7),i=n(170),o=n(69),s=n(8).Buffer,a=new Array(160);function u(){this.init(),this._w=a,o.call(this,128,112)}r(u,i),u.prototype.init=function(){return this._ah=3418070365,this._bh=1654270250,this._ch=2438529370,this._dh=355462360,this._eh=1731405415,this._fh=2394180231,this._gh=3675008525,this._hh=1203062813,this._al=3238371032,this._bl=914150663,this._cl=812702999,this._dl=4144912697,this._el=4290775857,this._fl=1750603025,this._gl=1694076839,this._hl=3204075428,this},u.prototype._hash=function(){var e=s.allocUnsafe(48);function t(t,n,r){e.writeInt32BE(t,r),e.writeInt32BE(n,r+4)}return t(this._ah,this._al,0),t(this._bh,this._bl,8),t(this._ch,this._cl,16),t(this._dh,this._dl,24),t(this._eh,this._el,32),t(this._fh,this._fl,40),e},e.exports=u},function(e,t,n){e.exports=i;var r=n(49).EventEmitter;function i(){r.call(this)}n(7)(i,r),i.Readable=n(118),i.Writable=n(292),i.Duplex=n(293),i.Transform=n(294),i.PassThrough=n(295),i.Stream=i,i.prototype.pipe=function(e,t){var n=this;function i(t){e.writable&&!1===e.write(t)&&n.pause&&n.pause()}function o(){n.readable&&n.resume&&n.resume()}n.on("data",i),e.on("drain",o),e._isStdio||t&&!1===t.end||(n.on("end",a),n.on("close",u));var s=!1;function a(){s||(s=!0,e.end())}function u(){s||(s=!0,"function"==typeof e.destroy&&e.destroy())}function c(e){if(f(),0===r.listenerCount(this,"error"))throw e}function f(){n.removeListener("data",i),e.removeListener("drain",o),n.removeListener("end",a),n.removeListener("close",u),n.removeListener("error",c),e.removeListener("error",c),n.removeListener("end",f),n.removeListener("close",f),e.removeListener("close",f)}return n.on("error",c),e.on("error",c),n.on("end",f),n.on("close",f),e.on("close",f),e.emit("pipe",n),e}},function(e,t){},function(e,t,n){"use strict";var r=n(119).Buffer,i=n(288);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,i,o=r.allocUnsafe(e>>>0),s=this.head,a=0;s;)t=s.data,n=o,i=a,t.copy(n,i),a+=s.data.length,s=s.next;return o},e}(),i&&i.inspect&&i.inspect.custom&&(e.exports.prototype[i.inspect.custom]=function(){var e=i.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,i=Function.prototype.apply;function o(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new o(i.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new o(i.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},o.prototype.unref=o.prototype.ref=function(){},o.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},n(290),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(31))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,i,o,s,a,u=1,c={},f=!1,l=e.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(e);d=d&&d.setTimeout?d:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick((function(){p(e)}))}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((o=new MessageChannel).port1.onmessage=function(e){p(e.data)},r=function(e){o.port2.postMessage(e)}):l&&"onreadystatechange"in l.createElement("script")?(i=l.documentElement,r=function(e){var t=l.createElement("script");t.onreadystatechange=function(){p(e),t.onreadystatechange=null,i.removeChild(t),t=null},i.appendChild(t)}):r=function(e){setTimeout(p,0,e)}:(s="setImmediate$"+Math.random()+"$",a=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(s)&&p(+t.data.slice(s.length))},e.addEventListener?e.addEventListener("message",a,!1):e.attachEvent("onmessage",a),r=function(t){e.postMessage(s+t,"*")}),d.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n<t.length;n++)t[n]=arguments[n+1];var i={callback:e,args:t};return c[u]=i,r(u),u++},d.clearImmediate=h}function h(e){delete c[e]}function p(e){if(f)setTimeout(p,0,e);else{var t=c[e];if(t){f=!0;try{!function(e){var t=e.callback,n=e.args;switch(n.length){case 0:t();break;case 1:t(n[0]);break;case 2:t(n[0],n[1]);break;case 3:t(n[0],n[1],n[2]);break;default:t.apply(void 0,n)}}(t)}finally{h(e),f=!1}}}}}("undefined"==typeof self?void 0===e?this:e:self)}).call(this,n(31),n(20))},function(e,t,n){"use strict";e.exports=o;var r=n(174),i=Object.create(n(80));function o(e){if(!(this instanceof o))return new o(e);r.call(this,e)}i.inherits=n(7),i.inherits(o,r),o.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){e.exports=n(120)},function(e,t,n){e.exports=n(60)},function(e,t,n){e.exports=n(118).Transform},function(e,t,n){e.exports=n(118).PassThrough},function(e,t,n){"use strict";var r=n(7),i=n(8).Buffer,o=n(56),s=i.alloc(128);function a(e,t){o.call(this,"digest"),"string"==typeof t&&(t=i.from(t)),this._alg=e,this._key=t,t.length>64?t=e(t):t.length<64&&(t=i.concat([t,s],64));for(var n=this._ipad=i.allocUnsafe(64),r=this._opad=i.allocUnsafe(64),a=0;a<64;a++)n[a]=54^t[a],r[a]=92^t[a];this._hash=[n]}r(a,o),a.prototype._update=function(e){this._hash.push(e)},a.prototype._final=function(){var e=this._alg(i.concat(this._hash));return this._alg(i.concat([this._opad,e]))},e.exports=a},function(e,t,n){e.exports=n(177)},function(e,t,n){(function(t,r){var i,o=n(8).Buffer,s=n(179),a=n(180),u=n(181),c=n(182),f=t.crypto&&t.crypto.subtle,l={sha:"SHA-1","sha-1":"SHA-1",sha1:"SHA-1",sha256:"SHA-256","sha-256":"SHA-256",sha384:"SHA-384","sha-384":"SHA-384","sha-512":"SHA-512",sha512:"SHA-512"},d=[];function h(e,t,n,r,i){return f.importKey("raw",e,{name:"PBKDF2"},!1,["deriveBits"]).then((function(e){return f.deriveBits({name:"PBKDF2",salt:t,iterations:n,hash:{name:i}},e,r<<3)})).then((function(e){return o.from(e)}))}e.exports=function(e,n,p,v,g,m){"function"==typeof g&&(m=g,g=void 0);var b=l[(g=g||"sha1").toLowerCase()];if(!b||"function"!=typeof t.Promise)return r.nextTick((function(){var t;try{t=u(e,n,p,v,g)}catch(e){return m(e)}m(null,t)}));if(s(p,v),e=c(e,a,"Password"),n=c(n,a,"Salt"),"function"!=typeof m)throw new Error("No callback provided to pbkdf2");!function(e,t){e.then((function(e){r.nextTick((function(){t(null,e)}))}),(function(e){r.nextTick((function(){t(e)}))}))}(function(e){if(t.process&&!t.process.browser)return Promise.resolve(!1);if(!f||!f.importKey||!f.deriveBits)return Promise.resolve(!1);if(void 0!==d[e])return d[e];var n=h(i=i||o.alloc(8),i,10,128,e).then((function(){return!0})).catch((function(){return!1}));return d[e]=n,n}(b).then((function(t){return t?h(e,n,p,v,b):u(e,n,p,v,g)})),m)}}).call(this,n(31),n(20))},function(e,t,n){var r=n(300),i=n(122),o=n(123),s=n(313),a=n(94);function u(e,t,n){if(e=e.toLowerCase(),o[e])return i.createCipheriv(e,t,n);if(s[e])return new r({key:t,iv:n,mode:e});throw new TypeError("invalid suite type")}function c(e,t,n){if(e=e.toLowerCase(),o[e])return i.createDecipheriv(e,t,n);if(s[e])return new r({key:t,iv:n,mode:e,decrypt:!0});throw new TypeError("invalid suite type")}t.createCipher=t.Cipher=function(e,t){var n,r;if(e=e.toLowerCase(),o[e])n=o[e].key,r=o[e].iv;else{if(!s[e])throw new TypeError("invalid suite type");n=8*s[e].key,r=s[e].iv}var i=a(t,!1,n,r);return u(e,i.key,i.iv)},t.createCipheriv=t.Cipheriv=u,t.createDecipher=t.Decipher=function(e,t){var n,r;if(e=e.toLowerCase(),o[e])n=o[e].key,r=o[e].iv;else{if(!s[e])throw new TypeError("invalid suite type");n=8*s[e].key,r=s[e].iv}var i=a(t,!1,n,r);return c(e,i.key,i.iv)},t.createDecipheriv=t.Decipheriv=c,t.listCiphers=t.getCiphers=function(){return Object.keys(s).concat(i.getCiphers())}},function(e,t,n){var r=n(56),i=n(301),o=n(7),s=n(8).Buffer,a={"des-ede3-cbc":i.CBC.instantiate(i.EDE),"des-ede3":i.EDE,"des-ede-cbc":i.CBC.instantiate(i.EDE),"des-ede":i.EDE,"des-cbc":i.CBC.instantiate(i.DES),"des-ecb":i.DES};function u(e){r.call(this);var t,n=e.mode.toLowerCase(),i=a[n];t=e.decrypt?"decrypt":"encrypt";var o=e.key;s.isBuffer(o)||(o=s.from(o)),"des-ede"!==n&&"des-ede-cbc"!==n||(o=s.concat([o,o.slice(0,8)]));var u=e.iv;s.isBuffer(u)||(u=s.from(u)),this._des=i.create({key:o,iv:u,type:t})}a.des=a["des-cbc"],a.des3=a["des-ede3-cbc"],e.exports=u,o(u,r),u.prototype._update=function(e){return s.from(this._des.update(e))},u.prototype._final=function(){return s.from(this._des.final())}},function(e,t,n){"use strict";t.utils=n(183),t.Cipher=n(121),t.DES=n(184),t.CBC=n(302),t.EDE=n(303)},function(e,t,n){"use strict";var r=n(46),i=n(7),o={};function s(e){r.equal(e.length,8,"Invalid IV length"),this.iv=new Array(8);for(var t=0;t<this.iv.length;t++)this.iv[t]=e[t]}t.instantiate=function(e){function t(t){e.call(this,t),this._cbcInit()}i(t,e);for(var n=Object.keys(o),r=0;r<n.length;r++){var s=n[r];t.prototype[s]=o[s]}return t.create=function(e){return new t(e)},t},o._cbcInit=function(){var e=new s(this.options.iv);this._cbcState=e},o._update=function(e,t,n,r){var i=this._cbcState,o=this.constructor.super_.prototype,s=i.iv;if("encrypt"===this.type){for(var a=0;a<this.blockSize;a++)s[a]^=e[t+a];o._update.call(this,s,0,n,r);for(a=0;a<this.blockSize;a++)s[a]=n[r+a]}else{o._update.call(this,e,t,n,r);for(a=0;a<this.blockSize;a++)n[r+a]^=s[a];for(a=0;a<this.blockSize;a++)s[a]=e[t+a]}}},function(e,t,n){"use strict";var r=n(46),i=n(7),o=n(121),s=n(184);function a(e,t){r.equal(t.length,24,"Invalid key length");var n=t.slice(0,8),i=t.slice(8,16),o=t.slice(16,24);this.ciphers="encrypt"===e?[s.create({type:"encrypt",key:n}),s.create({type:"decrypt",key:i}),s.create({type:"encrypt",key:o})]:[s.create({type:"decrypt",key:o}),s.create({type:"encrypt",key:i}),s.create({type:"decrypt",key:n})]}function u(e){o.call(this,e);var t=new a(this.type,this.options.key);this._edeState=t}i(u,o),e.exports=u,u.create=function(e){return new u(e)},u.prototype._update=function(e,t,n,r){var i=this._edeState;i.ciphers[0]._update(e,t,n,r),i.ciphers[1]._update(n,r,n,r),i.ciphers[2]._update(n,r,n,r)},u.prototype._pad=s.prototype._pad,u.prototype._unpad=s.prototype._unpad},function(e,t,n){var r=n(123),i=n(188),o=n(8).Buffer,s=n(189),a=n(56),u=n(93),c=n(94);function f(e,t,n){a.call(this),this._cache=new d,this._cipher=new u.AES(t),this._prev=o.from(n),this._mode=e,this._autopadding=!0}n(7)(f,a),f.prototype._update=function(e){var t,n;this._cache.add(e);for(var r=[];t=this._cache.get();)n=this._mode.encrypt(this,t),r.push(n);return o.concat(r)};var l=o.alloc(16,16);function d(){this.cache=o.allocUnsafe(0)}function h(e,t,n){var a=r[e.toLowerCase()];if(!a)throw new TypeError("invalid suite type");if("string"==typeof t&&(t=o.from(t)),t.length!==a.key/8)throw new TypeError("invalid key length "+t.length);if("string"==typeof n&&(n=o.from(n)),"GCM"!==a.mode&&n.length!==a.iv)throw new TypeError("invalid iv length "+n.length);return"stream"===a.type?new s(a.module,t,n):"auth"===a.type?new i(a.module,t,n):new f(a.module,t,n)}f.prototype._final=function(){var e=this._cache.flush();if(this._autopadding)return e=this._mode.encrypt(this,e),this._cipher.scrub(),e;if(!e.equals(l))throw this._cipher.scrub(),new Error("data not multiple of block length")},f.prototype.setAutoPadding=function(e){return this._autopadding=!!e,this},d.prototype.add=function(e){this.cache=o.concat([this.cache,e])},d.prototype.get=function(){if(this.cache.length>15){var e=this.cache.slice(0,16);return this.cache=this.cache.slice(16),e}return null},d.prototype.flush=function(){for(var e=16-this.cache.length,t=o.allocUnsafe(e),n=-1;++n<e;)t.writeUInt8(e,n);return o.concat([this.cache,t])},t.createCipheriv=h,t.createCipher=function(e,t){var n=r[e.toLowerCase()];if(!n)throw new TypeError("invalid suite type");var i=c(t,!1,n.key,n.iv);return h(e,i.key,i.iv)}},function(e,t){t.encrypt=function(e,t){return e._cipher.encryptBlock(t)},t.decrypt=function(e,t){return e._cipher.decryptBlock(t)}},function(e,t,n){var r=n(81);t.encrypt=function(e,t){var n=r(t,e._prev);return e._prev=e._cipher.encryptBlock(n),e._prev},t.decrypt=function(e,t){var n=e._prev;e._prev=t;var i=e._cipher.decryptBlock(t);return r(i,n)}},function(e,t,n){var r=n(8).Buffer,i=n(81);function o(e,t,n){var o=t.length,s=i(t,e._cache);return e._cache=e._cache.slice(o),e._prev=r.concat([e._prev,n?t:s]),s}t.encrypt=function(e,t,n){for(var i,s=r.allocUnsafe(0);t.length;){if(0===e._cache.length&&(e._cache=e._cipher.encryptBlock(e._prev),e._prev=r.allocUnsafe(0)),!(e._cache.length<=t.length)){s=r.concat([s,o(e,t,n)]);break}i=e._cache.length,s=r.concat([s,o(e,t.slice(0,i),n)]),t=t.slice(i)}return s}},function(e,t,n){var r=n(8).Buffer;function i(e,t,n){var i=e._cipher.encryptBlock(e._prev)[0]^t;return e._prev=r.concat([e._prev.slice(1),r.from([n?t:i])]),i}t.encrypt=function(e,t,n){for(var o=t.length,s=r.allocUnsafe(o),a=-1;++a<o;)s[a]=i(e,t[a],n);return s}},function(e,t,n){var r=n(8).Buffer;function i(e,t,n){for(var r,i,s=-1,a=0;++s<8;)r=t&1<<7-s?128:0,a+=(128&(i=e._cipher.encryptBlock(e._prev)[0]^r))>>s%8,e._prev=o(e._prev,n?r:i);return a}function o(e,t){var n=e.length,i=-1,o=r.allocUnsafe(e.length);for(e=r.concat([e,r.from([t])]);++i<n;)o[i]=e[i]<<1|e[i+1]>>7;return o}t.encrypt=function(e,t,n){for(var o=t.length,s=r.allocUnsafe(o),a=-1;++a<o;)s[a]=i(e,t[a],n);return s}},function(e,t,n){(function(e){var r=n(81);function i(e){return e._prev=e._cipher.encryptBlock(e._prev),e._prev}t.encrypt=function(t,n){for(;t._cache.length<n.length;)t._cache=e.concat([t._cache,i(t)]);var o=t._cache.slice(0,n.length);return t._cache=t._cache.slice(n.length),r(n,o)}}).call(this,n(6).Buffer)},function(e,t,n){var r=n(8).Buffer,i=r.alloc(16,0);function o(e){var t=r.allocUnsafe(16);return t.writeUInt32BE(e[0]>>>0,0),t.writeUInt32BE(e[1]>>>0,4),t.writeUInt32BE(e[2]>>>0,8),t.writeUInt32BE(e[3]>>>0,12),t}function s(e){this.h=e,this.state=r.alloc(16,0),this.cache=r.allocUnsafe(0)}s.prototype.ghash=function(e){for(var t=-1;++t<e.length;)this.state[t]^=e[t];this._multiply()},s.prototype._multiply=function(){for(var e,t,n,r=[(e=this.h).readUInt32BE(0),e.readUInt32BE(4),e.readUInt32BE(8),e.readUInt32BE(12)],i=[0,0,0,0],s=-1;++s<128;){for(0!=(this.state[~~(s/8)]&1<<7-s%8)&&(i[0]^=r[0],i[1]^=r[1],i[2]^=r[2],i[3]^=r[3]),n=0!=(1&r[3]),t=3;t>0;t--)r[t]=r[t]>>>1|(1&r[t-1])<<31;r[0]=r[0]>>>1,n&&(r[0]=r[0]^225<<24)}this.state=o(i)},s.prototype.update=function(e){var t;for(this.cache=r.concat([this.cache,e]);this.cache.length>=16;)t=this.cache.slice(0,16),this.cache=this.cache.slice(16),this.ghash(t)},s.prototype.final=function(e,t){return this.cache.length&&this.ghash(r.concat([this.cache,i],16)),this.ghash(o([0,e,0,t])),this.state},e.exports=s},function(e,t,n){var r=n(188),i=n(8).Buffer,o=n(123),s=n(189),a=n(56),u=n(93),c=n(94);function f(e,t,n){a.call(this),this._cache=new l,this._last=void 0,this._cipher=new u.AES(t),this._prev=i.from(n),this._mode=e,this._autopadding=!0}function l(){this.cache=i.allocUnsafe(0)}function d(e,t,n){var a=o[e.toLowerCase()];if(!a)throw new TypeError("invalid suite type");if("string"==typeof n&&(n=i.from(n)),"GCM"!==a.mode&&n.length!==a.iv)throw new TypeError("invalid iv length "+n.length);if("string"==typeof t&&(t=i.from(t)),t.length!==a.key/8)throw new TypeError("invalid key length "+t.length);return"stream"===a.type?new s(a.module,t,n,!0):"auth"===a.type?new r(a.module,t,n,!0):new f(a.module,t,n)}n(7)(f,a),f.prototype._update=function(e){var t,n;this._cache.add(e);for(var r=[];t=this._cache.get(this._autopadding);)n=this._mode.decrypt(this,t),r.push(n);return i.concat(r)},f.prototype._final=function(){var e=this._cache.flush();if(this._autopadding)return function(e){var t=e[15];if(t<1||t>16)throw new Error("unable to decrypt data");var n=-1;for(;++n<t;)if(e[n+(16-t)]!==t)throw new Error("unable to decrypt data");if(16===t)return;return e.slice(0,16-t)}(this._mode.decrypt(this,e));if(e)throw new Error("data not multiple of block length")},f.prototype.setAutoPadding=function(e){return this._autopadding=!!e,this},l.prototype.add=function(e){this.cache=i.concat([this.cache,e])},l.prototype.get=function(e){var t;if(e){if(this.cache.length>16)return t=this.cache.slice(0,16),this.cache=this.cache.slice(16),t}else if(this.cache.length>=16)return t=this.cache.slice(0,16),this.cache=this.cache.slice(16),t;return null},l.prototype.flush=function(){if(this.cache.length)return this.cache},t.createDecipher=function(e,t){var n=o[e.toLowerCase()];if(!n)throw new TypeError("invalid suite type");var r=c(t,!1,n.key,n.iv);return d(e,r.key,r.iv)},t.createDecipheriv=d},function(e,t){t["des-ecb"]={key:8,iv:0},t["des-cbc"]=t.des={key:8,iv:8},t["des-ede3-cbc"]=t.des3={key:24,iv:8},t["des-ede3"]={key:24,iv:0},t["des-ede-cbc"]={key:16,iv:8},t["des-ede"]={key:16,iv:0}},function(e,t,n){(function(e){var r=n(190),i=n(317),o=n(318);var s={binary:!0,hex:!0,base64:!0};t.DiffieHellmanGroup=t.createDiffieHellmanGroup=t.getDiffieHellman=function(t){var n=new e(i[t].prime,"hex"),r=new e(i[t].gen,"hex");return new o(n,r)},t.createDiffieHellman=t.DiffieHellman=function t(n,i,a,u){return e.isBuffer(i)||void 0===s[i]?t(n,"binary",i,a):(i=i||"binary",u=u||"binary",a=a||new e([2]),e.isBuffer(a)||(a=new e(a,u)),"number"==typeof n?new o(r(n,a),a,!0):(e.isBuffer(n)||(n=new e(n,i)),new o(n,a,!0)))}}).call(this,n(6).Buffer)},function(e,t){},function(e,t){},function(e){e.exports=JSON.parse('{"modp1":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a63a3620ffffffffffffffff"},"modp2":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff"},"modp5":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff"},"modp14":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff"},"modp15":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a93ad2caffffffffffffffff"},"modp16":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c934063199ffffffffffffffff"},"modp17":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dcc4024ffffffffffffffff"},"modp18":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dbe115974a3926f12fee5e438777cb6a932df8cd8bec4d073b931ba3bc832b68d9dd300741fa7bf8afc47ed2576f6936ba424663aab639c5ae4f5683423b4742bf1c978238f16cbe39d652de3fdb8befc848ad922222e04a4037c0713eb57a81a23f0c73473fc646cea306b4bcbc8862f8385ddfa9d4b7fa2c087e879683303ed5bdd3a062b3cf5b3a278a66d2a13f83f44f82ddf310ee074ab6a364597e899a0255dc164f31cc50846851df9ab48195ded7ea1b1d510bd7ee74d73faf36bc31ecfa268359046f4eb879f924009438b481c6cd7889a002ed5ee382bc9190da6fc026e479558e4475677e9aa9e3050e2765694dfc81f56e880b96e7160c980dd98edd3dfffffffffffffffff"}}')},function(e,t,n){(function(t){var r=n(29),i=new(n(191)),o=new r(24),s=new r(11),a=new r(10),u=new r(3),c=new r(7),f=n(190),l=n(66);function d(e,n){return n=n||"utf8",t.isBuffer(e)||(e=new t(e,n)),this._pub=new r(e),this}function h(e,n){return n=n||"utf8",t.isBuffer(e)||(e=new t(e,n)),this._priv=new r(e),this}e.exports=v;var p={};function v(e,t,n){this.setGenerator(t),this.__prime=new r(e),this._prime=r.mont(this.__prime),this._primeLen=e.length,this._pub=void 0,this._priv=void 0,this._primeCode=void 0,n?(this.setPublicKey=d,this.setPrivateKey=h):this._primeCode=8}function g(e,n){var r=new t(e.toArray());return n?r.toString(n):r}Object.defineProperty(v.prototype,"verifyError",{enumerable:!0,get:function(){return"number"!=typeof this._primeCode&&(this._primeCode=function(e,t){var n=t.toString("hex"),r=[n,e.toString(16)].join("_");if(r in p)return p[r];var l,d=0;if(e.isEven()||!f.simpleSieve||!f.fermatTest(e)||!i.test(e))return d+=1,d+="02"===n||"05"===n?8:4,p[r]=d,d;switch(i.test(e.shrn(1))||(d+=2),n){case"02":e.mod(o).cmp(s)&&(d+=8);break;case"05":(l=e.mod(a)).cmp(u)&&l.cmp(c)&&(d+=8);break;default:d+=4}return p[r]=d,d}(this.__prime,this.__gen)),this._primeCode}}),v.prototype.generateKeys=function(){return this._priv||(this._priv=new r(l(this._primeLen))),this._pub=this._gen.toRed(this._prime).redPow(this._priv).fromRed(),this.getPublicKey()},v.prototype.computeSecret=function(e){var n=(e=(e=new r(e)).toRed(this._prime)).redPow(this._priv).fromRed(),i=new t(n.toArray()),o=this.getPrime();if(i.length<o.length){var s=new t(o.length-i.length);s.fill(0),i=t.concat([s,i])}return i},v.prototype.getPublicKey=function(e){return g(this._pub,e)},v.prototype.getPrivateKey=function(e){return g(this._priv,e)},v.prototype.getPrime=function(e){return g(this.__prime,e)},v.prototype.getGenerator=function(e){return g(this._gen,e)},v.prototype.setGenerator=function(e,n){return n=n||"utf8",t.isBuffer(e)||(e=new t(e,n)),this.__gen=e,this._gen=new r(e),this}}).call(this,n(6).Buffer)},function(e,t,n){var r=n(8).Buffer,i=n(79),o=n(320),s=n(7),a=n(328),u=n(359),c=n(177);function f(e){o.Writable.call(this);var t=c[e];if(!t)throw new Error("Unknown message digest");this._hashType=t.hash,this._hash=i(t.hash),this._tag=t.id,this._signType=t.sign}function l(e){o.Writable.call(this);var t=c[e];if(!t)throw new Error("Unknown message digest");this._hash=i(t.hash),this._tag=t.id,this._signType=t.sign}function d(e){return new f(e)}function h(e){return new l(e)}Object.keys(c).forEach((function(e){c[e].id=r.from(c[e].id,"hex"),c[e.toLowerCase()]=c[e]})),s(f,o.Writable),f.prototype._write=function(e,t,n){this._hash.update(e),n()},f.prototype.update=function(e,t){return"string"==typeof e&&(e=r.from(e,t)),this._hash.update(e),this},f.prototype.sign=function(e,t){this.end();var n=this._hash.digest(),r=a(n,e,this._hashType,this._signType,this._tag);return t?r.toString(t):r},s(l,o.Writable),l.prototype._write=function(e,t,n){this._hash.update(e),n()},l.prototype.update=function(e,t){return"string"==typeof e&&(e=r.from(e,t)),this._hash.update(e),this},l.prototype.verify=function(e,t,n){"string"==typeof t&&(t=r.from(t,n)),this.end();var i=this._hash.digest();return u(t,i,e,this._signType,this._tag)},e.exports={Sign:d,Verify:h,createSign:d,createVerify:h}},function(e,t,n){(t=e.exports=n(192)).Stream=t,t.Readable=t,t.Writable=n(196),t.Duplex=n(71),t.Transform=n(197),t.PassThrough=n(326),t.finished=n(125),t.pipeline=n(327)},function(e,t){},function(e,t,n){"use strict";function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}var s=n(6).Buffer,a=n(323).inspect,u=a&&a.custom||"inspect";e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}var t,n,c;return t=e,(n=[{key:"push",value:function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length}},{key:"unshift",value:function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length}},{key:"shift",value:function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}}},{key:"clear",value:function(){this.head=this.tail=null,this.length=0}},{key:"join",value:function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n}},{key:"concat",value:function(e){if(0===this.length)return s.alloc(0);for(var t,n,r,i=s.allocUnsafe(e>>>0),o=this.head,a=0;o;)t=o.data,n=i,r=a,s.prototype.copy.call(t,n,r),a+=o.data.length,o=o.next;return i}},{key:"consume",value:function(e,t){var n;return e<this.head.data.length?(n=this.head.data.slice(0,e),this.head.data=this.head.data.slice(e)):n=e===this.head.data.length?this.shift():t?this._getString(e):this._getBuffer(e),n}},{key:"first",value:function(){return this.head.data}},{key:"_getString",value:function(e){var t=this.head,n=1,r=t.data;for(e-=r.length;t=t.next;){var i=t.data,o=e>i.length?i.length:e;if(o===i.length?r+=i:r+=i.slice(0,e),0==(e-=o)){o===i.length?(++n,t.next?this.head=t.next:this.head=this.tail=null):(this.head=t,t.data=i.slice(o));break}++n}return this.length-=n,r}},{key:"_getBuffer",value:function(e){var t=s.allocUnsafe(e),n=this.head,r=1;for(n.data.copy(t),e-=n.data.length;n=n.next;){var i=n.data,o=e>i.length?i.length:e;if(i.copy(t,t.length-e,0,o),0==(e-=o)){o===i.length?(++r,n.next?this.head=n.next:this.head=this.tail=null):(this.head=n,n.data=i.slice(o));break}++r}return this.length-=r,t}},{key:u,value:function(e,t){return a(this,function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}({},t,{depth:0,customInspect:!1}))}}])&&o(t.prototype,n),c&&o(t,c),e}()},function(e,t){},function(e,t,n){"use strict";(function(t){var r;function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var o=n(125),s=Symbol("lastResolve"),a=Symbol("lastReject"),u=Symbol("error"),c=Symbol("ended"),f=Symbol("lastPromise"),l=Symbol("handlePromise"),d=Symbol("stream");function h(e,t){return{value:e,done:t}}function p(e){var t=e[s];if(null!==t){var n=e[d].read();null!==n&&(e[f]=null,e[s]=null,e[a]=null,t(h(n,!1)))}}function v(e){t.nextTick(p,e)}var g=Object.getPrototypeOf((function(){})),m=Object.setPrototypeOf((i(r={get stream(){return this[d]},next:function(){var e=this,n=this[u];if(null!==n)return Promise.reject(n);if(this[c])return Promise.resolve(h(void 0,!0));if(this[d].destroyed)return new Promise((function(n,r){t.nextTick((function(){e[u]?r(e[u]):n(h(void 0,!0))}))}));var r,i=this[f];if(i)r=new Promise(function(e,t){return function(n,r){e.then((function(){t[c]?n(h(void 0,!0)):t[l](n,r)}),r)}}(i,this));else{var o=this[d].read();if(null!==o)return Promise.resolve(h(o,!1));r=new Promise(this[l])}return this[f]=r,r}},Symbol.asyncIterator,(function(){return this})),i(r,"return",(function(){var e=this;return new Promise((function(t,n){e[d].destroy(null,(function(e){e?n(e):t(h(void 0,!0))}))}))})),r),g);e.exports=function(e){var t,n=Object.create(m,(i(t={},d,{value:e,writable:!0}),i(t,s,{value:null,writable:!0}),i(t,a,{value:null,writable:!0}),i(t,u,{value:null,writable:!0}),i(t,c,{value:e._readableState.endEmitted,writable:!0}),i(t,l,{value:function(e,t){var r=n[d].read();r?(n[f]=null,n[s]=null,n[a]=null,e(h(r,!1))):(n[s]=e,n[a]=t)},writable:!0}),t));return n[f]=null,o(e,(function(e){if(e&&"ERR_STREAM_PREMATURE_CLOSE"!==e.code){var t=n[a];return null!==t&&(n[f]=null,n[s]=null,n[a]=null,t(e)),void(n[u]=e)}var r=n[s];null!==r&&(n[f]=null,n[s]=null,n[a]=null,r(h(void 0,!0))),n[c]=!0})),e.on("readable",v.bind(null,n)),n}}).call(this,n(20))},function(e,t){e.exports=function(){throw new Error("Readable.from is not available in the browser")}},function(e,t,n){"use strict";e.exports=i;var r=n(197);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}n(7)(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){"use strict";var r;var i=n(70).codes,o=i.ERR_MISSING_ARGS,s=i.ERR_STREAM_DESTROYED;function a(e){if(e)throw e}function u(e,t,i,o){o=function(e){var t=!1;return function(){t||(t=!0,e.apply(void 0,arguments))}}(o);var a=!1;e.on("close",(function(){a=!0})),void 0===r&&(r=n(125)),r(e,{readable:t,writable:i},(function(e){if(e)return o(e);a=!0,o()}));var u=!1;return function(t){if(!a&&!u)return u=!0,function(e){return e.setHeader&&"function"==typeof e.abort}(e)?e.abort():"function"==typeof e.destroy?e.destroy():void o(t||new s("pipe"))}}function c(e){e()}function f(e,t){return e.pipe(t)}function l(e){return e.length?"function"!=typeof e[e.length-1]?a:e.pop():a}e.exports=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];var r,i=l(t);if(Array.isArray(t[0])&&(t=t[0]),t.length<2)throw new o("streams");var s=t.map((function(e,n){var o=n<t.length-1;return u(e,o,n>0,(function(e){r||(r=e),e&&s.forEach(c),o||(s.forEach(c),i(r))}))}));return t.reduce(f)}},function(e,t,n){var r=n(8).Buffer,i=n(175),o=n(126),s=n(127).ec,a=n(203),u=n(96),c=n(209);function f(e,t,n,o){if((e=r.from(e.toArray())).length<t.byteLength()){var s=r.alloc(t.byteLength()-e.length);e=r.concat([s,e])}var a=n.length,u=function(e,t){e=(e=l(e,t)).mod(t);var n=r.from(e.toArray());if(n.length<t.byteLength()){var i=r.alloc(t.byteLength()-n.length);n=r.concat([i,n])}return n}(n,t),c=r.alloc(a);c.fill(1);var f=r.alloc(a);return f=i(o,f).update(c).update(r.from([0])).update(e).update(u).digest(),c=i(o,f).update(c).digest(),{k:f=i(o,f).update(c).update(r.from([1])).update(e).update(u).digest(),v:c=i(o,f).update(c).digest()}}function l(e,t){var n=new a(e),r=(e.length<<3)-t.bitLength();return r>0&&n.ishrn(r),n}function d(e,t,n){var o,s;do{for(o=r.alloc(0);8*o.length<e.bitLength();)t.v=i(n,t.k).update(t.v).digest(),o=r.concat([o,t.v]);s=l(o,e),t.k=i(n,t.k).update(t.v).update(r.from([0])).digest(),t.v=i(n,t.k).update(t.v).digest()}while(-1!==s.cmp(e));return s}function h(e,t,n,r){return e.toRed(a.mont(n)).redPow(t).fromRed().mod(r)}e.exports=function(e,t,n,i,p){var v=u(t);if(v.curve){if("ecdsa"!==i&&"ecdsa/rsa"!==i)throw new Error("wrong private key type");return function(e,t){var n=c[t.curve.join(".")];if(!n)throw new Error("unknown curve "+t.curve.join("."));var i=new s(n).keyFromPrivate(t.privateKey).sign(e);return r.from(i.toDER())}(e,v)}if("dsa"===v.type){if("dsa"!==i)throw new Error("wrong private key type");return function(e,t,n){var i,o=t.params.priv_key,s=t.params.p,u=t.params.q,c=t.params.g,p=new a(0),v=l(e,u).mod(u),g=!1,m=f(o,u,e,n);for(;!1===g;)i=d(u,m,n),p=h(c,i,s,u),0===(g=i.invm(u).imul(v.add(o.mul(p))).mod(u)).cmpn(0)&&(g=!1,p=new a(0));return function(e,t){e=e.toArray(),t=t.toArray(),128&e[0]&&(e=[0].concat(e));128&t[0]&&(t=[0].concat(t));var n=[48,e.length+t.length+4,2,e.length];return n=n.concat(e,[2,t.length],t),r.from(n)}(p,g)}(e,v,n)}if("rsa"!==i&&"ecdsa/rsa"!==i)throw new Error("wrong private key type");e=r.concat([p,e]);for(var g=v.modulus.byteLength(),m=[0,1];e.length+m.length+1<g;)m.push(255);m.push(0);for(var b=-1;++b<e.length;)m.push(e[b]);return o(m,v)},e.exports.getKey=f,e.exports.makeKey=d},function(e,t,n){(function(e){!function(e,t){"use strict";function r(e,t){if(!e)throw new Error(t||"Assertion failed")}function i(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}function o(e,t,n){if(o.isBN(e))return e;this.negative=0,this.words=null,this.length=0,this.red=null,null!==e&&("le"!==t&&"be"!==t||(n=t,t=10),this._init(e||0,t||10,n||"be"))}var s;"object"==typeof e?e.exports=o:t.BN=o,o.BN=o,o.wordSize=26;try{s=n(330).Buffer}catch(e){}function a(e,t,n){for(var i=0,o=Math.min(e.length,n),s=0,a=t;a<o;a++){var u,c=e.charCodeAt(a)-48;i<<=4,i|=u=c>=49&&c<=54?c-49+10:c>=17&&c<=22?c-17+10:c,s|=u}return r(!(240&s),"Invalid character in "+e),i}function u(e,t,n,i){for(var o=0,s=0,a=Math.min(e.length,n),u=t;u<a;u++){var c=e.charCodeAt(u)-48;o*=i,s=c>=49?c-49+10:c>=17?c-17+10:c,r(c>=0&&s<i,"Invalid character"),o+=s}return o}function c(e,t){e.words=t.words,e.length=t.length,e.negative=t.negative,e.red=t.red}if(o.isBN=function(e){return e instanceof o||null!==e&&"object"==typeof e&&e.constructor.wordSize===o.wordSize&&Array.isArray(e.words)},o.max=function(e,t){return e.cmp(t)>0?e:t},o.min=function(e,t){return e.cmp(t)<0?e:t},o.prototype._init=function(e,t,n){if("number"==typeof e)return this._initNumber(e,t,n);if("object"==typeof e)return this._initArray(e,t,n);"hex"===t&&(t=16),r(t===(0|t)&&t>=2&&t<=36);var i=0;"-"===(e=e.toString().replace(/\s+/g,""))[0]&&i++,16===t?this._parseHex(e,i):this._parseBase(e,t,i),"-"===e[0]&&(this.negative=1),this._strip(),"le"===n&&this._initArray(this.toArray(),t,n)},o.prototype._initNumber=function(e,t,n){e<0&&(this.negative=1,e=-e),e<67108864?(this.words=[67108863&e],this.length=1):e<4503599627370496?(this.words=[67108863&e,e/67108864&67108863],this.length=2):(r(e<9007199254740992),this.words=[67108863&e,e/67108864&67108863,1],this.length=3),"le"===n&&this._initArray(this.toArray(),t,n)},o.prototype._initArray=function(e,t,n){if(r("number"==typeof e.length),e.length<=0)return this.words=[0],this.length=1,this;this.length=Math.ceil(e.length/3),this.words=new Array(this.length);for(var i=0;i<this.length;i++)this.words[i]=0;var o,s,a=0;if("be"===n)for(i=e.length-1,o=0;i>=0;i-=3)s=e[i]|e[i-1]<<8|e[i-2]<<16,this.words[o]|=s<<a&67108863,this.words[o+1]=s>>>26-a&67108863,(a+=24)>=26&&(a-=26,o++);else if("le"===n)for(i=0,o=0;i<e.length;i+=3)s=e[i]|e[i+1]<<8|e[i+2]<<16,this.words[o]|=s<<a&67108863,this.words[o+1]=s>>>26-a&67108863,(a+=24)>=26&&(a-=26,o++);return this._strip()},o.prototype._parseHex=function(e,t){this.length=Math.ceil((e.length-t)/6),this.words=new Array(this.length);for(var n=0;n<this.length;n++)this.words[n]=0;var r,i,o=0;for(n=e.length-6,r=0;n>=t;n-=6)i=a(e,n,n+6),this.words[r]|=i<<o&67108863,this.words[r+1]|=i>>>26-o&4194303,(o+=24)>=26&&(o-=26,r++);n+6!==t&&(i=a(e,t,n+6),this.words[r]|=i<<o&67108863,this.words[r+1]|=i>>>26-o&4194303),this._strip()},o.prototype._parseBase=function(e,t,n){this.words=[0],this.length=1;for(var r=0,i=1;i<=67108863;i*=t)r++;r--,i=i/t|0;for(var o=e.length-n,s=o%r,a=Math.min(o,o-s)+n,c=0,f=n;f<a;f+=r)c=u(e,f,f+r,t),this.imuln(i),this.words[0]+c<67108864?this.words[0]+=c:this._iaddn(c);if(0!==s){var l=1;for(c=u(e,f,e.length,t),f=0;f<s;f++)l*=t;this.imuln(l),this.words[0]+c<67108864?this.words[0]+=c:this._iaddn(c)}},o.prototype.copy=function(e){e.words=new Array(this.length);for(var t=0;t<this.length;t++)e.words[t]=this.words[t];e.length=this.length,e.negative=this.negative,e.red=this.red},o.prototype._move=function(e){c(e,this)},o.prototype.clone=function(){var e=new o(null);return this.copy(e),e},o.prototype._expand=function(e){for(;this.length<e;)this.words[this.length++]=0;return this},o.prototype._strip=function(){for(;this.length>1&&0===this.words[this.length-1];)this.length--;return this._normSign()},o.prototype._normSign=function(){return 1===this.length&&0===this.words[0]&&(this.negative=0),this},"undefined"!=typeof Symbol&&"function"==typeof Symbol.for)try{o.prototype[Symbol.for("nodejs.util.inspect.custom")]=f}catch(e){o.prototype.inspect=f}else o.prototype.inspect=f;function f(){return(this.red?"<BN-R: ":"<BN: ")+this.toString(16)+">"}var l=["","0","00","000","0000","00000","000000","0000000","00000000","000000000","0000000000","00000000000","000000000000","0000000000000","00000000000000","000000000000000","0000000000000000","00000000000000000","000000000000000000","0000000000000000000","00000000000000000000","000000000000000000000","0000000000000000000000","00000000000000000000000","000000000000000000000000","0000000000000000000000000"],d=[0,0,25,16,12,11,10,9,8,8,7,7,7,7,6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5],h=[0,0,33554432,43046721,16777216,48828125,60466176,40353607,16777216,43046721,1e7,19487171,35831808,62748517,7529536,11390625,16777216,24137569,34012224,47045881,64e6,4084101,5153632,6436343,7962624,9765625,11881376,14348907,17210368,20511149,243e5,28629151,33554432,39135393,45435424,52521875,60466176];o.prototype.toString=function(e,t){var n;if(t=0|t||1,16===(e=e||10)||"hex"===e){n="";for(var i=0,o=0,s=0;s<this.length;s++){var a=this.words[s],u=(16777215&(a<<i|o)).toString(16);n=0!==(o=a>>>24-i&16777215)||s!==this.length-1?l[6-u.length]+u+n:u+n,(i+=2)>=26&&(i-=26,s--)}for(0!==o&&(n=o.toString(16)+n);n.length%t!=0;)n="0"+n;return 0!==this.negative&&(n="-"+n),n}if(e===(0|e)&&e>=2&&e<=36){var c=d[e],f=h[e];n="";var p=this.clone();for(p.negative=0;!p.isZero();){var v=p.modrn(f).toString(e);n=(p=p.idivn(f)).isZero()?v+n:l[c-v.length]+v+n}for(this.isZero()&&(n="0"+n);n.length%t!=0;)n="0"+n;return 0!==this.negative&&(n="-"+n),n}r(!1,"Base should be between 2 and 36")},o.prototype.toNumber=function(){var e=this.words[0];return 2===this.length?e+=67108864*this.words[1]:3===this.length&&1===this.words[2]?e+=4503599627370496+67108864*this.words[1]:this.length>2&&r(!1,"Number can only safely store up to 53 bits"),0!==this.negative?-e:e},o.prototype.toJSON=function(){return this.toString(16,2)},s&&(o.prototype.toBuffer=function(e,t){return this.toArrayLike(s,e,t)}),o.prototype.toArray=function(e,t){return this.toArrayLike(Array,e,t)};function p(e,t,n){n.negative=t.negative^e.negative;var r=e.length+t.length|0;n.length=r,r=r-1|0;var i=0|e.words[0],o=0|t.words[0],s=i*o,a=67108863&s,u=s/67108864|0;n.words[0]=a;for(var c=1;c<r;c++){for(var f=u>>>26,l=67108863&u,d=Math.min(c,t.length-1),h=Math.max(0,c-e.length+1);h<=d;h++){var p=c-h|0;f+=(s=(i=0|e.words[p])*(o=0|t.words[h])+l)/67108864|0,l=67108863&s}n.words[c]=0|l,u=0|f}return 0!==u?n.words[c]=0|u:n.length--,n._strip()}o.prototype.toArrayLike=function(e,t,n){this._strip();var i=this.byteLength(),o=n||Math.max(1,i);r(i<=o,"byte array longer than desired length"),r(o>0,"Requested array length <= 0");var s=function(e,t){return e.allocUnsafe?e.allocUnsafe(t):new e(t)}(e,o);return this["_toArrayLike"+("le"===t?"LE":"BE")](s,i),s},o.prototype._toArrayLikeLE=function(e,t){for(var n=0,r=0,i=0,o=0;i<this.length;i++){var s=this.words[i]<<o|r;e[n++]=255&s,n<e.length&&(e[n++]=s>>8&255),n<e.length&&(e[n++]=s>>16&255),6===o?(n<e.length&&(e[n++]=s>>24&255),r=0,o=0):(r=s>>>24,o+=2)}if(n<e.length)for(e[n++]=r;n<e.length;)e[n++]=0},o.prototype._toArrayLikeBE=function(e,t){for(var n=e.length-1,r=0,i=0,o=0;i<this.length;i++){var s=this.words[i]<<o|r;e[n--]=255&s,n>=0&&(e[n--]=s>>8&255),n>=0&&(e[n--]=s>>16&255),6===o?(n>=0&&(e[n--]=s>>24&255),r=0,o=0):(r=s>>>24,o+=2)}if(n>=0)for(e[n--]=r;n>=0;)e[n--]=0},Math.clz32?o.prototype._countBits=function(e){return 32-Math.clz32(e)}:o.prototype._countBits=function(e){var t=e,n=0;return t>=4096&&(n+=13,t>>>=13),t>=64&&(n+=7,t>>>=7),t>=8&&(n+=4,t>>>=4),t>=2&&(n+=2,t>>>=2),n+t},o.prototype._zeroBits=function(e){if(0===e)return 26;var t=e,n=0;return 0==(8191&t)&&(n+=13,t>>>=13),0==(127&t)&&(n+=7,t>>>=7),0==(15&t)&&(n+=4,t>>>=4),0==(3&t)&&(n+=2,t>>>=2),0==(1&t)&&n++,n},o.prototype.bitLength=function(){var e=this.words[this.length-1],t=this._countBits(e);return 26*(this.length-1)+t},o.prototype.zeroBits=function(){if(this.isZero())return 0;for(var e=0,t=0;t<this.length;t++){var n=this._zeroBits(this.words[t]);if(e+=n,26!==n)break}return e},o.prototype.byteLength=function(){return Math.ceil(this.bitLength()/8)},o.prototype.toTwos=function(e){return 0!==this.negative?this.abs().inotn(e).iaddn(1):this.clone()},o.prototype.fromTwos=function(e){return this.testn(e-1)?this.notn(e).iaddn(1).ineg():this.clone()},o.prototype.isNeg=function(){return 0!==this.negative},o.prototype.neg=function(){return this.clone().ineg()},o.prototype.ineg=function(){return this.isZero()||(this.negative^=1),this},o.prototype.iuor=function(e){for(;this.length<e.length;)this.words[this.length++]=0;for(var t=0;t<e.length;t++)this.words[t]=this.words[t]|e.words[t];return this._strip()},o.prototype.ior=function(e){return r(0==(this.negative|e.negative)),this.iuor(e)},o.prototype.or=function(e){return this.length>e.length?this.clone().ior(e):e.clone().ior(this)},o.prototype.uor=function(e){return this.length>e.length?this.clone().iuor(e):e.clone().iuor(this)},o.prototype.iuand=function(e){var t;t=this.length>e.length?e:this;for(var n=0;n<t.length;n++)this.words[n]=this.words[n]&e.words[n];return this.length=t.length,this._strip()},o.prototype.iand=function(e){return r(0==(this.negative|e.negative)),this.iuand(e)},o.prototype.and=function(e){return this.length>e.length?this.clone().iand(e):e.clone().iand(this)},o.prototype.uand=function(e){return this.length>e.length?this.clone().iuand(e):e.clone().iuand(this)},o.prototype.iuxor=function(e){var t,n;this.length>e.length?(t=this,n=e):(t=e,n=this);for(var r=0;r<n.length;r++)this.words[r]=t.words[r]^n.words[r];if(this!==t)for(;r<t.length;r++)this.words[r]=t.words[r];return this.length=t.length,this._strip()},o.prototype.ixor=function(e){return r(0==(this.negative|e.negative)),this.iuxor(e)},o.prototype.xor=function(e){return this.length>e.length?this.clone().ixor(e):e.clone().ixor(this)},o.prototype.uxor=function(e){return this.length>e.length?this.clone().iuxor(e):e.clone().iuxor(this)},o.prototype.inotn=function(e){r("number"==typeof e&&e>=0);var t=0|Math.ceil(e/26),n=e%26;this._expand(t),n>0&&t--;for(var i=0;i<t;i++)this.words[i]=67108863&~this.words[i];return n>0&&(this.words[i]=~this.words[i]&67108863>>26-n),this._strip()},o.prototype.notn=function(e){return this.clone().inotn(e)},o.prototype.setn=function(e,t){r("number"==typeof e&&e>=0);var n=e/26|0,i=e%26;return this._expand(n+1),this.words[n]=t?this.words[n]|1<<i:this.words[n]&~(1<<i),this._strip()},o.prototype.iadd=function(e){var t,n,r;if(0!==this.negative&&0===e.negative)return this.negative=0,t=this.isub(e),this.negative^=1,this._normSign();if(0===this.negative&&0!==e.negative)return e.negative=0,t=this.isub(e),e.negative=1,t._normSign();this.length>e.length?(n=this,r=e):(n=e,r=this);for(var i=0,o=0;o<r.length;o++)t=(0|n.words[o])+(0|r.words[o])+i,this.words[o]=67108863&t,i=t>>>26;for(;0!==i&&o<n.length;o++)t=(0|n.words[o])+i,this.words[o]=67108863&t,i=t>>>26;if(this.length=n.length,0!==i)this.words[this.length]=i,this.length++;else if(n!==this)for(;o<n.length;o++)this.words[o]=n.words[o];return this},o.prototype.add=function(e){var t;return 0!==e.negative&&0===this.negative?(e.negative=0,t=this.sub(e),e.negative^=1,t):0===e.negative&&0!==this.negative?(this.negative=0,t=e.sub(this),this.negative=1,t):this.length>e.length?this.clone().iadd(e):e.clone().iadd(this)},o.prototype.isub=function(e){if(0!==e.negative){e.negative=0;var t=this.iadd(e);return e.negative=1,t._normSign()}if(0!==this.negative)return this.negative=0,this.iadd(e),this.negative=1,this._normSign();var n,r,i=this.cmp(e);if(0===i)return this.negative=0,this.length=1,this.words[0]=0,this;i>0?(n=this,r=e):(n=e,r=this);for(var o=0,s=0;s<r.length;s++)o=(t=(0|n.words[s])-(0|r.words[s])+o)>>26,this.words[s]=67108863&t;for(;0!==o&&s<n.length;s++)o=(t=(0|n.words[s])+o)>>26,this.words[s]=67108863&t;if(0===o&&s<n.length&&n!==this)for(;s<n.length;s++)this.words[s]=n.words[s];return this.length=Math.max(this.length,s),n!==this&&(this.negative=1),this._strip()},o.prototype.sub=function(e){return this.clone().isub(e)};var v=function(e,t,n){var r,i,o,s=e.words,a=t.words,u=n.words,c=0,f=0|s[0],l=8191&f,d=f>>>13,h=0|s[1],p=8191&h,v=h>>>13,g=0|s[2],m=8191&g,b=g>>>13,y=0|s[3],w=8191&y,_=y>>>13,S=0|s[4],E=8191&S,M=S>>>13,A=0|s[5],I=8191&A,k=A>>>13,O=0|s[6],x=8191&O,C=O>>>13,T=0|s[7],P=8191&T,N=T>>>13,R=0|s[8],L=8191&R,j=R>>>13,D=0|s[9],U=8191&D,B=D>>>13,F=0|a[0],z=8191&F,q=F>>>13,K=0|a[1],H=8191&K,V=K>>>13,G=0|a[2],W=8191&G,$=G>>>13,Y=0|a[3],J=8191&Y,Z=Y>>>13,X=0|a[4],Q=8191&X,ee=X>>>13,te=0|a[5],ne=8191&te,re=te>>>13,ie=0|a[6],oe=8191&ie,se=ie>>>13,ae=0|a[7],ue=8191&ae,ce=ae>>>13,fe=0|a[8],le=8191&fe,de=fe>>>13,he=0|a[9],pe=8191&he,ve=he>>>13;n.negative=e.negative^t.negative,n.length=19;var ge=(c+(r=Math.imul(l,z))|0)+((8191&(i=(i=Math.imul(l,q))+Math.imul(d,z)|0))<<13)|0;c=((o=Math.imul(d,q))+(i>>>13)|0)+(ge>>>26)|0,ge&=67108863,r=Math.imul(p,z),i=(i=Math.imul(p,q))+Math.imul(v,z)|0,o=Math.imul(v,q);var me=(c+(r=r+Math.imul(l,H)|0)|0)+((8191&(i=(i=i+Math.imul(l,V)|0)+Math.imul(d,H)|0))<<13)|0;c=((o=o+Math.imul(d,V)|0)+(i>>>13)|0)+(me>>>26)|0,me&=67108863,r=Math.imul(m,z),i=(i=Math.imul(m,q))+Math.imul(b,z)|0,o=Math.imul(b,q),r=r+Math.imul(p,H)|0,i=(i=i+Math.imul(p,V)|0)+Math.imul(v,H)|0,o=o+Math.imul(v,V)|0;var be=(c+(r=r+Math.imul(l,W)|0)|0)+((8191&(i=(i=i+Math.imul(l,$)|0)+Math.imul(d,W)|0))<<13)|0;c=((o=o+Math.imul(d,$)|0)+(i>>>13)|0)+(be>>>26)|0,be&=67108863,r=Math.imul(w,z),i=(i=Math.imul(w,q))+Math.imul(_,z)|0,o=Math.imul(_,q),r=r+Math.imul(m,H)|0,i=(i=i+Math.imul(m,V)|0)+Math.imul(b,H)|0,o=o+Math.imul(b,V)|0,r=r+Math.imul(p,W)|0,i=(i=i+Math.imul(p,$)|0)+Math.imul(v,W)|0,o=o+Math.imul(v,$)|0;var ye=(c+(r=r+Math.imul(l,J)|0)|0)+((8191&(i=(i=i+Math.imul(l,Z)|0)+Math.imul(d,J)|0))<<13)|0;c=((o=o+Math.imul(d,Z)|0)+(i>>>13)|0)+(ye>>>26)|0,ye&=67108863,r=Math.imul(E,z),i=(i=Math.imul(E,q))+Math.imul(M,z)|0,o=Math.imul(M,q),r=r+Math.imul(w,H)|0,i=(i=i+Math.imul(w,V)|0)+Math.imul(_,H)|0,o=o+Math.imul(_,V)|0,r=r+Math.imul(m,W)|0,i=(i=i+Math.imul(m,$)|0)+Math.imul(b,W)|0,o=o+Math.imul(b,$)|0,r=r+Math.imul(p,J)|0,i=(i=i+Math.imul(p,Z)|0)+Math.imul(v,J)|0,o=o+Math.imul(v,Z)|0;var we=(c+(r=r+Math.imul(l,Q)|0)|0)+((8191&(i=(i=i+Math.imul(l,ee)|0)+Math.imul(d,Q)|0))<<13)|0;c=((o=o+Math.imul(d,ee)|0)+(i>>>13)|0)+(we>>>26)|0,we&=67108863,r=Math.imul(I,z),i=(i=Math.imul(I,q))+Math.imul(k,z)|0,o=Math.imul(k,q),r=r+Math.imul(E,H)|0,i=(i=i+Math.imul(E,V)|0)+Math.imul(M,H)|0,o=o+Math.imul(M,V)|0,r=r+Math.imul(w,W)|0,i=(i=i+Math.imul(w,$)|0)+Math.imul(_,W)|0,o=o+Math.imul(_,$)|0,r=r+Math.imul(m,J)|0,i=(i=i+Math.imul(m,Z)|0)+Math.imul(b,J)|0,o=o+Math.imul(b,Z)|0,r=r+Math.imul(p,Q)|0,i=(i=i+Math.imul(p,ee)|0)+Math.imul(v,Q)|0,o=o+Math.imul(v,ee)|0;var _e=(c+(r=r+Math.imul(l,ne)|0)|0)+((8191&(i=(i=i+Math.imul(l,re)|0)+Math.imul(d,ne)|0))<<13)|0;c=((o=o+Math.imul(d,re)|0)+(i>>>13)|0)+(_e>>>26)|0,_e&=67108863,r=Math.imul(x,z),i=(i=Math.imul(x,q))+Math.imul(C,z)|0,o=Math.imul(C,q),r=r+Math.imul(I,H)|0,i=(i=i+Math.imul(I,V)|0)+Math.imul(k,H)|0,o=o+Math.imul(k,V)|0,r=r+Math.imul(E,W)|0,i=(i=i+Math.imul(E,$)|0)+Math.imul(M,W)|0,o=o+Math.imul(M,$)|0,r=r+Math.imul(w,J)|0,i=(i=i+Math.imul(w,Z)|0)+Math.imul(_,J)|0,o=o+Math.imul(_,Z)|0,r=r+Math.imul(m,Q)|0,i=(i=i+Math.imul(m,ee)|0)+Math.imul(b,Q)|0,o=o+Math.imul(b,ee)|0,r=r+Math.imul(p,ne)|0,i=(i=i+Math.imul(p,re)|0)+Math.imul(v,ne)|0,o=o+Math.imul(v,re)|0;var Se=(c+(r=r+Math.imul(l,oe)|0)|0)+((8191&(i=(i=i+Math.imul(l,se)|0)+Math.imul(d,oe)|0))<<13)|0;c=((o=o+Math.imul(d,se)|0)+(i>>>13)|0)+(Se>>>26)|0,Se&=67108863,r=Math.imul(P,z),i=(i=Math.imul(P,q))+Math.imul(N,z)|0,o=Math.imul(N,q),r=r+Math.imul(x,H)|0,i=(i=i+Math.imul(x,V)|0)+Math.imul(C,H)|0,o=o+Math.imul(C,V)|0,r=r+Math.imul(I,W)|0,i=(i=i+Math.imul(I,$)|0)+Math.imul(k,W)|0,o=o+Math.imul(k,$)|0,r=r+Math.imul(E,J)|0,i=(i=i+Math.imul(E,Z)|0)+Math.imul(M,J)|0,o=o+Math.imul(M,Z)|0,r=r+Math.imul(w,Q)|0,i=(i=i+Math.imul(w,ee)|0)+Math.imul(_,Q)|0,o=o+Math.imul(_,ee)|0,r=r+Math.imul(m,ne)|0,i=(i=i+Math.imul(m,re)|0)+Math.imul(b,ne)|0,o=o+Math.imul(b,re)|0,r=r+Math.imul(p,oe)|0,i=(i=i+Math.imul(p,se)|0)+Math.imul(v,oe)|0,o=o+Math.imul(v,se)|0;var Ee=(c+(r=r+Math.imul(l,ue)|0)|0)+((8191&(i=(i=i+Math.imul(l,ce)|0)+Math.imul(d,ue)|0))<<13)|0;c=((o=o+Math.imul(d,ce)|0)+(i>>>13)|0)+(Ee>>>26)|0,Ee&=67108863,r=Math.imul(L,z),i=(i=Math.imul(L,q))+Math.imul(j,z)|0,o=Math.imul(j,q),r=r+Math.imul(P,H)|0,i=(i=i+Math.imul(P,V)|0)+Math.imul(N,H)|0,o=o+Math.imul(N,V)|0,r=r+Math.imul(x,W)|0,i=(i=i+Math.imul(x,$)|0)+Math.imul(C,W)|0,o=o+Math.imul(C,$)|0,r=r+Math.imul(I,J)|0,i=(i=i+Math.imul(I,Z)|0)+Math.imul(k,J)|0,o=o+Math.imul(k,Z)|0,r=r+Math.imul(E,Q)|0,i=(i=i+Math.imul(E,ee)|0)+Math.imul(M,Q)|0,o=o+Math.imul(M,ee)|0,r=r+Math.imul(w,ne)|0,i=(i=i+Math.imul(w,re)|0)+Math.imul(_,ne)|0,o=o+Math.imul(_,re)|0,r=r+Math.imul(m,oe)|0,i=(i=i+Math.imul(m,se)|0)+Math.imul(b,oe)|0,o=o+Math.imul(b,se)|0,r=r+Math.imul(p,ue)|0,i=(i=i+Math.imul(p,ce)|0)+Math.imul(v,ue)|0,o=o+Math.imul(v,ce)|0;var Me=(c+(r=r+Math.imul(l,le)|0)|0)+((8191&(i=(i=i+Math.imul(l,de)|0)+Math.imul(d,le)|0))<<13)|0;c=((o=o+Math.imul(d,de)|0)+(i>>>13)|0)+(Me>>>26)|0,Me&=67108863,r=Math.imul(U,z),i=(i=Math.imul(U,q))+Math.imul(B,z)|0,o=Math.imul(B,q),r=r+Math.imul(L,H)|0,i=(i=i+Math.imul(L,V)|0)+Math.imul(j,H)|0,o=o+Math.imul(j,V)|0,r=r+Math.imul(P,W)|0,i=(i=i+Math.imul(P,$)|0)+Math.imul(N,W)|0,o=o+Math.imul(N,$)|0,r=r+Math.imul(x,J)|0,i=(i=i+Math.imul(x,Z)|0)+Math.imul(C,J)|0,o=o+Math.imul(C,Z)|0,r=r+Math.imul(I,Q)|0,i=(i=i+Math.imul(I,ee)|0)+Math.imul(k,Q)|0,o=o+Math.imul(k,ee)|0,r=r+Math.imul(E,ne)|0,i=(i=i+Math.imul(E,re)|0)+Math.imul(M,ne)|0,o=o+Math.imul(M,re)|0,r=r+Math.imul(w,oe)|0,i=(i=i+Math.imul(w,se)|0)+Math.imul(_,oe)|0,o=o+Math.imul(_,se)|0,r=r+Math.imul(m,ue)|0,i=(i=i+Math.imul(m,ce)|0)+Math.imul(b,ue)|0,o=o+Math.imul(b,ce)|0,r=r+Math.imul(p,le)|0,i=(i=i+Math.imul(p,de)|0)+Math.imul(v,le)|0,o=o+Math.imul(v,de)|0;var Ae=(c+(r=r+Math.imul(l,pe)|0)|0)+((8191&(i=(i=i+Math.imul(l,ve)|0)+Math.imul(d,pe)|0))<<13)|0;c=((o=o+Math.imul(d,ve)|0)+(i>>>13)|0)+(Ae>>>26)|0,Ae&=67108863,r=Math.imul(U,H),i=(i=Math.imul(U,V))+Math.imul(B,H)|0,o=Math.imul(B,V),r=r+Math.imul(L,W)|0,i=(i=i+Math.imul(L,$)|0)+Math.imul(j,W)|0,o=o+Math.imul(j,$)|0,r=r+Math.imul(P,J)|0,i=(i=i+Math.imul(P,Z)|0)+Math.imul(N,J)|0,o=o+Math.imul(N,Z)|0,r=r+Math.imul(x,Q)|0,i=(i=i+Math.imul(x,ee)|0)+Math.imul(C,Q)|0,o=o+Math.imul(C,ee)|0,r=r+Math.imul(I,ne)|0,i=(i=i+Math.imul(I,re)|0)+Math.imul(k,ne)|0,o=o+Math.imul(k,re)|0,r=r+Math.imul(E,oe)|0,i=(i=i+Math.imul(E,se)|0)+Math.imul(M,oe)|0,o=o+Math.imul(M,se)|0,r=r+Math.imul(w,ue)|0,i=(i=i+Math.imul(w,ce)|0)+Math.imul(_,ue)|0,o=o+Math.imul(_,ce)|0,r=r+Math.imul(m,le)|0,i=(i=i+Math.imul(m,de)|0)+Math.imul(b,le)|0,o=o+Math.imul(b,de)|0;var Ie=(c+(r=r+Math.imul(p,pe)|0)|0)+((8191&(i=(i=i+Math.imul(p,ve)|0)+Math.imul(v,pe)|0))<<13)|0;c=((o=o+Math.imul(v,ve)|0)+(i>>>13)|0)+(Ie>>>26)|0,Ie&=67108863,r=Math.imul(U,W),i=(i=Math.imul(U,$))+Math.imul(B,W)|0,o=Math.imul(B,$),r=r+Math.imul(L,J)|0,i=(i=i+Math.imul(L,Z)|0)+Math.imul(j,J)|0,o=o+Math.imul(j,Z)|0,r=r+Math.imul(P,Q)|0,i=(i=i+Math.imul(P,ee)|0)+Math.imul(N,Q)|0,o=o+Math.imul(N,ee)|0,r=r+Math.imul(x,ne)|0,i=(i=i+Math.imul(x,re)|0)+Math.imul(C,ne)|0,o=o+Math.imul(C,re)|0,r=r+Math.imul(I,oe)|0,i=(i=i+Math.imul(I,se)|0)+Math.imul(k,oe)|0,o=o+Math.imul(k,se)|0,r=r+Math.imul(E,ue)|0,i=(i=i+Math.imul(E,ce)|0)+Math.imul(M,ue)|0,o=o+Math.imul(M,ce)|0,r=r+Math.imul(w,le)|0,i=(i=i+Math.imul(w,de)|0)+Math.imul(_,le)|0,o=o+Math.imul(_,de)|0;var ke=(c+(r=r+Math.imul(m,pe)|0)|0)+((8191&(i=(i=i+Math.imul(m,ve)|0)+Math.imul(b,pe)|0))<<13)|0;c=((o=o+Math.imul(b,ve)|0)+(i>>>13)|0)+(ke>>>26)|0,ke&=67108863,r=Math.imul(U,J),i=(i=Math.imul(U,Z))+Math.imul(B,J)|0,o=Math.imul(B,Z),r=r+Math.imul(L,Q)|0,i=(i=i+Math.imul(L,ee)|0)+Math.imul(j,Q)|0,o=o+Math.imul(j,ee)|0,r=r+Math.imul(P,ne)|0,i=(i=i+Math.imul(P,re)|0)+Math.imul(N,ne)|0,o=o+Math.imul(N,re)|0,r=r+Math.imul(x,oe)|0,i=(i=i+Math.imul(x,se)|0)+Math.imul(C,oe)|0,o=o+Math.imul(C,se)|0,r=r+Math.imul(I,ue)|0,i=(i=i+Math.imul(I,ce)|0)+Math.imul(k,ue)|0,o=o+Math.imul(k,ce)|0,r=r+Math.imul(E,le)|0,i=(i=i+Math.imul(E,de)|0)+Math.imul(M,le)|0,o=o+Math.imul(M,de)|0;var Oe=(c+(r=r+Math.imul(w,pe)|0)|0)+((8191&(i=(i=i+Math.imul(w,ve)|0)+Math.imul(_,pe)|0))<<13)|0;c=((o=o+Math.imul(_,ve)|0)+(i>>>13)|0)+(Oe>>>26)|0,Oe&=67108863,r=Math.imul(U,Q),i=(i=Math.imul(U,ee))+Math.imul(B,Q)|0,o=Math.imul(B,ee),r=r+Math.imul(L,ne)|0,i=(i=i+Math.imul(L,re)|0)+Math.imul(j,ne)|0,o=o+Math.imul(j,re)|0,r=r+Math.imul(P,oe)|0,i=(i=i+Math.imul(P,se)|0)+Math.imul(N,oe)|0,o=o+Math.imul(N,se)|0,r=r+Math.imul(x,ue)|0,i=(i=i+Math.imul(x,ce)|0)+Math.imul(C,ue)|0,o=o+Math.imul(C,ce)|0,r=r+Math.imul(I,le)|0,i=(i=i+Math.imul(I,de)|0)+Math.imul(k,le)|0,o=o+Math.imul(k,de)|0;var xe=(c+(r=r+Math.imul(E,pe)|0)|0)+((8191&(i=(i=i+Math.imul(E,ve)|0)+Math.imul(M,pe)|0))<<13)|0;c=((o=o+Math.imul(M,ve)|0)+(i>>>13)|0)+(xe>>>26)|0,xe&=67108863,r=Math.imul(U,ne),i=(i=Math.imul(U,re))+Math.imul(B,ne)|0,o=Math.imul(B,re),r=r+Math.imul(L,oe)|0,i=(i=i+Math.imul(L,se)|0)+Math.imul(j,oe)|0,o=o+Math.imul(j,se)|0,r=r+Math.imul(P,ue)|0,i=(i=i+Math.imul(P,ce)|0)+Math.imul(N,ue)|0,o=o+Math.imul(N,ce)|0,r=r+Math.imul(x,le)|0,i=(i=i+Math.imul(x,de)|0)+Math.imul(C,le)|0,o=o+Math.imul(C,de)|0;var Ce=(c+(r=r+Math.imul(I,pe)|0)|0)+((8191&(i=(i=i+Math.imul(I,ve)|0)+Math.imul(k,pe)|0))<<13)|0;c=((o=o+Math.imul(k,ve)|0)+(i>>>13)|0)+(Ce>>>26)|0,Ce&=67108863,r=Math.imul(U,oe),i=(i=Math.imul(U,se))+Math.imul(B,oe)|0,o=Math.imul(B,se),r=r+Math.imul(L,ue)|0,i=(i=i+Math.imul(L,ce)|0)+Math.imul(j,ue)|0,o=o+Math.imul(j,ce)|0,r=r+Math.imul(P,le)|0,i=(i=i+Math.imul(P,de)|0)+Math.imul(N,le)|0,o=o+Math.imul(N,de)|0;var Te=(c+(r=r+Math.imul(x,pe)|0)|0)+((8191&(i=(i=i+Math.imul(x,ve)|0)+Math.imul(C,pe)|0))<<13)|0;c=((o=o+Math.imul(C,ve)|0)+(i>>>13)|0)+(Te>>>26)|0,Te&=67108863,r=Math.imul(U,ue),i=(i=Math.imul(U,ce))+Math.imul(B,ue)|0,o=Math.imul(B,ce),r=r+Math.imul(L,le)|0,i=(i=i+Math.imul(L,de)|0)+Math.imul(j,le)|0,o=o+Math.imul(j,de)|0;var Pe=(c+(r=r+Math.imul(P,pe)|0)|0)+((8191&(i=(i=i+Math.imul(P,ve)|0)+Math.imul(N,pe)|0))<<13)|0;c=((o=o+Math.imul(N,ve)|0)+(i>>>13)|0)+(Pe>>>26)|0,Pe&=67108863,r=Math.imul(U,le),i=(i=Math.imul(U,de))+Math.imul(B,le)|0,o=Math.imul(B,de);var Ne=(c+(r=r+Math.imul(L,pe)|0)|0)+((8191&(i=(i=i+Math.imul(L,ve)|0)+Math.imul(j,pe)|0))<<13)|0;c=((o=o+Math.imul(j,ve)|0)+(i>>>13)|0)+(Ne>>>26)|0,Ne&=67108863;var Re=(c+(r=Math.imul(U,pe))|0)+((8191&(i=(i=Math.imul(U,ve))+Math.imul(B,pe)|0))<<13)|0;return c=((o=Math.imul(B,ve))+(i>>>13)|0)+(Re>>>26)|0,Re&=67108863,u[0]=ge,u[1]=me,u[2]=be,u[3]=ye,u[4]=we,u[5]=_e,u[6]=Se,u[7]=Ee,u[8]=Me,u[9]=Ae,u[10]=Ie,u[11]=ke,u[12]=Oe,u[13]=xe,u[14]=Ce,u[15]=Te,u[16]=Pe,u[17]=Ne,u[18]=Re,0!==c&&(u[19]=c,n.length++),n};function g(e,t,n){n.negative=t.negative^e.negative,n.length=e.length+t.length;for(var r=0,i=0,o=0;o<n.length-1;o++){var s=i;i=0;for(var a=67108863&r,u=Math.min(o,t.length-1),c=Math.max(0,o-e.length+1);c<=u;c++){var f=o-c,l=(0|e.words[f])*(0|t.words[c]),d=67108863&l;a=67108863&(d=d+a|0),i+=(s=(s=s+(l/67108864|0)|0)+(d>>>26)|0)>>>26,s&=67108863}n.words[o]=a,r=s,s=i}return 0!==r?n.words[o]=r:n.length--,n._strip()}function m(e,t,n){return g(e,t,n)}function b(e,t){this.x=e,this.y=t}Math.imul||(v=p),o.prototype.mulTo=function(e,t){var n=this.length+e.length;return 10===this.length&&10===e.length?v(this,e,t):n<63?p(this,e,t):n<1024?g(this,e,t):m(this,e,t)},b.prototype.makeRBT=function(e){for(var t=new Array(e),n=o.prototype._countBits(e)-1,r=0;r<e;r++)t[r]=this.revBin(r,n,e);return t},b.prototype.revBin=function(e,t,n){if(0===e||e===n-1)return e;for(var r=0,i=0;i<t;i++)r|=(1&e)<<t-i-1,e>>=1;return r},b.prototype.permute=function(e,t,n,r,i,o){for(var s=0;s<o;s++)r[s]=t[e[s]],i[s]=n[e[s]]},b.prototype.transform=function(e,t,n,r,i,o){this.permute(o,e,t,n,r,i);for(var s=1;s<i;s<<=1)for(var a=s<<1,u=Math.cos(2*Math.PI/a),c=Math.sin(2*Math.PI/a),f=0;f<i;f+=a)for(var l=u,d=c,h=0;h<s;h++){var p=n[f+h],v=r[f+h],g=n[f+h+s],m=r[f+h+s],b=l*g-d*m;m=l*m+d*g,g=b,n[f+h]=p+g,r[f+h]=v+m,n[f+h+s]=p-g,r[f+h+s]=v-m,h!==a&&(b=u*l-c*d,d=u*d+c*l,l=b)}},b.prototype.guessLen13b=function(e,t){var n=1|Math.max(t,e),r=1&n,i=0;for(n=n/2|0;n;n>>>=1)i++;return 1<<i+1+r},b.prototype.conjugate=function(e,t,n){if(!(n<=1))for(var r=0;r<n/2;r++){var i=e[r];e[r]=e[n-r-1],e[n-r-1]=i,i=t[r],t[r]=-t[n-r-1],t[n-r-1]=-i}},b.prototype.normalize13b=function(e,t){for(var n=0,r=0;r<t/2;r++){var i=8192*Math.round(e[2*r+1]/t)+Math.round(e[2*r]/t)+n;e[r]=67108863&i,n=i<67108864?0:i/67108864|0}return e},b.prototype.convert13b=function(e,t,n,i){for(var o=0,s=0;s<t;s++)o+=0|e[s],n[2*s]=8191&o,o>>>=13,n[2*s+1]=8191&o,o>>>=13;for(s=2*t;s<i;++s)n[s]=0;r(0===o),r(0==(-8192&o))},b.prototype.stub=function(e){for(var t=new Array(e),n=0;n<e;n++)t[n]=0;return t},b.prototype.mulp=function(e,t,n){var r=2*this.guessLen13b(e.length,t.length),i=this.makeRBT(r),o=this.stub(r),s=new Array(r),a=new Array(r),u=new Array(r),c=new Array(r),f=new Array(r),l=new Array(r),d=n.words;d.length=r,this.convert13b(e.words,e.length,s,r),this.convert13b(t.words,t.length,c,r),this.transform(s,o,a,u,r,i),this.transform(c,o,f,l,r,i);for(var h=0;h<r;h++){var p=a[h]*f[h]-u[h]*l[h];u[h]=a[h]*l[h]+u[h]*f[h],a[h]=p}return this.conjugate(a,u,r),this.transform(a,u,d,o,r,i),this.conjugate(d,o,r),this.normalize13b(d,r),n.negative=e.negative^t.negative,n.length=e.length+t.length,n._strip()},o.prototype.mul=function(e){var t=new o(null);return t.words=new Array(this.length+e.length),this.mulTo(e,t)},o.prototype.mulf=function(e){var t=new o(null);return t.words=new Array(this.length+e.length),m(this,e,t)},o.prototype.imul=function(e){return this.clone().mulTo(e,this)},o.prototype.imuln=function(e){var t=e<0;t&&(e=-e),r("number"==typeof e),r(e<67108864);for(var n=0,i=0;i<this.length;i++){var o=(0|this.words[i])*e,s=(67108863&o)+(67108863&n);n>>=26,n+=o/67108864|0,n+=s>>>26,this.words[i]=67108863&s}return 0!==n&&(this.words[i]=n,this.length++),t?this.ineg():this},o.prototype.muln=function(e){return this.clone().imuln(e)},o.prototype.sqr=function(){return this.mul(this)},o.prototype.isqr=function(){return this.imul(this.clone())},o.prototype.pow=function(e){var t=function(e){for(var t=new Array(e.bitLength()),n=0;n<t.length;n++){var r=n/26|0,i=n%26;t[n]=e.words[r]>>>i&1}return t}(e);if(0===t.length)return new o(1);for(var n=this,r=0;r<t.length&&0===t[r];r++,n=n.sqr());if(++r<t.length)for(var i=n.sqr();r<t.length;r++,i=i.sqr())0!==t[r]&&(n=n.mul(i));return n},o.prototype.iushln=function(e){r("number"==typeof e&&e>=0);var t,n=e%26,i=(e-n)/26,o=67108863>>>26-n<<26-n;if(0!==n){var s=0;for(t=0;t<this.length;t++){var a=this.words[t]&o,u=(0|this.words[t])-a<<n;this.words[t]=u|s,s=a>>>26-n}s&&(this.words[t]=s,this.length++)}if(0!==i){for(t=this.length-1;t>=0;t--)this.words[t+i]=this.words[t];for(t=0;t<i;t++)this.words[t]=0;this.length+=i}return this._strip()},o.prototype.ishln=function(e){return r(0===this.negative),this.iushln(e)},o.prototype.iushrn=function(e,t,n){var i;r("number"==typeof e&&e>=0),i=t?(t-t%26)/26:0;var o=e%26,s=Math.min((e-o)/26,this.length),a=67108863^67108863>>>o<<o,u=n;if(i-=s,i=Math.max(0,i),u){for(var c=0;c<s;c++)u.words[c]=this.words[c];u.length=s}if(0===s);else if(this.length>s)for(this.length-=s,c=0;c<this.length;c++)this.words[c]=this.words[c+s];else this.words[0]=0,this.length=1;var f=0;for(c=this.length-1;c>=0&&(0!==f||c>=i);c--){var l=0|this.words[c];this.words[c]=f<<26-o|l>>>o,f=l&a}return u&&0!==f&&(u.words[u.length++]=f),0===this.length&&(this.words[0]=0,this.length=1),this._strip()},o.prototype.ishrn=function(e,t,n){return r(0===this.negative),this.iushrn(e,t,n)},o.prototype.shln=function(e){return this.clone().ishln(e)},o.prototype.ushln=function(e){return this.clone().iushln(e)},o.prototype.shrn=function(e){return this.clone().ishrn(e)},o.prototype.ushrn=function(e){return this.clone().iushrn(e)},o.prototype.testn=function(e){r("number"==typeof e&&e>=0);var t=e%26,n=(e-t)/26,i=1<<t;return!(this.length<=n)&&!!(this.words[n]&i)},o.prototype.imaskn=function(e){r("number"==typeof e&&e>=0);var t=e%26,n=(e-t)/26;if(r(0===this.negative,"imaskn works only with positive numbers"),this.length<=n)return this;if(0!==t&&n++,this.length=Math.min(n,this.length),0!==t){var i=67108863^67108863>>>t<<t;this.words[this.length-1]&=i}return this._strip()},o.prototype.maskn=function(e){return this.clone().imaskn(e)},o.prototype.iaddn=function(e){return r("number"==typeof e),r(e<67108864),e<0?this.isubn(-e):0!==this.negative?1===this.length&&(0|this.words[0])<=e?(this.words[0]=e-(0|this.words[0]),this.negative=0,this):(this.negative=0,this.isubn(e),this.negative=1,this):this._iaddn(e)},o.prototype._iaddn=function(e){this.words[0]+=e;for(var t=0;t<this.length&&this.words[t]>=67108864;t++)this.words[t]-=67108864,t===this.length-1?this.words[t+1]=1:this.words[t+1]++;return this.length=Math.max(this.length,t+1),this},o.prototype.isubn=function(e){if(r("number"==typeof e),r(e<67108864),e<0)return this.iaddn(-e);if(0!==this.negative)return this.negative=0,this.iaddn(e),this.negative=1,this;if(this.words[0]-=e,1===this.length&&this.words[0]<0)this.words[0]=-this.words[0],this.negative=1;else for(var t=0;t<this.length&&this.words[t]<0;t++)this.words[t]+=67108864,this.words[t+1]-=1;return this._strip()},o.prototype.addn=function(e){return this.clone().iaddn(e)},o.prototype.subn=function(e){return this.clone().isubn(e)},o.prototype.iabs=function(){return this.negative=0,this},o.prototype.abs=function(){return this.clone().iabs()},o.prototype._ishlnsubmul=function(e,t,n){var i,o,s=e.length+n;this._expand(s);var a=0;for(i=0;i<e.length;i++){o=(0|this.words[i+n])+a;var u=(0|e.words[i])*t;a=((o-=67108863&u)>>26)-(u/67108864|0),this.words[i+n]=67108863&o}for(;i<this.length-n;i++)a=(o=(0|this.words[i+n])+a)>>26,this.words[i+n]=67108863&o;if(0===a)return this._strip();for(r(-1===a),a=0,i=0;i<this.length;i++)a=(o=-(0|this.words[i])+a)>>26,this.words[i]=67108863&o;return this.negative=1,this._strip()},o.prototype._wordDiv=function(e,t){var n=(this.length,e.length),r=this.clone(),i=e,s=0|i.words[i.length-1];0!==(n=26-this._countBits(s))&&(i=i.ushln(n),r.iushln(n),s=0|i.words[i.length-1]);var a,u=r.length-i.length;if("mod"!==t){(a=new o(null)).length=u+1,a.words=new Array(a.length);for(var c=0;c<a.length;c++)a.words[c]=0}var f=r.clone()._ishlnsubmul(i,1,u);0===f.negative&&(r=f,a&&(a.words[u]=1));for(var l=u-1;l>=0;l--){var d=67108864*(0|r.words[i.length+l])+(0|r.words[i.length+l-1]);for(d=Math.min(d/s|0,67108863),r._ishlnsubmul(i,d,l);0!==r.negative;)d--,r.negative=0,r._ishlnsubmul(i,1,l),r.isZero()||(r.negative^=1);a&&(a.words[l]=d)}return a&&a._strip(),r._strip(),"div"!==t&&0!==n&&r.iushrn(n),{div:a||null,mod:r}},o.prototype.divmod=function(e,t,n){return r(!e.isZero()),this.isZero()?{div:new o(0),mod:new o(0)}:0!==this.negative&&0===e.negative?(a=this.neg().divmod(e,t),"mod"!==t&&(i=a.div.neg()),"div"!==t&&(s=a.mod.neg(),n&&0!==s.negative&&s.iadd(e)),{div:i,mod:s}):0===this.negative&&0!==e.negative?(a=this.divmod(e.neg(),t),"mod"!==t&&(i=a.div.neg()),{div:i,mod:a.mod}):0!=(this.negative&e.negative)?(a=this.neg().divmod(e.neg(),t),"div"!==t&&(s=a.mod.neg(),n&&0!==s.negative&&s.isub(e)),{div:a.div,mod:s}):e.length>this.length||this.cmp(e)<0?{div:new o(0),mod:this}:1===e.length?"div"===t?{div:this.divn(e.words[0]),mod:null}:"mod"===t?{div:null,mod:new o(this.modrn(e.words[0]))}:{div:this.divn(e.words[0]),mod:new o(this.modrn(e.words[0]))}:this._wordDiv(e,t);var i,s,a},o.prototype.div=function(e){return this.divmod(e,"div",!1).div},o.prototype.mod=function(e){return this.divmod(e,"mod",!1).mod},o.prototype.umod=function(e){return this.divmod(e,"mod",!0).mod},o.prototype.divRound=function(e){var t=this.divmod(e);if(t.mod.isZero())return t.div;var n=0!==t.div.negative?t.mod.isub(e):t.mod,r=e.ushrn(1),i=e.andln(1),o=n.cmp(r);return o<0||1===i&&0===o?t.div:0!==t.div.negative?t.div.isubn(1):t.div.iaddn(1)},o.prototype.modrn=function(e){var t=e<0;t&&(e=-e),r(e<=67108863);for(var n=(1<<26)%e,i=0,o=this.length-1;o>=0;o--)i=(n*i+(0|this.words[o]))%e;return t?-i:i},o.prototype.modn=function(e){return this.modrn(e)},o.prototype.idivn=function(e){var t=e<0;t&&(e=-e),r(e<=67108863);for(var n=0,i=this.length-1;i>=0;i--){var o=(0|this.words[i])+67108864*n;this.words[i]=o/e|0,n=o%e}return this._strip(),t?this.ineg():this},o.prototype.divn=function(e){return this.clone().idivn(e)},o.prototype.egcd=function(e){r(0===e.negative),r(!e.isZero());var t=this,n=e.clone();t=0!==t.negative?t.umod(e):t.clone();for(var i=new o(1),s=new o(0),a=new o(0),u=new o(1),c=0;t.isEven()&&n.isEven();)t.iushrn(1),n.iushrn(1),++c;for(var f=n.clone(),l=t.clone();!t.isZero();){for(var d=0,h=1;0==(t.words[0]&h)&&d<26;++d,h<<=1);if(d>0)for(t.iushrn(d);d-- >0;)(i.isOdd()||s.isOdd())&&(i.iadd(f),s.isub(l)),i.iushrn(1),s.iushrn(1);for(var p=0,v=1;0==(n.words[0]&v)&&p<26;++p,v<<=1);if(p>0)for(n.iushrn(p);p-- >0;)(a.isOdd()||u.isOdd())&&(a.iadd(f),u.isub(l)),a.iushrn(1),u.iushrn(1);t.cmp(n)>=0?(t.isub(n),i.isub(a),s.isub(u)):(n.isub(t),a.isub(i),u.isub(s))}return{a:a,b:u,gcd:n.iushln(c)}},o.prototype._invmp=function(e){r(0===e.negative),r(!e.isZero());var t=this,n=e.clone();t=0!==t.negative?t.umod(e):t.clone();for(var i,s=new o(1),a=new o(0),u=n.clone();t.cmpn(1)>0&&n.cmpn(1)>0;){for(var c=0,f=1;0==(t.words[0]&f)&&c<26;++c,f<<=1);if(c>0)for(t.iushrn(c);c-- >0;)s.isOdd()&&s.iadd(u),s.iushrn(1);for(var l=0,d=1;0==(n.words[0]&d)&&l<26;++l,d<<=1);if(l>0)for(n.iushrn(l);l-- >0;)a.isOdd()&&a.iadd(u),a.iushrn(1);t.cmp(n)>=0?(t.isub(n),s.isub(a)):(n.isub(t),a.isub(s))}return(i=0===t.cmpn(1)?s:a).cmpn(0)<0&&i.iadd(e),i},o.prototype.gcd=function(e){if(this.isZero())return e.abs();if(e.isZero())return this.abs();var t=this.clone(),n=e.clone();t.negative=0,n.negative=0;for(var r=0;t.isEven()&&n.isEven();r++)t.iushrn(1),n.iushrn(1);for(;;){for(;t.isEven();)t.iushrn(1);for(;n.isEven();)n.iushrn(1);var i=t.cmp(n);if(i<0){var o=t;t=n,n=o}else if(0===i||0===n.cmpn(1))break;t.isub(n)}return n.iushln(r)},o.prototype.invm=function(e){return this.egcd(e).a.umod(e)},o.prototype.isEven=function(){return 0==(1&this.words[0])},o.prototype.isOdd=function(){return 1==(1&this.words[0])},o.prototype.andln=function(e){return this.words[0]&e},o.prototype.bincn=function(e){r("number"==typeof e);var t=e%26,n=(e-t)/26,i=1<<t;if(this.length<=n)return this._expand(n+1),this.words[n]|=i,this;for(var o=i,s=n;0!==o&&s<this.length;s++){var a=0|this.words[s];o=(a+=o)>>>26,a&=67108863,this.words[s]=a}return 0!==o&&(this.words[s]=o,this.length++),this},o.prototype.isZero=function(){return 1===this.length&&0===this.words[0]},o.prototype.cmpn=function(e){var t,n=e<0;if(0!==this.negative&&!n)return-1;if(0===this.negative&&n)return 1;if(this._strip(),this.length>1)t=1;else{n&&(e=-e),r(e<=67108863,"Number is too big");var i=0|this.words[0];t=i===e?0:i<e?-1:1}return 0!==this.negative?0|-t:t},o.prototype.cmp=function(e){if(0!==this.negative&&0===e.negative)return-1;if(0===this.negative&&0!==e.negative)return 1;var t=this.ucmp(e);return 0!==this.negative?0|-t:t},o.prototype.ucmp=function(e){if(this.length>e.length)return 1;if(this.length<e.length)return-1;for(var t=0,n=this.length-1;n>=0;n--){var r=0|this.words[n],i=0|e.words[n];if(r!==i){r<i?t=-1:r>i&&(t=1);break}}return t},o.prototype.gtn=function(e){return 1===this.cmpn(e)},o.prototype.gt=function(e){return 1===this.cmp(e)},o.prototype.gten=function(e){return this.cmpn(e)>=0},o.prototype.gte=function(e){return this.cmp(e)>=0},o.prototype.ltn=function(e){return-1===this.cmpn(e)},o.prototype.lt=function(e){return-1===this.cmp(e)},o.prototype.lten=function(e){return this.cmpn(e)<=0},o.prototype.lte=function(e){return this.cmp(e)<=0},o.prototype.eqn=function(e){return 0===this.cmpn(e)},o.prototype.eq=function(e){return 0===this.cmp(e)},o.red=function(e){return new A(e)},o.prototype.toRed=function(e){return r(!this.red,"Already a number in reduction context"),r(0===this.negative,"red works only with positives"),e.convertTo(this)._forceRed(e)},o.prototype.fromRed=function(){return r(this.red,"fromRed works only with numbers in reduction context"),this.red.convertFrom(this)},o.prototype._forceRed=function(e){return this.red=e,this},o.prototype.forceRed=function(e){return r(!this.red,"Already a number in reduction context"),this._forceRed(e)},o.prototype.redAdd=function(e){return r(this.red,"redAdd works only with red numbers"),this.red.add(this,e)},o.prototype.redIAdd=function(e){return r(this.red,"redIAdd works only with red numbers"),this.red.iadd(this,e)},o.prototype.redSub=function(e){return r(this.red,"redSub works only with red numbers"),this.red.sub(this,e)},o.prototype.redISub=function(e){return r(this.red,"redISub works only with red numbers"),this.red.isub(this,e)},o.prototype.redShl=function(e){return r(this.red,"redShl works only with red numbers"),this.red.shl(this,e)},o.prototype.redMul=function(e){return r(this.red,"redMul works only with red numbers"),this.red._verify2(this,e),this.red.mul(this,e)},o.prototype.redIMul=function(e){return r(this.red,"redMul works only with red numbers"),this.red._verify2(this,e),this.red.imul(this,e)},o.prototype.redSqr=function(){return r(this.red,"redSqr works only with red numbers"),this.red._verify1(this),this.red.sqr(this)},o.prototype.redISqr=function(){return r(this.red,"redISqr works only with red numbers"),this.red._verify1(this),this.red.isqr(this)},o.prototype.redSqrt=function(){return r(this.red,"redSqrt works only with red numbers"),this.red._verify1(this),this.red.sqrt(this)},o.prototype.redInvm=function(){return r(this.red,"redInvm works only with red numbers"),this.red._verify1(this),this.red.invm(this)},o.prototype.redNeg=function(){return r(this.red,"redNeg works only with red numbers"),this.red._verify1(this),this.red.neg(this)},o.prototype.redPow=function(e){return r(this.red&&!e.red,"redPow(normalNum)"),this.red._verify1(this),this.red.pow(this,e)};var y={k256:null,p224:null,p192:null,p25519:null};function w(e,t){this.name=e,this.p=new o(t,16),this.n=this.p.bitLength(),this.k=new o(1).iushln(this.n).isub(this.p),this.tmp=this._tmp()}function _(){w.call(this,"k256","ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f")}function S(){w.call(this,"p224","ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001")}function E(){w.call(this,"p192","ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff")}function M(){w.call(this,"25519","7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed")}function A(e){if("string"==typeof e){var t=o._prime(e);this.m=t.p,this.prime=t}else r(e.gtn(1),"modulus must be greater than 1"),this.m=e,this.prime=null}function I(e){A.call(this,e),this.shift=this.m.bitLength(),this.shift%26!=0&&(this.shift+=26-this.shift%26),this.r=new o(1).iushln(this.shift),this.r2=this.imod(this.r.sqr()),this.rinv=this.r._invmp(this.m),this.minv=this.rinv.mul(this.r).isubn(1).div(this.m),this.minv=this.minv.umod(this.r),this.minv=this.r.sub(this.minv)}w.prototype._tmp=function(){var e=new o(null);return e.words=new Array(Math.ceil(this.n/13)),e},w.prototype.ireduce=function(e){var t,n=e;do{this.split(n,this.tmp),t=(n=(n=this.imulK(n)).iadd(this.tmp)).bitLength()}while(t>this.n);var r=t<this.n?-1:n.ucmp(this.p);return 0===r?(n.words[0]=0,n.length=1):r>0?n.isub(this.p):void 0!==n.strip?n.strip():n._strip(),n},w.prototype.split=function(e,t){e.iushrn(this.n,0,t)},w.prototype.imulK=function(e){return e.imul(this.k)},i(_,w),_.prototype.split=function(e,t){for(var n=Math.min(e.length,9),r=0;r<n;r++)t.words[r]=e.words[r];if(t.length=n,e.length<=9)return e.words[0]=0,void(e.length=1);var i=e.words[9];for(t.words[t.length++]=4194303&i,r=10;r<e.length;r++){var o=0|e.words[r];e.words[r-10]=(4194303&o)<<4|i>>>22,i=o}i>>>=22,e.words[r-10]=i,0===i&&e.length>10?e.length-=10:e.length-=9},_.prototype.imulK=function(e){e.words[e.length]=0,e.words[e.length+1]=0,e.length+=2;for(var t=0,n=0;n<e.length;n++){var r=0|e.words[n];t+=977*r,e.words[n]=67108863&t,t=64*r+(t/67108864|0)}return 0===e.words[e.length-1]&&(e.length--,0===e.words[e.length-1]&&e.length--),e},i(S,w),i(E,w),i(M,w),M.prototype.imulK=function(e){for(var t=0,n=0;n<e.length;n++){var r=19*(0|e.words[n])+t,i=67108863&r;r>>>=26,e.words[n]=i,t=r}return 0!==t&&(e.words[e.length++]=t),e},o._prime=function(e){if(y[e])return y[e];var t;if("k256"===e)t=new _;else if("p224"===e)t=new S;else if("p192"===e)t=new E;else{if("p25519"!==e)throw new Error("Unknown prime "+e);t=new M}return y[e]=t,t},A.prototype._verify1=function(e){r(0===e.negative,"red works only with positives"),r(e.red,"red works only with red numbers")},A.prototype._verify2=function(e,t){r(0==(e.negative|t.negative),"red works only with positives"),r(e.red&&e.red===t.red,"red works only with red numbers")},A.prototype.imod=function(e){return this.prime?this.prime.ireduce(e)._forceRed(this):(c(e,e.umod(this.m)._forceRed(this)),e)},A.prototype.neg=function(e){return e.isZero()?e.clone():this.m.sub(e)._forceRed(this)},A.prototype.add=function(e,t){this._verify2(e,t);var n=e.add(t);return n.cmp(this.m)>=0&&n.isub(this.m),n._forceRed(this)},A.prototype.iadd=function(e,t){this._verify2(e,t);var n=e.iadd(t);return n.cmp(this.m)>=0&&n.isub(this.m),n},A.prototype.sub=function(e,t){this._verify2(e,t);var n=e.sub(t);return n.cmpn(0)<0&&n.iadd(this.m),n._forceRed(this)},A.prototype.isub=function(e,t){this._verify2(e,t);var n=e.isub(t);return n.cmpn(0)<0&&n.iadd(this.m),n},A.prototype.shl=function(e,t){return this._verify1(e),this.imod(e.ushln(t))},A.prototype.imul=function(e,t){return this._verify2(e,t),this.imod(e.imul(t))},A.prototype.mul=function(e,t){return this._verify2(e,t),this.imod(e.mul(t))},A.prototype.isqr=function(e){return this.imul(e,e.clone())},A.prototype.sqr=function(e){return this.mul(e,e)},A.prototype.sqrt=function(e){if(e.isZero())return e.clone();var t=this.m.andln(3);if(r(t%2==1),3===t){var n=this.m.add(new o(1)).iushrn(2);return this.pow(e,n)}for(var i=this.m.subn(1),s=0;!i.isZero()&&0===i.andln(1);)s++,i.iushrn(1);r(!i.isZero());var a=new o(1).toRed(this),u=a.redNeg(),c=this.m.subn(1).iushrn(1),f=this.m.bitLength();for(f=new o(2*f*f).toRed(this);0!==this.pow(f,c).cmp(u);)f.redIAdd(u);for(var l=this.pow(f,i),d=this.pow(e,i.addn(1).iushrn(1)),h=this.pow(e,i),p=s;0!==h.cmp(a);){for(var v=h,g=0;0!==v.cmp(a);g++)v=v.redSqr();r(g<p);var m=this.pow(l,new o(1).iushln(p-g-1));d=d.redMul(m),l=m.redSqr(),h=h.redMul(l),p=g}return d},A.prototype.invm=function(e){var t=e._invmp(this.m);return 0!==t.negative?(t.negative=0,this.imod(t).redNeg()):this.imod(t)},A.prototype.pow=function(e,t){if(t.isZero())return new o(1).toRed(this);if(0===t.cmpn(1))return e.clone();var n=new Array(16);n[0]=new o(1).toRed(this),n[1]=e;for(var r=2;r<n.length;r++)n[r]=this.mul(n[r-1],e);var i=n[0],s=0,a=0,u=t.bitLength()%26;for(0===u&&(u=26),r=t.length-1;r>=0;r--){for(var c=t.words[r],f=u-1;f>=0;f--){var l=c>>f&1;i!==n[0]&&(i=this.sqr(i)),0!==l||0!==s?(s<<=1,s|=l,(4===++a||0===r&&0===f)&&(i=this.mul(i,n[s]),a=0,s=0)):a=0}u=26}return i},A.prototype.convertTo=function(e){var t=e.umod(this.m);return t===e?t.clone():t},A.prototype.convertFrom=function(e){var t=e.clone();return t.red=null,t},o.mont=function(e){return new I(e)},i(I,A),I.prototype.convertTo=function(e){return this.imod(e.ushln(this.shift))},I.prototype.convertFrom=function(e){var t=this.imod(e.mul(this.rinv));return t.red=null,t},I.prototype.imul=function(e,t){if(e.isZero()||t.isZero())return e.words[0]=0,e.length=1,e;var n=e.imul(t),r=n.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=n.isub(r).iushrn(this.shift),o=i;return i.cmp(this.m)>=0?o=i.isub(this.m):i.cmpn(0)<0&&(o=i.iadd(this.m)),o._forceRed(this)},I.prototype.mul=function(e,t){if(e.isZero()||t.isZero())return new o(0)._forceRed(this);var n=e.mul(t),r=n.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=n.isub(r).iushrn(this.shift),s=i;return i.cmp(this.m)>=0?s=i.isub(this.m):i.cmpn(0)<0&&(s=i.iadd(this.m)),s._forceRed(this)},I.prototype.invm=function(e){return this.imod(e._invmp(this.m).mul(this.r2))._forceRed(this)}}(e,this)}).call(this,n(57)(e))},function(e,t){},function(e){e.exports=JSON.parse('{"name":"elliptic","version":"6.5.4","description":"EC cryptography","main":"lib/elliptic.js","files":["lib"],"scripts":{"lint":"eslint lib test","lint:fix":"npm run lint -- --fix","unit":"istanbul test _mocha --reporter=spec test/index.js","test":"npm run lint && npm run unit","version":"grunt dist && git add dist/"},"repository":{"type":"git","url":"git@github.com:indutny/elliptic"},"keywords":["EC","Elliptic","curve","Cryptography"],"author":"Fedor Indutny <fedor@indutny.com>","license":"MIT","bugs":{"url":"https://github.com/indutny/elliptic/issues"},"homepage":"https://github.com/indutny/elliptic","devDependencies":{"brfs":"^2.0.2","coveralls":"^3.1.0","eslint":"^7.6.0","grunt":"^1.2.1","grunt-browserify":"^5.3.0","grunt-cli":"^1.3.2","grunt-contrib-connect":"^3.0.0","grunt-contrib-copy":"^1.0.0","grunt-contrib-uglify":"^5.0.0","grunt-mocha-istanbul":"^5.0.2","grunt-saucelabs":"^9.0.1","istanbul":"^0.4.5","mocha":"^8.0.1"},"dependencies":{"bn.js":"^4.11.9","brorand":"^1.1.0","hash.js":"^1.0.0","hmac-drbg":"^1.0.1","inherits":"^2.0.4","minimalistic-assert":"^1.0.1","minimalistic-crypto-utils":"^1.0.1"}}')},function(e,t,n){"use strict";var r=n(47),i=n(29),o=n(7),s=n(95),a=r.assert;function u(e){s.call(this,"short",e),this.a=new i(e.a,16).toRed(this.red),this.b=new i(e.b,16).toRed(this.red),this.tinv=this.two.redInvm(),this.zeroA=0===this.a.fromRed().cmpn(0),this.threeA=0===this.a.fromRed().sub(this.p).cmpn(-3),this.endo=this._getEndomorphism(e),this._endoWnafT1=new Array(4),this._endoWnafT2=new Array(4)}function c(e,t,n,r){s.BasePoint.call(this,e,"affine"),null===t&&null===n?(this.x=null,this.y=null,this.inf=!0):(this.x=new i(t,16),this.y=new i(n,16),r&&(this.x.forceRed(this.curve.red),this.y.forceRed(this.curve.red)),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.inf=!1)}function f(e,t,n,r){s.BasePoint.call(this,e,"jacobian"),null===t&&null===n&&null===r?(this.x=this.curve.one,this.y=this.curve.one,this.z=new i(0)):(this.x=new i(t,16),this.y=new i(n,16),this.z=new i(r,16)),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)),this.zOne=this.z===this.curve.one}o(u,s),e.exports=u,u.prototype._getEndomorphism=function(e){if(this.zeroA&&this.g&&this.n&&1===this.p.modn(3)){var t,n;if(e.beta)t=new i(e.beta,16).toRed(this.red);else{var r=this._getEndoRoots(this.p);t=(t=r[0].cmp(r[1])<0?r[0]:r[1]).toRed(this.red)}if(e.lambda)n=new i(e.lambda,16);else{var o=this._getEndoRoots(this.n);0===this.g.mul(o[0]).x.cmp(this.g.x.redMul(t))?n=o[0]:(n=o[1],a(0===this.g.mul(n).x.cmp(this.g.x.redMul(t))))}return{beta:t,lambda:n,basis:e.basis?e.basis.map((function(e){return{a:new i(e.a,16),b:new i(e.b,16)}})):this._getEndoBasis(n)}}},u.prototype._getEndoRoots=function(e){var t=e===this.p?this.red:i.mont(e),n=new i(2).toRed(t).redInvm(),r=n.redNeg(),o=new i(3).toRed(t).redNeg().redSqrt().redMul(n);return[r.redAdd(o).fromRed(),r.redSub(o).fromRed()]},u.prototype._getEndoBasis=function(e){for(var t,n,r,o,s,a,u,c,f,l=this.n.ushrn(Math.floor(this.n.bitLength()/2)),d=e,h=this.n.clone(),p=new i(1),v=new i(0),g=new i(0),m=new i(1),b=0;0!==d.cmpn(0);){var y=h.div(d);c=h.sub(y.mul(d)),f=g.sub(y.mul(p));var w=m.sub(y.mul(v));if(!r&&c.cmp(l)<0)t=u.neg(),n=p,r=c.neg(),o=f;else if(r&&2==++b)break;u=c,h=d,d=c,g=p,p=f,m=v,v=w}s=c.neg(),a=f;var _=r.sqr().add(o.sqr());return s.sqr().add(a.sqr()).cmp(_)>=0&&(s=t,a=n),r.negative&&(r=r.neg(),o=o.neg()),s.negative&&(s=s.neg(),a=a.neg()),[{a:r,b:o},{a:s,b:a}]},u.prototype._endoSplit=function(e){var t=this.endo.basis,n=t[0],r=t[1],i=r.b.mul(e).divRound(this.n),o=n.b.neg().mul(e).divRound(this.n),s=i.mul(n.a),a=o.mul(r.a),u=i.mul(n.b),c=o.mul(r.b);return{k1:e.sub(s).sub(a),k2:u.add(c).neg()}},u.prototype.pointFromX=function(e,t){(e=new i(e,16)).red||(e=e.toRed(this.red));var n=e.redSqr().redMul(e).redIAdd(e.redMul(this.a)).redIAdd(this.b),r=n.redSqrt();if(0!==r.redSqr().redSub(n).cmp(this.zero))throw new Error("invalid point");var o=r.fromRed().isOdd();return(t&&!o||!t&&o)&&(r=r.redNeg()),this.point(e,r)},u.prototype.validate=function(e){if(e.inf)return!0;var t=e.x,n=e.y,r=this.a.redMul(t),i=t.redSqr().redMul(t).redIAdd(r).redIAdd(this.b);return 0===n.redSqr().redISub(i).cmpn(0)},u.prototype._endoWnafMulAdd=function(e,t,n){for(var r=this._endoWnafT1,i=this._endoWnafT2,o=0;o<e.length;o++){var s=this._endoSplit(t[o]),a=e[o],u=a._getBeta();s.k1.negative&&(s.k1.ineg(),a=a.neg(!0)),s.k2.negative&&(s.k2.ineg(),u=u.neg(!0)),r[2*o]=a,r[2*o+1]=u,i[2*o]=s.k1,i[2*o+1]=s.k2}for(var c=this._wnafMulAdd(1,r,i,2*o,n),f=0;f<2*o;f++)r[f]=null,i[f]=null;return c},o(c,s.BasePoint),u.prototype.point=function(e,t,n){return new c(this,e,t,n)},u.prototype.pointFromJSON=function(e,t){return c.fromJSON(this,e,t)},c.prototype._getBeta=function(){if(this.curve.endo){var e=this.precomputed;if(e&&e.beta)return e.beta;var t=this.curve.point(this.x.redMul(this.curve.endo.beta),this.y);if(e){var n=this.curve,r=function(e){return n.point(e.x.redMul(n.endo.beta),e.y)};e.beta=t,t.precomputed={beta:null,naf:e.naf&&{wnd:e.naf.wnd,points:e.naf.points.map(r)},doubles:e.doubles&&{step:e.doubles.step,points:e.doubles.points.map(r)}}}return t}},c.prototype.toJSON=function(){return this.precomputed?[this.x,this.y,this.precomputed&&{doubles:this.precomputed.doubles&&{step:this.precomputed.doubles.step,points:this.precomputed.doubles.points.slice(1)},naf:this.precomputed.naf&&{wnd:this.precomputed.naf.wnd,points:this.precomputed.naf.points.slice(1)}}]:[this.x,this.y]},c.fromJSON=function(e,t,n){"string"==typeof t&&(t=JSON.parse(t));var r=e.point(t[0],t[1],n);if(!t[2])return r;function i(t){return e.point(t[0],t[1],n)}var o=t[2];return r.precomputed={beta:null,doubles:o.doubles&&{step:o.doubles.step,points:[r].concat(o.doubles.points.map(i))},naf:o.naf&&{wnd:o.naf.wnd,points:[r].concat(o.naf.points.map(i))}},r},c.prototype.inspect=function(){return this.isInfinity()?"<EC Point Infinity>":"<EC Point x: "+this.x.fromRed().toString(16,2)+" y: "+this.y.fromRed().toString(16,2)+">"},c.prototype.isInfinity=function(){return this.inf},c.prototype.add=function(e){if(this.inf)return e;if(e.inf)return this;if(this.eq(e))return this.dbl();if(this.neg().eq(e))return this.curve.point(null,null);if(0===this.x.cmp(e.x))return this.curve.point(null,null);var t=this.y.redSub(e.y);0!==t.cmpn(0)&&(t=t.redMul(this.x.redSub(e.x).redInvm()));var n=t.redSqr().redISub(this.x).redISub(e.x),r=t.redMul(this.x.redSub(n)).redISub(this.y);return this.curve.point(n,r)},c.prototype.dbl=function(){if(this.inf)return this;var e=this.y.redAdd(this.y);if(0===e.cmpn(0))return this.curve.point(null,null);var t=this.curve.a,n=this.x.redSqr(),r=e.redInvm(),i=n.redAdd(n).redIAdd(n).redIAdd(t).redMul(r),o=i.redSqr().redISub(this.x.redAdd(this.x)),s=i.redMul(this.x.redSub(o)).redISub(this.y);return this.curve.point(o,s)},c.prototype.getX=function(){return this.x.fromRed()},c.prototype.getY=function(){return this.y.fromRed()},c.prototype.mul=function(e){return e=new i(e,16),this.isInfinity()?this:this._hasDoubles(e)?this.curve._fixedNafMul(this,e):this.curve.endo?this.curve._endoWnafMulAdd([this],[e]):this.curve._wnafMul(this,e)},c.prototype.mulAdd=function(e,t,n){var r=[this,t],i=[e,n];return this.curve.endo?this.curve._endoWnafMulAdd(r,i):this.curve._wnafMulAdd(1,r,i,2)},c.prototype.jmulAdd=function(e,t,n){var r=[this,t],i=[e,n];return this.curve.endo?this.curve._endoWnafMulAdd(r,i,!0):this.curve._wnafMulAdd(1,r,i,2,!0)},c.prototype.eq=function(e){return this===e||this.inf===e.inf&&(this.inf||0===this.x.cmp(e.x)&&0===this.y.cmp(e.y))},c.prototype.neg=function(e){if(this.inf)return this;var t=this.curve.point(this.x,this.y.redNeg());if(e&&this.precomputed){var n=this.precomputed,r=function(e){return e.neg()};t.precomputed={naf:n.naf&&{wnd:n.naf.wnd,points:n.naf.points.map(r)},doubles:n.doubles&&{step:n.doubles.step,points:n.doubles.points.map(r)}}}return t},c.prototype.toJ=function(){return this.inf?this.curve.jpoint(null,null,null):this.curve.jpoint(this.x,this.y,this.curve.one)},o(f,s.BasePoint),u.prototype.jpoint=function(e,t,n){return new f(this,e,t,n)},f.prototype.toP=function(){if(this.isInfinity())return this.curve.point(null,null);var e=this.z.redInvm(),t=e.redSqr(),n=this.x.redMul(t),r=this.y.redMul(t).redMul(e);return this.curve.point(n,r)},f.prototype.neg=function(){return this.curve.jpoint(this.x,this.y.redNeg(),this.z)},f.prototype.add=function(e){if(this.isInfinity())return e;if(e.isInfinity())return this;var t=e.z.redSqr(),n=this.z.redSqr(),r=this.x.redMul(t),i=e.x.redMul(n),o=this.y.redMul(t.redMul(e.z)),s=e.y.redMul(n.redMul(this.z)),a=r.redSub(i),u=o.redSub(s);if(0===a.cmpn(0))return 0!==u.cmpn(0)?this.curve.jpoint(null,null,null):this.dbl();var c=a.redSqr(),f=c.redMul(a),l=r.redMul(c),d=u.redSqr().redIAdd(f).redISub(l).redISub(l),h=u.redMul(l.redISub(d)).redISub(o.redMul(f)),p=this.z.redMul(e.z).redMul(a);return this.curve.jpoint(d,h,p)},f.prototype.mixedAdd=function(e){if(this.isInfinity())return e.toJ();if(e.isInfinity())return this;var t=this.z.redSqr(),n=this.x,r=e.x.redMul(t),i=this.y,o=e.y.redMul(t).redMul(this.z),s=n.redSub(r),a=i.redSub(o);if(0===s.cmpn(0))return 0!==a.cmpn(0)?this.curve.jpoint(null,null,null):this.dbl();var u=s.redSqr(),c=u.redMul(s),f=n.redMul(u),l=a.redSqr().redIAdd(c).redISub(f).redISub(f),d=a.redMul(f.redISub(l)).redISub(i.redMul(c)),h=this.z.redMul(s);return this.curve.jpoint(l,d,h)},f.prototype.dblp=function(e){if(0===e)return this;if(this.isInfinity())return this;if(!e)return this.dbl();var t;if(this.curve.zeroA||this.curve.threeA){var n=this;for(t=0;t<e;t++)n=n.dbl();return n}var r=this.curve.a,i=this.curve.tinv,o=this.x,s=this.y,a=this.z,u=a.redSqr().redSqr(),c=s.redAdd(s);for(t=0;t<e;t++){var f=o.redSqr(),l=c.redSqr(),d=l.redSqr(),h=f.redAdd(f).redIAdd(f).redIAdd(r.redMul(u)),p=o.redMul(l),v=h.redSqr().redISub(p.redAdd(p)),g=p.redISub(v),m=h.redMul(g);m=m.redIAdd(m).redISub(d);var b=c.redMul(a);t+1<e&&(u=u.redMul(d)),o=v,a=b,c=m}return this.curve.jpoint(o,c.redMul(i),a)},f.prototype.dbl=function(){return this.isInfinity()?this:this.curve.zeroA?this._zeroDbl():this.curve.threeA?this._threeDbl():this._dbl()},f.prototype._zeroDbl=function(){var e,t,n;if(this.zOne){var r=this.x.redSqr(),i=this.y.redSqr(),o=i.redSqr(),s=this.x.redAdd(i).redSqr().redISub(r).redISub(o);s=s.redIAdd(s);var a=r.redAdd(r).redIAdd(r),u=a.redSqr().redISub(s).redISub(s),c=o.redIAdd(o);c=(c=c.redIAdd(c)).redIAdd(c),e=u,t=a.redMul(s.redISub(u)).redISub(c),n=this.y.redAdd(this.y)}else{var f=this.x.redSqr(),l=this.y.redSqr(),d=l.redSqr(),h=this.x.redAdd(l).redSqr().redISub(f).redISub(d);h=h.redIAdd(h);var p=f.redAdd(f).redIAdd(f),v=p.redSqr(),g=d.redIAdd(d);g=(g=g.redIAdd(g)).redIAdd(g),e=v.redISub(h).redISub(h),t=p.redMul(h.redISub(e)).redISub(g),n=(n=this.y.redMul(this.z)).redIAdd(n)}return this.curve.jpoint(e,t,n)},f.prototype._threeDbl=function(){var e,t,n;if(this.zOne){var r=this.x.redSqr(),i=this.y.redSqr(),o=i.redSqr(),s=this.x.redAdd(i).redSqr().redISub(r).redISub(o);s=s.redIAdd(s);var a=r.redAdd(r).redIAdd(r).redIAdd(this.curve.a),u=a.redSqr().redISub(s).redISub(s);e=u;var c=o.redIAdd(o);c=(c=c.redIAdd(c)).redIAdd(c),t=a.redMul(s.redISub(u)).redISub(c),n=this.y.redAdd(this.y)}else{var f=this.z.redSqr(),l=this.y.redSqr(),d=this.x.redMul(l),h=this.x.redSub(f).redMul(this.x.redAdd(f));h=h.redAdd(h).redIAdd(h);var p=d.redIAdd(d),v=(p=p.redIAdd(p)).redAdd(p);e=h.redSqr().redISub(v),n=this.y.redAdd(this.z).redSqr().redISub(l).redISub(f);var g=l.redSqr();g=(g=(g=g.redIAdd(g)).redIAdd(g)).redIAdd(g),t=h.redMul(p.redISub(e)).redISub(g)}return this.curve.jpoint(e,t,n)},f.prototype._dbl=function(){var e=this.curve.a,t=this.x,n=this.y,r=this.z,i=r.redSqr().redSqr(),o=t.redSqr(),s=n.redSqr(),a=o.redAdd(o).redIAdd(o).redIAdd(e.redMul(i)),u=t.redAdd(t),c=(u=u.redIAdd(u)).redMul(s),f=a.redSqr().redISub(c.redAdd(c)),l=c.redISub(f),d=s.redSqr();d=(d=(d=d.redIAdd(d)).redIAdd(d)).redIAdd(d);var h=a.redMul(l).redISub(d),p=n.redAdd(n).redMul(r);return this.curve.jpoint(f,h,p)},f.prototype.trpl=function(){if(!this.curve.zeroA)return this.dbl().add(this);var e=this.x.redSqr(),t=this.y.redSqr(),n=this.z.redSqr(),r=t.redSqr(),i=e.redAdd(e).redIAdd(e),o=i.redSqr(),s=this.x.redAdd(t).redSqr().redISub(e).redISub(r),a=(s=(s=(s=s.redIAdd(s)).redAdd(s).redIAdd(s)).redISub(o)).redSqr(),u=r.redIAdd(r);u=(u=(u=u.redIAdd(u)).redIAdd(u)).redIAdd(u);var c=i.redIAdd(s).redSqr().redISub(o).redISub(a).redISub(u),f=t.redMul(c);f=(f=f.redIAdd(f)).redIAdd(f);var l=this.x.redMul(a).redISub(f);l=(l=l.redIAdd(l)).redIAdd(l);var d=this.y.redMul(c.redMul(u.redISub(c)).redISub(s.redMul(a)));d=(d=(d=d.redIAdd(d)).redIAdd(d)).redIAdd(d);var h=this.z.redAdd(s).redSqr().redISub(n).redISub(a);return this.curve.jpoint(l,d,h)},f.prototype.mul=function(e,t){return e=new i(e,t),this.curve._wnafMul(this,e)},f.prototype.eq=function(e){if("affine"===e.type)return this.eq(e.toJ());if(this===e)return!0;var t=this.z.redSqr(),n=e.z.redSqr();if(0!==this.x.redMul(n).redISub(e.x.redMul(t)).cmpn(0))return!1;var r=t.redMul(this.z),i=n.redMul(e.z);return 0===this.y.redMul(i).redISub(e.y.redMul(r)).cmpn(0)},f.prototype.eqXToP=function(e){var t=this.z.redSqr(),n=e.toRed(this.curve.red).redMul(t);if(0===this.x.cmp(n))return!0;for(var r=e.clone(),i=this.curve.redN.redMul(t);;){if(r.iadd(this.curve.n),r.cmp(this.curve.p)>=0)return!1;if(n.redIAdd(i),0===this.x.cmp(n))return!0}},f.prototype.inspect=function(){return this.isInfinity()?"<EC JPoint Infinity>":"<EC JPoint x: "+this.x.toString(16,2)+" y: "+this.y.toString(16,2)+" z: "+this.z.toString(16,2)+">"},f.prototype.isInfinity=function(){return 0===this.z.cmpn(0)}},function(e,t,n){"use strict";var r=n(29),i=n(7),o=n(95),s=n(47);function a(e){o.call(this,"mont",e),this.a=new r(e.a,16).toRed(this.red),this.b=new r(e.b,16).toRed(this.red),this.i4=new r(4).toRed(this.red).redInvm(),this.two=new r(2).toRed(this.red),this.a24=this.i4.redMul(this.a.redAdd(this.two))}function u(e,t,n){o.BasePoint.call(this,e,"projective"),null===t&&null===n?(this.x=this.curve.one,this.z=this.curve.zero):(this.x=new r(t,16),this.z=new r(n,16),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)))}i(a,o),e.exports=a,a.prototype.validate=function(e){var t=e.normalize().x,n=t.redSqr(),r=n.redMul(t).redAdd(n.redMul(this.a)).redAdd(t);return 0===r.redSqrt().redSqr().cmp(r)},i(u,o.BasePoint),a.prototype.decodePoint=function(e,t){return this.point(s.toArray(e,t),1)},a.prototype.point=function(e,t){return new u(this,e,t)},a.prototype.pointFromJSON=function(e){return u.fromJSON(this,e)},u.prototype.precompute=function(){},u.prototype._encode=function(){return this.getX().toArray("be",this.curve.p.byteLength())},u.fromJSON=function(e,t){return new u(e,t[0],t[1]||e.one)},u.prototype.inspect=function(){return this.isInfinity()?"<EC Point Infinity>":"<EC Point x: "+this.x.fromRed().toString(16,2)+" z: "+this.z.fromRed().toString(16,2)+">"},u.prototype.isInfinity=function(){return 0===this.z.cmpn(0)},u.prototype.dbl=function(){var e=this.x.redAdd(this.z).redSqr(),t=this.x.redSub(this.z).redSqr(),n=e.redSub(t),r=e.redMul(t),i=n.redMul(t.redAdd(this.curve.a24.redMul(n)));return this.curve.point(r,i)},u.prototype.add=function(){throw new Error("Not supported on Montgomery curve")},u.prototype.diffAdd=function(e,t){var n=this.x.redAdd(this.z),r=this.x.redSub(this.z),i=e.x.redAdd(e.z),o=e.x.redSub(e.z).redMul(n),s=i.redMul(r),a=t.z.redMul(o.redAdd(s).redSqr()),u=t.x.redMul(o.redISub(s).redSqr());return this.curve.point(a,u)},u.prototype.mul=function(e){for(var t=e.clone(),n=this,r=this.curve.point(null,null),i=[];0!==t.cmpn(0);t.iushrn(1))i.push(t.andln(1));for(var o=i.length-1;o>=0;o--)0===i[o]?(n=n.diffAdd(r,this),r=r.dbl()):(r=n.diffAdd(r,this),n=n.dbl());return r},u.prototype.mulAdd=function(){throw new Error("Not supported on Montgomery curve")},u.prototype.jumlAdd=function(){throw new Error("Not supported on Montgomery curve")},u.prototype.eq=function(e){return 0===this.getX().cmp(e.getX())},u.prototype.normalize=function(){return this.x=this.x.redMul(this.z.redInvm()),this.z=this.curve.one,this},u.prototype.getX=function(){return this.normalize(),this.x.fromRed()}},function(e,t,n){"use strict";var r=n(47),i=n(29),o=n(7),s=n(95),a=r.assert;function u(e){this.twisted=1!=(0|e.a),this.mOneA=this.twisted&&-1==(0|e.a),this.extended=this.mOneA,s.call(this,"edwards",e),this.a=new i(e.a,16).umod(this.red.m),this.a=this.a.toRed(this.red),this.c=new i(e.c,16).toRed(this.red),this.c2=this.c.redSqr(),this.d=new i(e.d,16).toRed(this.red),this.dd=this.d.redAdd(this.d),a(!this.twisted||0===this.c.fromRed().cmpn(1)),this.oneC=1==(0|e.c)}function c(e,t,n,r,o){s.BasePoint.call(this,e,"projective"),null===t&&null===n&&null===r?(this.x=this.curve.zero,this.y=this.curve.one,this.z=this.curve.one,this.t=this.curve.zero,this.zOne=!0):(this.x=new i(t,16),this.y=new i(n,16),this.z=r?new i(r,16):this.curve.one,this.t=o&&new i(o,16),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)),this.t&&!this.t.red&&(this.t=this.t.toRed(this.curve.red)),this.zOne=this.z===this.curve.one,this.curve.extended&&!this.t&&(this.t=this.x.redMul(this.y),this.zOne||(this.t=this.t.redMul(this.z.redInvm()))))}o(u,s),e.exports=u,u.prototype._mulA=function(e){return this.mOneA?e.redNeg():this.a.redMul(e)},u.prototype._mulC=function(e){return this.oneC?e:this.c.redMul(e)},u.prototype.jpoint=function(e,t,n,r){return this.point(e,t,n,r)},u.prototype.pointFromX=function(e,t){(e=new i(e,16)).red||(e=e.toRed(this.red));var n=e.redSqr(),r=this.c2.redSub(this.a.redMul(n)),o=this.one.redSub(this.c2.redMul(this.d).redMul(n)),s=r.redMul(o.redInvm()),a=s.redSqrt();if(0!==a.redSqr().redSub(s).cmp(this.zero))throw new Error("invalid point");var u=a.fromRed().isOdd();return(t&&!u||!t&&u)&&(a=a.redNeg()),this.point(e,a)},u.prototype.pointFromY=function(e,t){(e=new i(e,16)).red||(e=e.toRed(this.red));var n=e.redSqr(),r=n.redSub(this.c2),o=n.redMul(this.d).redMul(this.c2).redSub(this.a),s=r.redMul(o.redInvm());if(0===s.cmp(this.zero)){if(t)throw new Error("invalid point");return this.point(this.zero,e)}var a=s.redSqrt();if(0!==a.redSqr().redSub(s).cmp(this.zero))throw new Error("invalid point");return a.fromRed().isOdd()!==t&&(a=a.redNeg()),this.point(a,e)},u.prototype.validate=function(e){if(e.isInfinity())return!0;e.normalize();var t=e.x.redSqr(),n=e.y.redSqr(),r=t.redMul(this.a).redAdd(n),i=this.c2.redMul(this.one.redAdd(this.d.redMul(t).redMul(n)));return 0===r.cmp(i)},o(c,s.BasePoint),u.prototype.pointFromJSON=function(e){return c.fromJSON(this,e)},u.prototype.point=function(e,t,n,r){return new c(this,e,t,n,r)},c.fromJSON=function(e,t){return new c(e,t[0],t[1],t[2])},c.prototype.inspect=function(){return this.isInfinity()?"<EC Point Infinity>":"<EC Point x: "+this.x.fromRed().toString(16,2)+" y: "+this.y.fromRed().toString(16,2)+" z: "+this.z.fromRed().toString(16,2)+">"},c.prototype.isInfinity=function(){return 0===this.x.cmpn(0)&&(0===this.y.cmp(this.z)||this.zOne&&0===this.y.cmp(this.curve.c))},c.prototype._extDbl=function(){var e=this.x.redSqr(),t=this.y.redSqr(),n=this.z.redSqr();n=n.redIAdd(n);var r=this.curve._mulA(e),i=this.x.redAdd(this.y).redSqr().redISub(e).redISub(t),o=r.redAdd(t),s=o.redSub(n),a=r.redSub(t),u=i.redMul(s),c=o.redMul(a),f=i.redMul(a),l=s.redMul(o);return this.curve.point(u,c,l,f)},c.prototype._projDbl=function(){var e,t,n,r,i,o,s=this.x.redAdd(this.y).redSqr(),a=this.x.redSqr(),u=this.y.redSqr();if(this.curve.twisted){var c=(r=this.curve._mulA(a)).redAdd(u);this.zOne?(e=s.redSub(a).redSub(u).redMul(c.redSub(this.curve.two)),t=c.redMul(r.redSub(u)),n=c.redSqr().redSub(c).redSub(c)):(i=this.z.redSqr(),o=c.redSub(i).redISub(i),e=s.redSub(a).redISub(u).redMul(o),t=c.redMul(r.redSub(u)),n=c.redMul(o))}else r=a.redAdd(u),i=this.curve._mulC(this.z).redSqr(),o=r.redSub(i).redSub(i),e=this.curve._mulC(s.redISub(r)).redMul(o),t=this.curve._mulC(r).redMul(a.redISub(u)),n=r.redMul(o);return this.curve.point(e,t,n)},c.prototype.dbl=function(){return this.isInfinity()?this:this.curve.extended?this._extDbl():this._projDbl()},c.prototype._extAdd=function(e){var t=this.y.redSub(this.x).redMul(e.y.redSub(e.x)),n=this.y.redAdd(this.x).redMul(e.y.redAdd(e.x)),r=this.t.redMul(this.curve.dd).redMul(e.t),i=this.z.redMul(e.z.redAdd(e.z)),o=n.redSub(t),s=i.redSub(r),a=i.redAdd(r),u=n.redAdd(t),c=o.redMul(s),f=a.redMul(u),l=o.redMul(u),d=s.redMul(a);return this.curve.point(c,f,d,l)},c.prototype._projAdd=function(e){var t,n,r=this.z.redMul(e.z),i=r.redSqr(),o=this.x.redMul(e.x),s=this.y.redMul(e.y),a=this.curve.d.redMul(o).redMul(s),u=i.redSub(a),c=i.redAdd(a),f=this.x.redAdd(this.y).redMul(e.x.redAdd(e.y)).redISub(o).redISub(s),l=r.redMul(u).redMul(f);return this.curve.twisted?(t=r.redMul(c).redMul(s.redSub(this.curve._mulA(o))),n=u.redMul(c)):(t=r.redMul(c).redMul(s.redSub(o)),n=this.curve._mulC(u).redMul(c)),this.curve.point(l,t,n)},c.prototype.add=function(e){return this.isInfinity()?e:e.isInfinity()?this:this.curve.extended?this._extAdd(e):this._projAdd(e)},c.prototype.mul=function(e){return this._hasDoubles(e)?this.curve._fixedNafMul(this,e):this.curve._wnafMul(this,e)},c.prototype.mulAdd=function(e,t,n){return this.curve._wnafMulAdd(1,[this,t],[e,n],2,!1)},c.prototype.jmulAdd=function(e,t,n){return this.curve._wnafMulAdd(1,[this,t],[e,n],2,!0)},c.prototype.normalize=function(){if(this.zOne)return this;var e=this.z.redInvm();return this.x=this.x.redMul(e),this.y=this.y.redMul(e),this.t&&(this.t=this.t.redMul(e)),this.z=this.curve.one,this.zOne=!0,this},c.prototype.neg=function(){return this.curve.point(this.x.redNeg(),this.y,this.z,this.t&&this.t.redNeg())},c.prototype.getX=function(){return this.normalize(),this.x.fromRed()},c.prototype.getY=function(){return this.normalize(),this.y.fromRed()},c.prototype.eq=function(e){return this===e||0===this.getX().cmp(e.getX())&&0===this.getY().cmp(e.getY())},c.prototype.eqXToP=function(e){var t=e.toRed(this.curve.red).redMul(this.z);if(0===this.x.cmp(t))return!0;for(var n=e.clone(),r=this.curve.redN.redMul(this.z);;){if(n.iadd(this.curve.n),n.cmp(this.curve.p)>=0)return!1;if(t.redIAdd(r),0===this.x.cmp(t))return!0}},c.prototype.toP=c.prototype.normalize,c.prototype.mixedAdd=c.prototype.add},function(e,t,n){"use strict";t.sha1=n(336),t.sha224=n(337),t.sha256=n(201),t.sha384=n(338),t.sha512=n(202)},function(e,t,n){"use strict";var r=n(51),i=n(82),o=n(200),s=r.rotl32,a=r.sum32,u=r.sum32_5,c=o.ft_1,f=i.BlockHash,l=[1518500249,1859775393,2400959708,3395469782];function d(){if(!(this instanceof d))return new d;f.call(this),this.h=[1732584193,4023233417,2562383102,271733878,3285377520],this.W=new Array(80)}r.inherits(d,f),e.exports=d,d.blockSize=512,d.outSize=160,d.hmacStrength=80,d.padLength=64,d.prototype._update=function(e,t){for(var n=this.W,r=0;r<16;r++)n[r]=e[t+r];for(;r<n.length;r++)n[r]=s(n[r-3]^n[r-8]^n[r-14]^n[r-16],1);var i=this.h[0],o=this.h[1],f=this.h[2],d=this.h[3],h=this.h[4];for(r=0;r<n.length;r++){var p=~~(r/20),v=u(s(i,5),c(p,o,f,d),h,n[r],l[p]);h=d,d=f,f=s(o,30),o=i,i=v}this.h[0]=a(this.h[0],i),this.h[1]=a(this.h[1],o),this.h[2]=a(this.h[2],f),this.h[3]=a(this.h[3],d),this.h[4]=a(this.h[4],h)},d.prototype._digest=function(e){return"hex"===e?r.toHex32(this.h,"big"):r.split32(this.h,"big")}},function(e,t,n){"use strict";var r=n(51),i=n(201);function o(){if(!(this instanceof o))return new o;i.call(this),this.h=[3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428]}r.inherits(o,i),e.exports=o,o.blockSize=512,o.outSize=224,o.hmacStrength=192,o.padLength=64,o.prototype._digest=function(e){return"hex"===e?r.toHex32(this.h.slice(0,7),"big"):r.split32(this.h.slice(0,7),"big")}},function(e,t,n){"use strict";var r=n(51),i=n(202);function o(){if(!(this instanceof o))return new o;i.call(this),this.h=[3418070365,3238371032,1654270250,914150663,2438529370,812702999,355462360,4144912697,1731405415,4290775857,2394180231,1750603025,3675008525,1694076839,1203062813,3204075428]}r.inherits(o,i),e.exports=o,o.blockSize=1024,o.outSize=384,o.hmacStrength=192,o.padLength=128,o.prototype._digest=function(e){return"hex"===e?r.toHex32(this.h.slice(0,12),"big"):r.split32(this.h.slice(0,12),"big")}},function(e,t,n){"use strict";var r=n(51),i=n(82),o=r.rotl32,s=r.sum32,a=r.sum32_3,u=r.sum32_4,c=i.BlockHash;function f(){if(!(this instanceof f))return new f;c.call(this),this.h=[1732584193,4023233417,2562383102,271733878,3285377520],this.endian="little"}function l(e,t,n,r){return e<=15?t^n^r:e<=31?t&n|~t&r:e<=47?(t|~n)^r:e<=63?t&r|n&~r:t^(n|~r)}function d(e){return e<=15?0:e<=31?1518500249:e<=47?1859775393:e<=63?2400959708:2840853838}function h(e){return e<=15?1352829926:e<=31?1548603684:e<=47?1836072691:e<=63?2053994217:0}r.inherits(f,c),t.ripemd160=f,f.blockSize=512,f.outSize=160,f.hmacStrength=192,f.padLength=64,f.prototype._update=function(e,t){for(var n=this.h[0],r=this.h[1],i=this.h[2],c=this.h[3],f=this.h[4],b=n,y=r,w=i,_=c,S=f,E=0;E<80;E++){var M=s(o(u(n,l(E,r,i,c),e[p[E]+t],d(E)),g[E]),f);n=f,f=c,c=o(i,10),i=r,r=M,M=s(o(u(b,l(79-E,y,w,_),e[v[E]+t],h(E)),m[E]),S),b=S,S=_,_=o(w,10),w=y,y=M}M=a(this.h[1],i,_),this.h[1]=a(this.h[2],c,S),this.h[2]=a(this.h[3],f,b),this.h[3]=a(this.h[4],n,y),this.h[4]=a(this.h[0],r,w),this.h[0]=M},f.prototype._digest=function(e){return"hex"===e?r.toHex32(this.h,"little"):r.split32(this.h,"little")};var p=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13],v=[5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11],g=[11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6],m=[8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]},function(e,t,n){"use strict";var r=n(51),i=n(46);function o(e,t,n){if(!(this instanceof o))return new o(e,t,n);this.Hash=e,this.blockSize=e.blockSize/8,this.outSize=e.outSize/8,this.inner=null,this.outer=null,this._init(r.toArray(t,n))}e.exports=o,o.prototype._init=function(e){e.length>this.blockSize&&(e=(new this.Hash).update(e).digest()),i(e.length<=this.blockSize);for(var t=e.length;t<this.blockSize;t++)e.push(0);for(t=0;t<e.length;t++)e[t]^=54;for(this.inner=(new this.Hash).update(e),t=0;t<e.length;t++)e[t]^=106;this.outer=(new this.Hash).update(e)},o.prototype.update=function(e,t){return this.inner.update(e,t),this},o.prototype.digest=function(e){return this.outer.update(this.inner.digest()),this.outer.digest(e)}},function(e,t){e.exports={doubles:{step:4,points:[["e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a","f7e3507399e595929db99f34f57937101296891e44d23f0be1f32cce69616821"],["8282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508","11f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf"],["175e159f728b865a72f99cc6c6fc846de0b93833fd2222ed73fce5b551e5b739","d3506e0d9e3c79eba4ef97a51ff71f5eacb5955add24345c6efa6ffee9fed695"],["363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640","4e273adfc732221953b445397f3363145b9a89008199ecb62003c7f3bee9de9"],["8b4b5f165df3c2be8c6244b5b745638843e4a781a15bcd1b69f79a55dffdf80c","4aad0a6f68d308b4b3fbd7813ab0da04f9e336546162ee56b3eff0c65fd4fd36"],["723cbaa6e5db996d6bf771c00bd548c7b700dbffa6c0e77bcb6115925232fcda","96e867b5595cc498a921137488824d6e2660a0653779494801dc069d9eb39f5f"],["eebfa4d493bebf98ba5feec812c2d3b50947961237a919839a533eca0e7dd7fa","5d9a8ca3970ef0f269ee7edaf178089d9ae4cdc3a711f712ddfd4fdae1de8999"],["100f44da696e71672791d0a09b7bde459f1215a29b3c03bfefd7835b39a48db0","cdd9e13192a00b772ec8f3300c090666b7ff4a18ff5195ac0fbd5cd62bc65a09"],["e1031be262c7ed1b1dc9227a4a04c017a77f8d4464f3b3852c8acde6e534fd2d","9d7061928940405e6bb6a4176597535af292dd419e1ced79a44f18f29456a00d"],["feea6cae46d55b530ac2839f143bd7ec5cf8b266a41d6af52d5e688d9094696d","e57c6b6c97dce1bab06e4e12bf3ecd5c981c8957cc41442d3155debf18090088"],["da67a91d91049cdcb367be4be6ffca3cfeed657d808583de33fa978bc1ec6cb1","9bacaa35481642bc41f463f7ec9780e5dec7adc508f740a17e9ea8e27a68be1d"],["53904faa0b334cdda6e000935ef22151ec08d0f7bb11069f57545ccc1a37b7c0","5bc087d0bc80106d88c9eccac20d3c1c13999981e14434699dcb096b022771c8"],["8e7bcd0bd35983a7719cca7764ca906779b53a043a9b8bcaeff959f43ad86047","10b7770b2a3da4b3940310420ca9514579e88e2e47fd68b3ea10047e8460372a"],["385eed34c1cdff21e6d0818689b81bde71a7f4f18397e6690a841e1599c43862","283bebc3e8ea23f56701de19e9ebf4576b304eec2086dc8cc0458fe5542e5453"],["6f9d9b803ecf191637c73a4413dfa180fddf84a5947fbc9c606ed86c3fac3a7","7c80c68e603059ba69b8e2a30e45c4d47ea4dd2f5c281002d86890603a842160"],["3322d401243c4e2582a2147c104d6ecbf774d163db0f5e5313b7e0e742d0e6bd","56e70797e9664ef5bfb019bc4ddaf9b72805f63ea2873af624f3a2e96c28b2a0"],["85672c7d2de0b7da2bd1770d89665868741b3f9af7643397721d74d28134ab83","7c481b9b5b43b2eb6374049bfa62c2e5e77f17fcc5298f44c8e3094f790313a6"],["948bf809b1988a46b06c9f1919413b10f9226c60f668832ffd959af60c82a0a","53a562856dcb6646dc6b74c5d1c3418c6d4dff08c97cd2bed4cb7f88d8c8e589"],["6260ce7f461801c34f067ce0f02873a8f1b0e44dfc69752accecd819f38fd8e8","bc2da82b6fa5b571a7f09049776a1ef7ecd292238051c198c1a84e95b2b4ae17"],["e5037de0afc1d8d43d8348414bbf4103043ec8f575bfdc432953cc8d2037fa2d","4571534baa94d3b5f9f98d09fb990bddbd5f5b03ec481f10e0e5dc841d755bda"],["e06372b0f4a207adf5ea905e8f1771b4e7e8dbd1c6a6c5b725866a0ae4fce725","7a908974bce18cfe12a27bb2ad5a488cd7484a7787104870b27034f94eee31dd"],["213c7a715cd5d45358d0bbf9dc0ce02204b10bdde2a3f58540ad6908d0559754","4b6dad0b5ae462507013ad06245ba190bb4850f5f36a7eeddff2c27534b458f2"],["4e7c272a7af4b34e8dbb9352a5419a87e2838c70adc62cddf0cc3a3b08fbd53c","17749c766c9d0b18e16fd09f6def681b530b9614bff7dd33e0b3941817dcaae6"],["fea74e3dbe778b1b10f238ad61686aa5c76e3db2be43057632427e2840fb27b6","6e0568db9b0b13297cf674deccb6af93126b596b973f7b77701d3db7f23cb96f"],["76e64113f677cf0e10a2570d599968d31544e179b760432952c02a4417bdde39","c90ddf8dee4e95cf577066d70681f0d35e2a33d2b56d2032b4b1752d1901ac01"],["c738c56b03b2abe1e8281baa743f8f9a8f7cc643df26cbee3ab150242bcbb891","893fb578951ad2537f718f2eacbfbbbb82314eef7880cfe917e735d9699a84c3"],["d895626548b65b81e264c7637c972877d1d72e5f3a925014372e9f6588f6c14b","febfaa38f2bc7eae728ec60818c340eb03428d632bb067e179363ed75d7d991f"],["b8da94032a957518eb0f6433571e8761ceffc73693e84edd49150a564f676e03","2804dfa44805a1e4d7c99cc9762808b092cc584d95ff3b511488e4e74efdf6e7"],["e80fea14441fb33a7d8adab9475d7fab2019effb5156a792f1a11778e3c0df5d","eed1de7f638e00771e89768ca3ca94472d155e80af322ea9fcb4291b6ac9ec78"],["a301697bdfcd704313ba48e51d567543f2a182031efd6915ddc07bbcc4e16070","7370f91cfb67e4f5081809fa25d40f9b1735dbf7c0a11a130c0d1a041e177ea1"],["90ad85b389d6b936463f9d0512678de208cc330b11307fffab7ac63e3fb04ed4","e507a3620a38261affdcbd9427222b839aefabe1582894d991d4d48cb6ef150"],["8f68b9d2f63b5f339239c1ad981f162ee88c5678723ea3351b7b444c9ec4c0da","662a9f2dba063986de1d90c2b6be215dbbea2cfe95510bfdf23cbf79501fff82"],["e4f3fb0176af85d65ff99ff9198c36091f48e86503681e3e6686fd5053231e11","1e63633ad0ef4f1c1661a6d0ea02b7286cc7e74ec951d1c9822c38576feb73bc"],["8c00fa9b18ebf331eb961537a45a4266c7034f2f0d4e1d0716fb6eae20eae29e","efa47267fea521a1a9dc343a3736c974c2fadafa81e36c54e7d2a4c66702414b"],["e7a26ce69dd4829f3e10cec0a9e98ed3143d084f308b92c0997fddfc60cb3e41","2a758e300fa7984b471b006a1aafbb18d0a6b2c0420e83e20e8a9421cf2cfd51"],["b6459e0ee3662ec8d23540c223bcbdc571cbcb967d79424f3cf29eb3de6b80ef","67c876d06f3e06de1dadf16e5661db3c4b3ae6d48e35b2ff30bf0b61a71ba45"],["d68a80c8280bb840793234aa118f06231d6f1fc67e73c5a5deda0f5b496943e8","db8ba9fff4b586d00c4b1f9177b0e28b5b0e7b8f7845295a294c84266b133120"],["324aed7df65c804252dc0270907a30b09612aeb973449cea4095980fc28d3d5d","648a365774b61f2ff130c0c35aec1f4f19213b0c7e332843967224af96ab7c84"],["4df9c14919cde61f6d51dfdbe5fee5dceec4143ba8d1ca888e8bd373fd054c96","35ec51092d8728050974c23a1d85d4b5d506cdc288490192ebac06cad10d5d"],["9c3919a84a474870faed8a9c1cc66021523489054d7f0308cbfc99c8ac1f98cd","ddb84f0f4a4ddd57584f044bf260e641905326f76c64c8e6be7e5e03d4fc599d"],["6057170b1dd12fdf8de05f281d8e06bb91e1493a8b91d4cc5a21382120a959e5","9a1af0b26a6a4807add9a2daf71df262465152bc3ee24c65e899be932385a2a8"],["a576df8e23a08411421439a4518da31880cef0fba7d4df12b1a6973eecb94266","40a6bf20e76640b2c92b97afe58cd82c432e10a7f514d9f3ee8be11ae1b28ec8"],["7778a78c28dec3e30a05fe9629de8c38bb30d1f5cf9a3a208f763889be58ad71","34626d9ab5a5b22ff7098e12f2ff580087b38411ff24ac563b513fc1fd9f43ac"],["928955ee637a84463729fd30e7afd2ed5f96274e5ad7e5cb09eda9c06d903ac","c25621003d3f42a827b78a13093a95eeac3d26efa8a8d83fc5180e935bcd091f"],["85d0fef3ec6db109399064f3a0e3b2855645b4a907ad354527aae75163d82751","1f03648413a38c0be29d496e582cf5663e8751e96877331582c237a24eb1f962"],["ff2b0dce97eece97c1c9b6041798b85dfdfb6d8882da20308f5404824526087e","493d13fef524ba188af4c4dc54d07936c7b7ed6fb90e2ceb2c951e01f0c29907"],["827fbbe4b1e880ea9ed2b2e6301b212b57f1ee148cd6dd28780e5e2cf856e241","c60f9c923c727b0b71bef2c67d1d12687ff7a63186903166d605b68baec293ec"],["eaa649f21f51bdbae7be4ae34ce6e5217a58fdce7f47f9aa7f3b58fa2120e2b3","be3279ed5bbbb03ac69a80f89879aa5a01a6b965f13f7e59d47a5305ba5ad93d"],["e4a42d43c5cf169d9391df6decf42ee541b6d8f0c9a137401e23632dda34d24f","4d9f92e716d1c73526fc99ccfb8ad34ce886eedfa8d8e4f13a7f7131deba9414"],["1ec80fef360cbdd954160fadab352b6b92b53576a88fea4947173b9d4300bf19","aeefe93756b5340d2f3a4958a7abbf5e0146e77f6295a07b671cdc1cc107cefd"],["146a778c04670c2f91b00af4680dfa8bce3490717d58ba889ddb5928366642be","b318e0ec3354028add669827f9d4b2870aaa971d2f7e5ed1d0b297483d83efd0"],["fa50c0f61d22e5f07e3acebb1aa07b128d0012209a28b9776d76a8793180eef9","6b84c6922397eba9b72cd2872281a68a5e683293a57a213b38cd8d7d3f4f2811"],["da1d61d0ca721a11b1a5bf6b7d88e8421a288ab5d5bba5220e53d32b5f067ec2","8157f55a7c99306c79c0766161c91e2966a73899d279b48a655fba0f1ad836f1"],["a8e282ff0c9706907215ff98e8fd416615311de0446f1e062a73b0610d064e13","7f97355b8db81c09abfb7f3c5b2515888b679a3e50dd6bd6cef7c73111f4cc0c"],["174a53b9c9a285872d39e56e6913cab15d59b1fa512508c022f382de8319497c","ccc9dc37abfc9c1657b4155f2c47f9e6646b3a1d8cb9854383da13ac079afa73"],["959396981943785c3d3e57edf5018cdbe039e730e4918b3d884fdff09475b7ba","2e7e552888c331dd8ba0386a4b9cd6849c653f64c8709385e9b8abf87524f2fd"],["d2a63a50ae401e56d645a1153b109a8fcca0a43d561fba2dbb51340c9d82b151","e82d86fb6443fcb7565aee58b2948220a70f750af484ca52d4142174dcf89405"],["64587e2335471eb890ee7896d7cfdc866bacbdbd3839317b3436f9b45617e073","d99fcdd5bf6902e2ae96dd6447c299a185b90a39133aeab358299e5e9faf6589"],["8481bde0e4e4d885b3a546d3e549de042f0aa6cea250e7fd358d6c86dd45e458","38ee7b8cba5404dd84a25bf39cecb2ca900a79c42b262e556d64b1b59779057e"],["13464a57a78102aa62b6979ae817f4637ffcfed3c4b1ce30bcd6303f6caf666b","69be159004614580ef7e433453ccb0ca48f300a81d0942e13f495a907f6ecc27"],["bc4a9df5b713fe2e9aef430bcc1dc97a0cd9ccede2f28588cada3a0d2d83f366","d3a81ca6e785c06383937adf4b798caa6e8a9fbfa547b16d758d666581f33c1"],["8c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa","40a30463a3305193378fedf31f7cc0eb7ae784f0451cb9459e71dc73cbef9482"],["8ea9666139527a8c1dd94ce4f071fd23c8b350c5a4bb33748c4ba111faccae0","620efabbc8ee2782e24e7c0cfb95c5d735b783be9cf0f8e955af34a30e62b945"],["dd3625faef5ba06074669716bbd3788d89bdde815959968092f76cc4eb9a9787","7a188fa3520e30d461da2501045731ca941461982883395937f68d00c644a573"],["f710d79d9eb962297e4f6232b40e8f7feb2bc63814614d692c12de752408221e","ea98e67232d3b3295d3b535532115ccac8612c721851617526ae47a9c77bfc82"]]},naf:{wnd:7,points:[["f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9","388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672"],["2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4","d8ac222636e5e3d6d4dba9dda6c9c426f788271bab0d6840dca87d3aa6ac62d6"],["5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc","6aebca40ba255960a3178d6d861a54dba813d0b813fde7b5a5082628087264da"],["acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe","cc338921b0a7d9fd64380971763b61e9add888a4375f8e0f05cc262ac64f9c37"],["774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb","d984a032eb6b5e190243dd56d7b7b365372db1e2dff9d6a8301d74c9c953c61b"],["f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8","ab0902e8d880a89758212eb65cdaf473a1a06da521fa91f29b5cb52db03ed81"],["d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e","581e2872a86c72a683842ec228cc6defea40af2bd896d3a5c504dc9ff6a26b58"],["defdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34","4211ab0694635168e997b0ead2a93daeced1f4a04a95c0f6cfb199f69e56eb77"],["2b4ea0a797a443d293ef5cff444f4979f06acfebd7e86d277475656138385b6c","85e89bc037945d93b343083b5a1c86131a01f60c50269763b570c854e5c09b7a"],["352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5","321eb4075348f534d59c18259dda3e1f4a1b3b2e71b1039c67bd3d8bcf81998c"],["2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f","2de1068295dd865b64569335bd5dd80181d70ecfc882648423ba76b532b7d67"],["9248279b09b4d68dab21a9b066edda83263c3d84e09572e269ca0cd7f5453714","73016f7bf234aade5d1aa71bdea2b1ff3fc0de2a887912ffe54a32ce97cb3402"],["daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729","a69dce4a7d6c98e8d4a1aca87ef8d7003f83c230f3afa726ab40e52290be1c55"],["c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db","2119a460ce326cdc76c45926c982fdac0e106e861edf61c5a039063f0e0e6482"],["6a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4","e022cf42c2bd4a708b3f5126f16a24ad8b33ba48d0423b6efd5e6348100d8a82"],["1697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5","b9c398f186806f5d27561506e4557433a2cf15009e498ae7adee9d63d01b2396"],["605bdb019981718b986d0f07e834cb0d9deb8360ffb7f61df982345ef27a7479","2972d2de4f8d20681a78d93ec96fe23c26bfae84fb14db43b01e1e9056b8c49"],["62d14dab4150bf497402fdc45a215e10dcb01c354959b10cfe31c7e9d87ff33d","80fc06bd8cc5b01098088a1950eed0db01aa132967ab472235f5642483b25eaf"],["80c60ad0040f27dade5b4b06c408e56b2c50e9f56b9b8b425e555c2f86308b6f","1c38303f1cc5c30f26e66bad7fe72f70a65eed4cbe7024eb1aa01f56430bd57a"],["7a9375ad6167ad54aa74c6348cc54d344cc5dc9487d847049d5eabb0fa03c8fb","d0e3fa9eca8726909559e0d79269046bdc59ea10c70ce2b02d499ec224dc7f7"],["d528ecd9b696b54c907a9ed045447a79bb408ec39b68df504bb51f459bc3ffc9","eecf41253136e5f99966f21881fd656ebc4345405c520dbc063465b521409933"],["49370a4b5f43412ea25f514e8ecdad05266115e4a7ecb1387231808f8b45963","758f3f41afd6ed428b3081b0512fd62a54c3f3afbb5b6764b653052a12949c9a"],["77f230936ee88cbbd73df930d64702ef881d811e0e1498e2f1c13eb1fc345d74","958ef42a7886b6400a08266e9ba1b37896c95330d97077cbbe8eb3c7671c60d6"],["f2dac991cc4ce4b9ea44887e5c7c0bce58c80074ab9d4dbaeb28531b7739f530","e0dedc9b3b2f8dad4da1f32dec2531df9eb5fbeb0598e4fd1a117dba703a3c37"],["463b3d9f662621fb1b4be8fbbe2520125a216cdfc9dae3debcba4850c690d45b","5ed430d78c296c3543114306dd8622d7c622e27c970a1de31cb377b01af7307e"],["f16f804244e46e2a09232d4aff3b59976b98fac14328a2d1a32496b49998f247","cedabd9b82203f7e13d206fcdf4e33d92a6c53c26e5cce26d6579962c4e31df6"],["caf754272dc84563b0352b7a14311af55d245315ace27c65369e15f7151d41d1","cb474660ef35f5f2a41b643fa5e460575f4fa9b7962232a5c32f908318a04476"],["2600ca4b282cb986f85d0f1709979d8b44a09c07cb86d7c124497bc86f082120","4119b88753c15bd6a693b03fcddbb45d5ac6be74ab5f0ef44b0be9475a7e4b40"],["7635ca72d7e8432c338ec53cd12220bc01c48685e24f7dc8c602a7746998e435","91b649609489d613d1d5e590f78e6d74ecfc061d57048bad9e76f302c5b9c61"],["754e3239f325570cdbbf4a87deee8a66b7f2b33479d468fbc1a50743bf56cc18","673fb86e5bda30fb3cd0ed304ea49a023ee33d0197a695d0c5d98093c536683"],["e3e6bd1071a1e96aff57859c82d570f0330800661d1c952f9fe2694691d9b9e8","59c9e0bba394e76f40c0aa58379a3cb6a5a2283993e90c4167002af4920e37f5"],["186b483d056a033826ae73d88f732985c4ccb1f32ba35f4b4cc47fdcf04aa6eb","3b952d32c67cf77e2e17446e204180ab21fb8090895138b4a4a797f86e80888b"],["df9d70a6b9876ce544c98561f4be4f725442e6d2b737d9c91a8321724ce0963f","55eb2dafd84d6ccd5f862b785dc39d4ab157222720ef9da217b8c45cf2ba2417"],["5edd5cc23c51e87a497ca815d5dce0f8ab52554f849ed8995de64c5f34ce7143","efae9c8dbc14130661e8cec030c89ad0c13c66c0d17a2905cdc706ab7399a868"],["290798c2b6476830da12fe02287e9e777aa3fba1c355b17a722d362f84614fba","e38da76dcd440621988d00bcf79af25d5b29c094db2a23146d003afd41943e7a"],["af3c423a95d9f5b3054754efa150ac39cd29552fe360257362dfdecef4053b45","f98a3fd831eb2b749a93b0e6f35cfb40c8cd5aa667a15581bc2feded498fd9c6"],["766dbb24d134e745cccaa28c99bf274906bb66b26dcf98df8d2fed50d884249a","744b1152eacbe5e38dcc887980da38b897584a65fa06cedd2c924f97cbac5996"],["59dbf46f8c94759ba21277c33784f41645f7b44f6c596a58ce92e666191abe3e","c534ad44175fbc300f4ea6ce648309a042ce739a7919798cd85e216c4a307f6e"],["f13ada95103c4537305e691e74e9a4a8dd647e711a95e73cb62dc6018cfd87b8","e13817b44ee14de663bf4bc808341f326949e21a6a75c2570778419bdaf5733d"],["7754b4fa0e8aced06d4167a2c59cca4cda1869c06ebadfb6488550015a88522c","30e93e864e669d82224b967c3020b8fa8d1e4e350b6cbcc537a48b57841163a2"],["948dcadf5990e048aa3874d46abef9d701858f95de8041d2a6828c99e2262519","e491a42537f6e597d5d28a3224b1bc25df9154efbd2ef1d2cbba2cae5347d57e"],["7962414450c76c1689c7b48f8202ec37fb224cf5ac0bfa1570328a8a3d7c77ab","100b610ec4ffb4760d5c1fc133ef6f6b12507a051f04ac5760afa5b29db83437"],["3514087834964b54b15b160644d915485a16977225b8847bb0dd085137ec47ca","ef0afbb2056205448e1652c48e8127fc6039e77c15c2378b7e7d15a0de293311"],["d3cc30ad6b483e4bc79ce2c9dd8bc54993e947eb8df787b442943d3f7b527eaf","8b378a22d827278d89c5e9be8f9508ae3c2ad46290358630afb34db04eede0a4"],["1624d84780732860ce1c78fcbfefe08b2b29823db913f6493975ba0ff4847610","68651cf9b6da903e0914448c6cd9d4ca896878f5282be4c8cc06e2a404078575"],["733ce80da955a8a26902c95633e62a985192474b5af207da6df7b4fd5fc61cd4","f5435a2bd2badf7d485a4d8b8db9fcce3e1ef8e0201e4578c54673bc1dc5ea1d"],["15d9441254945064cf1a1c33bbd3b49f8966c5092171e699ef258dfab81c045c","d56eb30b69463e7234f5137b73b84177434800bacebfc685fc37bbe9efe4070d"],["a1d0fcf2ec9de675b612136e5ce70d271c21417c9d2b8aaaac138599d0717940","edd77f50bcb5a3cab2e90737309667f2641462a54070f3d519212d39c197a629"],["e22fbe15c0af8ccc5780c0735f84dbe9a790badee8245c06c7ca37331cb36980","a855babad5cd60c88b430a69f53a1a7a38289154964799be43d06d77d31da06"],["311091dd9860e8e20ee13473c1155f5f69635e394704eaa74009452246cfa9b3","66db656f87d1f04fffd1f04788c06830871ec5a64feee685bd80f0b1286d8374"],["34c1fd04d301be89b31c0442d3e6ac24883928b45a9340781867d4232ec2dbdf","9414685e97b1b5954bd46f730174136d57f1ceeb487443dc5321857ba73abee"],["f219ea5d6b54701c1c14de5b557eb42a8d13f3abbcd08affcc2a5e6b049b8d63","4cb95957e83d40b0f73af4544cccf6b1f4b08d3c07b27fb8d8c2962a400766d1"],["d7b8740f74a8fbaab1f683db8f45de26543a5490bca627087236912469a0b448","fa77968128d9c92ee1010f337ad4717eff15db5ed3c049b3411e0315eaa4593b"],["32d31c222f8f6f0ef86f7c98d3a3335ead5bcd32abdd94289fe4d3091aa824bf","5f3032f5892156e39ccd3d7915b9e1da2e6dac9e6f26e961118d14b8462e1661"],["7461f371914ab32671045a155d9831ea8793d77cd59592c4340f86cbc18347b5","8ec0ba238b96bec0cbdddcae0aa442542eee1ff50c986ea6b39847b3cc092ff6"],["ee079adb1df1860074356a25aa38206a6d716b2c3e67453d287698bad7b2b2d6","8dc2412aafe3be5c4c5f37e0ecc5f9f6a446989af04c4e25ebaac479ec1c8c1e"],["16ec93e447ec83f0467b18302ee620f7e65de331874c9dc72bfd8616ba9da6b5","5e4631150e62fb40d0e8c2a7ca5804a39d58186a50e497139626778e25b0674d"],["eaa5f980c245f6f038978290afa70b6bd8855897f98b6aa485b96065d537bd99","f65f5d3e292c2e0819a528391c994624d784869d7e6ea67fb18041024edc07dc"],["78c9407544ac132692ee1910a02439958ae04877151342ea96c4b6b35a49f51","f3e0319169eb9b85d5404795539a5e68fa1fbd583c064d2462b675f194a3ddb4"],["494f4be219a1a77016dcd838431aea0001cdc8ae7a6fc688726578d9702857a5","42242a969283a5f339ba7f075e36ba2af925ce30d767ed6e55f4b031880d562c"],["a598a8030da6d86c6bc7f2f5144ea549d28211ea58faa70ebf4c1e665c1fe9b5","204b5d6f84822c307e4b4a7140737aec23fc63b65b35f86a10026dbd2d864e6b"],["c41916365abb2b5d09192f5f2dbeafec208f020f12570a184dbadc3e58595997","4f14351d0087efa49d245b328984989d5caf9450f34bfc0ed16e96b58fa9913"],["841d6063a586fa475a724604da03bc5b92a2e0d2e0a36acfe4c73a5514742881","73867f59c0659e81904f9a1c7543698e62562d6744c169ce7a36de01a8d6154"],["5e95bb399a6971d376026947f89bde2f282b33810928be4ded112ac4d70e20d5","39f23f366809085beebfc71181313775a99c9aed7d8ba38b161384c746012865"],["36e4641a53948fd476c39f8a99fd974e5ec07564b5315d8bf99471bca0ef2f66","d2424b1b1abe4eb8164227b085c9aa9456ea13493fd563e06fd51cf5694c78fc"],["336581ea7bfbbb290c191a2f507a41cf5643842170e914faeab27c2c579f726","ead12168595fe1be99252129b6e56b3391f7ab1410cd1e0ef3dcdcabd2fda224"],["8ab89816dadfd6b6a1f2634fcf00ec8403781025ed6890c4849742706bd43ede","6fdcef09f2f6d0a044e654aef624136f503d459c3e89845858a47a9129cdd24e"],["1e33f1a746c9c5778133344d9299fcaa20b0938e8acff2544bb40284b8c5fb94","60660257dd11b3aa9c8ed618d24edff2306d320f1d03010e33a7d2057f3b3b6"],["85b7c1dcb3cec1b7ee7f30ded79dd20a0ed1f4cc18cbcfcfa410361fd8f08f31","3d98a9cdd026dd43f39048f25a8847f4fcafad1895d7a633c6fed3c35e999511"],["29df9fbd8d9e46509275f4b125d6d45d7fbe9a3b878a7af872a2800661ac5f51","b4c4fe99c775a606e2d8862179139ffda61dc861c019e55cd2876eb2a27d84b"],["a0b1cae06b0a847a3fea6e671aaf8adfdfe58ca2f768105c8082b2e449fce252","ae434102edde0958ec4b19d917a6a28e6b72da1834aff0e650f049503a296cf2"],["4e8ceafb9b3e9a136dc7ff67e840295b499dfb3b2133e4ba113f2e4c0e121e5","cf2174118c8b6d7a4b48f6d534ce5c79422c086a63460502b827ce62a326683c"],["d24a44e047e19b6f5afb81c7ca2f69080a5076689a010919f42725c2b789a33b","6fb8d5591b466f8fc63db50f1c0f1c69013f996887b8244d2cdec417afea8fa3"],["ea01606a7a6c9cdd249fdfcfacb99584001edd28abbab77b5104e98e8e3b35d4","322af4908c7312b0cfbfe369f7a7b3cdb7d4494bc2823700cfd652188a3ea98d"],["af8addbf2b661c8a6c6328655eb96651252007d8c5ea31be4ad196de8ce2131f","6749e67c029b85f52a034eafd096836b2520818680e26ac8f3dfbcdb71749700"],["e3ae1974566ca06cc516d47e0fb165a674a3dabcfca15e722f0e3450f45889","2aeabe7e4531510116217f07bf4d07300de97e4874f81f533420a72eeb0bd6a4"],["591ee355313d99721cf6993ffed1e3e301993ff3ed258802075ea8ced397e246","b0ea558a113c30bea60fc4775460c7901ff0b053d25ca2bdeee98f1a4be5d196"],["11396d55fda54c49f19aa97318d8da61fa8584e47b084945077cf03255b52984","998c74a8cd45ac01289d5833a7beb4744ff536b01b257be4c5767bea93ea57a4"],["3c5d2a1ba39c5a1790000738c9e0c40b8dcdfd5468754b6405540157e017aa7a","b2284279995a34e2f9d4de7396fc18b80f9b8b9fdd270f6661f79ca4c81bd257"],["cc8704b8a60a0defa3a99a7299f2e9c3fbc395afb04ac078425ef8a1793cc030","bdd46039feed17881d1e0862db347f8cf395b74fc4bcdc4e940b74e3ac1f1b13"],["c533e4f7ea8555aacd9777ac5cad29b97dd4defccc53ee7ea204119b2889b197","6f0a256bc5efdf429a2fb6242f1a43a2d9b925bb4a4b3a26bb8e0f45eb596096"],["c14f8f2ccb27d6f109f6d08d03cc96a69ba8c34eec07bbcf566d48e33da6593","c359d6923bb398f7fd4473e16fe1c28475b740dd098075e6c0e8649113dc3a38"],["a6cbc3046bc6a450bac24789fa17115a4c9739ed75f8f21ce441f72e0b90e6ef","21ae7f4680e889bb130619e2c0f95a360ceb573c70603139862afd617fa9b9f"],["347d6d9a02c48927ebfb86c1359b1caf130a3c0267d11ce6344b39f99d43cc38","60ea7f61a353524d1c987f6ecec92f086d565ab687870cb12689ff1e31c74448"],["da6545d2181db8d983f7dcb375ef5866d47c67b1bf31c8cf855ef7437b72656a","49b96715ab6878a79e78f07ce5680c5d6673051b4935bd897fea824b77dc208a"],["c40747cc9d012cb1a13b8148309c6de7ec25d6945d657146b9d5994b8feb1111","5ca560753be2a12fc6de6caf2cb489565db936156b9514e1bb5e83037e0fa2d4"],["4e42c8ec82c99798ccf3a610be870e78338c7f713348bd34c8203ef4037f3502","7571d74ee5e0fb92a7a8b33a07783341a5492144cc54bcc40a94473693606437"],["3775ab7089bc6af823aba2e1af70b236d251cadb0c86743287522a1b3b0dedea","be52d107bcfa09d8bcb9736a828cfa7fac8db17bf7a76a2c42ad961409018cf7"],["cee31cbf7e34ec379d94fb814d3d775ad954595d1314ba8846959e3e82f74e26","8fd64a14c06b589c26b947ae2bcf6bfa0149ef0be14ed4d80f448a01c43b1c6d"],["b4f9eaea09b6917619f6ea6a4eb5464efddb58fd45b1ebefcdc1a01d08b47986","39e5c9925b5a54b07433a4f18c61726f8bb131c012ca542eb24a8ac07200682a"],["d4263dfc3d2df923a0179a48966d30ce84e2515afc3dccc1b77907792ebcc60e","62dfaf07a0f78feb30e30d6295853ce189e127760ad6cf7fae164e122a208d54"],["48457524820fa65a4f8d35eb6930857c0032acc0a4a2de422233eeda897612c4","25a748ab367979d98733c38a1fa1c2e7dc6cc07db2d60a9ae7a76aaa49bd0f77"],["dfeeef1881101f2cb11644f3a2afdfc2045e19919152923f367a1767c11cceda","ecfb7056cf1de042f9420bab396793c0c390bde74b4bbdff16a83ae09a9a7517"],["6d7ef6b17543f8373c573f44e1f389835d89bcbc6062ced36c82df83b8fae859","cd450ec335438986dfefa10c57fea9bcc521a0959b2d80bbf74b190dca712d10"],["e75605d59102a5a2684500d3b991f2e3f3c88b93225547035af25af66e04541f","f5c54754a8f71ee540b9b48728473e314f729ac5308b06938360990e2bfad125"],["eb98660f4c4dfaa06a2be453d5020bc99a0c2e60abe388457dd43fefb1ed620c","6cb9a8876d9cb8520609af3add26cd20a0a7cd8a9411131ce85f44100099223e"],["13e87b027d8514d35939f2e6892b19922154596941888336dc3563e3b8dba942","fef5a3c68059a6dec5d624114bf1e91aac2b9da568d6abeb2570d55646b8adf1"],["ee163026e9fd6fe017c38f06a5be6fc125424b371ce2708e7bf4491691e5764a","1acb250f255dd61c43d94ccc670d0f58f49ae3fa15b96623e5430da0ad6c62b2"],["b268f5ef9ad51e4d78de3a750c2dc89b1e626d43505867999932e5db33af3d80","5f310d4b3c99b9ebb19f77d41c1dee018cf0d34fd4191614003e945a1216e423"],["ff07f3118a9df035e9fad85eb6c7bfe42b02f01ca99ceea3bf7ffdba93c4750d","438136d603e858a3a5c440c38eccbaddc1d2942114e2eddd4740d098ced1f0d8"],["8d8b9855c7c052a34146fd20ffb658bea4b9f69e0d825ebec16e8c3ce2b526a1","cdb559eedc2d79f926baf44fb84ea4d44bcf50fee51d7ceb30e2e7f463036758"],["52db0b5384dfbf05bfa9d472d7ae26dfe4b851ceca91b1eba54263180da32b63","c3b997d050ee5d423ebaf66a6db9f57b3180c902875679de924b69d84a7b375"],["e62f9490d3d51da6395efd24e80919cc7d0f29c3f3fa48c6fff543becbd43352","6d89ad7ba4876b0b22c2ca280c682862f342c8591f1daf5170e07bfd9ccafa7d"],["7f30ea2476b399b4957509c88f77d0191afa2ff5cb7b14fd6d8e7d65aaab1193","ca5ef7d4b231c94c3b15389a5f6311e9daff7bb67b103e9880ef4bff637acaec"],["5098ff1e1d9f14fb46a210fada6c903fef0fb7b4a1dd1d9ac60a0361800b7a00","9731141d81fc8f8084d37c6e7542006b3ee1b40d60dfe5362a5b132fd17ddc0"],["32b78c7de9ee512a72895be6b9cbefa6e2f3c4ccce445c96b9f2c81e2778ad58","ee1849f513df71e32efc3896ee28260c73bb80547ae2275ba497237794c8753c"],["e2cb74fddc8e9fbcd076eef2a7c72b0ce37d50f08269dfc074b581550547a4f7","d3aa2ed71c9dd2247a62df062736eb0baddea9e36122d2be8641abcb005cc4a4"],["8438447566d4d7bedadc299496ab357426009a35f235cb141be0d99cd10ae3a8","c4e1020916980a4da5d01ac5e6ad330734ef0d7906631c4f2390426b2edd791f"],["4162d488b89402039b584c6fc6c308870587d9c46f660b878ab65c82c711d67e","67163e903236289f776f22c25fb8a3afc1732f2b84b4e95dbda47ae5a0852649"],["3fad3fa84caf0f34f0f89bfd2dcf54fc175d767aec3e50684f3ba4a4bf5f683d","cd1bc7cb6cc407bb2f0ca647c718a730cf71872e7d0d2a53fa20efcdfe61826"],["674f2600a3007a00568c1a7ce05d0816c1fb84bf1370798f1c69532faeb1a86b","299d21f9413f33b3edf43b257004580b70db57da0b182259e09eecc69e0d38a5"],["d32f4da54ade74abb81b815ad1fb3b263d82d6c692714bcff87d29bd5ee9f08f","f9429e738b8e53b968e99016c059707782e14f4535359d582fc416910b3eea87"],["30e4e670435385556e593657135845d36fbb6931f72b08cb1ed954f1e3ce3ff6","462f9bce619898638499350113bbc9b10a878d35da70740dc695a559eb88db7b"],["be2062003c51cc3004682904330e4dee7f3dcd10b01e580bf1971b04d4cad297","62188bc49d61e5428573d48a74e1c655b1c61090905682a0d5558ed72dccb9bc"],["93144423ace3451ed29e0fb9ac2af211cb6e84a601df5993c419859fff5df04a","7c10dfb164c3425f5c71a3f9d7992038f1065224f72bb9d1d902a6d13037b47c"],["b015f8044f5fcbdcf21ca26d6c34fb8197829205c7b7d2a7cb66418c157b112c","ab8c1e086d04e813744a655b2df8d5f83b3cdc6faa3088c1d3aea1454e3a1d5f"],["d5e9e1da649d97d89e4868117a465a3a4f8a18de57a140d36b3f2af341a21b52","4cb04437f391ed73111a13cc1d4dd0db1693465c2240480d8955e8592f27447a"],["d3ae41047dd7ca065dbf8ed77b992439983005cd72e16d6f996a5316d36966bb","bd1aeb21ad22ebb22a10f0303417c6d964f8cdd7df0aca614b10dc14d125ac46"],["463e2763d885f958fc66cdd22800f0a487197d0a82e377b49f80af87c897b065","bfefacdb0e5d0fd7df3a311a94de062b26b80c61fbc97508b79992671ef7ca7f"],["7985fdfd127c0567c6f53ec1bb63ec3158e597c40bfe747c83cddfc910641917","603c12daf3d9862ef2b25fe1de289aed24ed291e0ec6708703a5bd567f32ed03"],["74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9","cc6157ef18c9c63cd6193d83631bbea0093e0968942e8c33d5737fd790e0db08"],["30682a50703375f602d416664ba19b7fc9bab42c72747463a71d0896b22f6da3","553e04f6b018b4fa6c8f39e7f311d3176290d0e0f19ca73f17714d9977a22ff8"],["9e2158f0d7c0d5f26c3791efefa79597654e7a2b2464f52b1ee6c1347769ef57","712fcdd1b9053f09003a3481fa7762e9ffd7c8ef35a38509e2fbf2629008373"],["176e26989a43c9cfeba4029c202538c28172e566e3c4fce7322857f3be327d66","ed8cc9d04b29eb877d270b4878dc43c19aefd31f4eee09ee7b47834c1fa4b1c3"],["75d46efea3771e6e68abb89a13ad747ecf1892393dfc4f1b7004788c50374da8","9852390a99507679fd0b86fd2b39a868d7efc22151346e1a3ca4726586a6bed8"],["809a20c67d64900ffb698c4c825f6d5f2310fb0451c869345b7319f645605721","9e994980d9917e22b76b061927fa04143d096ccc54963e6a5ebfa5f3f8e286c1"],["1b38903a43f7f114ed4500b4eac7083fdefece1cf29c63528d563446f972c180","4036edc931a60ae889353f77fd53de4a2708b26b6f5da72ad3394119daf408f9"]]}}},function(e,t,n){"use strict";var r=n(29),i=n(343),o=n(47),s=n(128),a=n(124),u=o.assert,c=n(344),f=n(345);function l(e){if(!(this instanceof l))return new l(e);"string"==typeof e&&(u(Object.prototype.hasOwnProperty.call(s,e),"Unknown curve "+e),e=s[e]),e instanceof s.PresetCurve&&(e={curve:e}),this.curve=e.curve.curve,this.n=this.curve.n,this.nh=this.n.ushrn(1),this.g=this.curve.g,this.g=e.curve.g,this.g.precompute(e.curve.n.bitLength()+1),this.hash=e.hash||e.curve.hash}e.exports=l,l.prototype.keyPair=function(e){return new c(this,e)},l.prototype.keyFromPrivate=function(e,t){return c.fromPrivate(this,e,t)},l.prototype.keyFromPublic=function(e,t){return c.fromPublic(this,e,t)},l.prototype.genKeyPair=function(e){e||(e={});for(var t=new i({hash:this.hash,pers:e.pers,persEnc:e.persEnc||"utf8",entropy:e.entropy||a(this.hash.hmacStrength),entropyEnc:e.entropy&&e.entropyEnc||"utf8",nonce:this.n.toArray()}),n=this.n.byteLength(),o=this.n.sub(new r(2));;){var s=new r(t.generate(n));if(!(s.cmp(o)>0))return s.iaddn(1),this.keyFromPrivate(s)}},l.prototype._truncateToN=function(e,t){var n=8*e.byteLength()-this.n.bitLength();return n>0&&(e=e.ushrn(n)),!t&&e.cmp(this.n)>=0?e.sub(this.n):e},l.prototype.sign=function(e,t,n,o){"object"==typeof n&&(o=n,n=null),o||(o={}),t=this.keyFromPrivate(t,n),e=this._truncateToN(new r(e,16));for(var s=this.n.byteLength(),a=t.getPrivate().toArray("be",s),u=e.toArray("be",s),c=new i({hash:this.hash,entropy:a,nonce:u,pers:o.pers,persEnc:o.persEnc||"utf8"}),l=this.n.sub(new r(1)),d=0;;d++){var h=o.k?o.k(d):new r(c.generate(this.n.byteLength()));if(!((h=this._truncateToN(h,!0)).cmpn(1)<=0||h.cmp(l)>=0)){var p=this.g.mul(h);if(!p.isInfinity()){var v=p.getX(),g=v.umod(this.n);if(0!==g.cmpn(0)){var m=h.invm(this.n).mul(g.mul(t.getPrivate()).iadd(e));if(0!==(m=m.umod(this.n)).cmpn(0)){var b=(p.getY().isOdd()?1:0)|(0!==v.cmp(g)?2:0);return o.canonical&&m.cmp(this.nh)>0&&(m=this.n.sub(m),b^=1),new f({r:g,s:m,recoveryParam:b})}}}}}},l.prototype.verify=function(e,t,n,i){e=this._truncateToN(new r(e,16)),n=this.keyFromPublic(n,i);var o=(t=new f(t,"hex")).r,s=t.s;if(o.cmpn(1)<0||o.cmp(this.n)>=0)return!1;if(s.cmpn(1)<0||s.cmp(this.n)>=0)return!1;var a,u=s.invm(this.n),c=u.mul(e).umod(this.n),l=u.mul(o).umod(this.n);return this.curve._maxwellTrick?!(a=this.g.jmulAdd(c,n.getPublic(),l)).isInfinity()&&a.eqXToP(o):!(a=this.g.mulAdd(c,n.getPublic(),l)).isInfinity()&&0===a.getX().umod(this.n).cmp(o)},l.prototype.recoverPubKey=function(e,t,n,i){u((3&n)===n,"The recovery param is more than two bits"),t=new f(t,i);var o=this.n,s=new r(e),a=t.r,c=t.s,l=1&n,d=n>>1;if(a.cmp(this.curve.p.umod(this.curve.n))>=0&&d)throw new Error("Unable to find sencond key candinate");a=d?this.curve.pointFromX(a.add(this.curve.n),l):this.curve.pointFromX(a,l);var h=t.r.invm(o),p=o.sub(s).mul(h).umod(o),v=c.mul(h).umod(o);return this.g.mulAdd(p,a,v)},l.prototype.getKeyRecoveryParam=function(e,t,n,r){if(null!==(t=new f(t,r)).recoveryParam)return t.recoveryParam;for(var i=0;i<4;i++){var o;try{o=this.recoverPubKey(e,t,i)}catch(e){continue}if(o.eq(n))return i}throw new Error("Unable to find valid recovery factor")}},function(e,t,n){"use strict";var r=n(129),i=n(198),o=n(46);function s(e){if(!(this instanceof s))return new s(e);this.hash=e.hash,this.predResist=!!e.predResist,this.outLen=this.hash.outSize,this.minEntropy=e.minEntropy||this.hash.hmacStrength,this._reseed=null,this.reseedInterval=null,this.K=null,this.V=null;var t=i.toArray(e.entropy,e.entropyEnc||"hex"),n=i.toArray(e.nonce,e.nonceEnc||"hex"),r=i.toArray(e.pers,e.persEnc||"hex");o(t.length>=this.minEntropy/8,"Not enough entropy. Minimum is: "+this.minEntropy+" bits"),this._init(t,n,r)}e.exports=s,s.prototype._init=function(e,t,n){var r=e.concat(t).concat(n);this.K=new Array(this.outLen/8),this.V=new Array(this.outLen/8);for(var i=0;i<this.V.length;i++)this.K[i]=0,this.V[i]=1;this._update(r),this._reseed=1,this.reseedInterval=281474976710656},s.prototype._hmac=function(){return new r.hmac(this.hash,this.K)},s.prototype._update=function(e){var t=this._hmac().update(this.V).update([0]);e&&(t=t.update(e)),this.K=t.digest(),this.V=this._hmac().update(this.V).digest(),e&&(this.K=this._hmac().update(this.V).update([1]).update(e).digest(),this.V=this._hmac().update(this.V).digest())},s.prototype.reseed=function(e,t,n,r){"string"!=typeof t&&(r=n,n=t,t=null),e=i.toArray(e,t),n=i.toArray(n,r),o(e.length>=this.minEntropy/8,"Not enough entropy. Minimum is: "+this.minEntropy+" bits"),this._update(e.concat(n||[])),this._reseed=1},s.prototype.generate=function(e,t,n,r){if(this._reseed>this.reseedInterval)throw new Error("Reseed is required");"string"!=typeof t&&(r=n,n=t,t=null),n&&(n=i.toArray(n,r||"hex"),this._update(n));for(var o=[];o.length<e;)this.V=this._hmac().update(this.V).digest(),o=o.concat(this.V);var s=o.slice(0,e);return this._update(n),this._reseed++,i.encode(s,t)}},function(e,t,n){"use strict";var r=n(29),i=n(47).assert;function o(e,t){this.ec=e,this.priv=null,this.pub=null,t.priv&&this._importPrivate(t.priv,t.privEnc),t.pub&&this._importPublic(t.pub,t.pubEnc)}e.exports=o,o.fromPublic=function(e,t,n){return t instanceof o?t:new o(e,{pub:t,pubEnc:n})},o.fromPrivate=function(e,t,n){return t instanceof o?t:new o(e,{priv:t,privEnc:n})},o.prototype.validate=function(){var e=this.getPublic();return e.isInfinity()?{result:!1,reason:"Invalid public key"}:e.validate()?e.mul(this.ec.curve.n).isInfinity()?{result:!0,reason:null}:{result:!1,reason:"Public key * N != O"}:{result:!1,reason:"Public key is not a point"}},o.prototype.getPublic=function(e,t){return"string"==typeof e&&(t=e,e=null),this.pub||(this.pub=this.ec.g.mul(this.priv)),t?this.pub.encode(t,e):this.pub},o.prototype.getPrivate=function(e){return"hex"===e?this.priv.toString(16,2):this.priv},o.prototype._importPrivate=function(e,t){this.priv=new r(e,t||16),this.priv=this.priv.umod(this.ec.curve.n)},o.prototype._importPublic=function(e,t){if(e.x||e.y)return"mont"===this.ec.curve.type?i(e.x,"Need x coordinate"):"short"!==this.ec.curve.type&&"edwards"!==this.ec.curve.type||i(e.x&&e.y,"Need both x and y coordinate"),void(this.pub=this.ec.curve.point(e.x,e.y));this.pub=this.ec.curve.decodePoint(e,t)},o.prototype.derive=function(e){return e.validate()||i(e.validate(),"public point not validated"),e.mul(this.priv).getX()},o.prototype.sign=function(e,t,n){return this.ec.sign(e,this,t,n)},o.prototype.verify=function(e,t){return this.ec.verify(e,t,this)},o.prototype.inspect=function(){return"<Key priv: "+(this.priv&&this.priv.toString(16,2))+" pub: "+(this.pub&&this.pub.inspect())+" >"}},function(e,t,n){"use strict";var r=n(29),i=n(47),o=i.assert;function s(e,t){if(e instanceof s)return e;this._importDER(e,t)||(o(e.r&&e.s,"Signature without r or s"),this.r=new r(e.r,16),this.s=new r(e.s,16),void 0===e.recoveryParam?this.recoveryParam=null:this.recoveryParam=e.recoveryParam)}function a(){this.place=0}function u(e,t){var n=e[t.place++];if(!(128&n))return n;var r=15&n;if(0===r||r>4)return!1;for(var i=0,o=0,s=t.place;o<r;o++,s++)i<<=8,i|=e[s],i>>>=0;return!(i<=127)&&(t.place=s,i)}function c(e){for(var t=0,n=e.length-1;!e[t]&&!(128&e[t+1])&&t<n;)t++;return 0===t?e:e.slice(t)}function f(e,t){if(t<128)e.push(t);else{var n=1+(Math.log(t)/Math.LN2>>>3);for(e.push(128|n);--n;)e.push(t>>>(n<<3)&255);e.push(t)}}e.exports=s,s.prototype._importDER=function(e,t){e=i.toArray(e,t);var n=new a;if(48!==e[n.place++])return!1;var o=u(e,n);if(!1===o)return!1;if(o+n.place!==e.length)return!1;if(2!==e[n.place++])return!1;var s=u(e,n);if(!1===s)return!1;var c=e.slice(n.place,s+n.place);if(n.place+=s,2!==e[n.place++])return!1;var f=u(e,n);if(!1===f)return!1;if(e.length!==f+n.place)return!1;var l=e.slice(n.place,f+n.place);if(0===c[0]){if(!(128&c[1]))return!1;c=c.slice(1)}if(0===l[0]){if(!(128&l[1]))return!1;l=l.slice(1)}return this.r=new r(c),this.s=new r(l),this.recoveryParam=null,!0},s.prototype.toDER=function(e){var t=this.r.toArray(),n=this.s.toArray();for(128&t[0]&&(t=[0].concat(t)),128&n[0]&&(n=[0].concat(n)),t=c(t),n=c(n);!(n[0]||128&n[1]);)n=n.slice(1);var r=[2];f(r,t.length),(r=r.concat(t)).push(2),f(r,n.length);var o=r.concat(n),s=[48];return f(s,o.length),s=s.concat(o),i.encode(s,e)}},function(e,t,n){"use strict";var r=n(129),i=n(128),o=n(47),s=o.assert,a=o.parseBytes,u=n(347),c=n(348);function f(e){if(s("ed25519"===e,"only tested with ed25519 so far"),!(this instanceof f))return new f(e);e=i[e].curve,this.curve=e,this.g=e.g,this.g.precompute(e.n.bitLength()+1),this.pointClass=e.point().constructor,this.encodingLength=Math.ceil(e.n.bitLength()/8),this.hash=r.sha512}e.exports=f,f.prototype.sign=function(e,t){e=a(e);var n=this.keyFromSecret(t),r=this.hashInt(n.messagePrefix(),e),i=this.g.mul(r),o=this.encodePoint(i),s=this.hashInt(o,n.pubBytes(),e).mul(n.priv()),u=r.add(s).umod(this.curve.n);return this.makeSignature({R:i,S:u,Rencoded:o})},f.prototype.verify=function(e,t,n){e=a(e),t=this.makeSignature(t);var r=this.keyFromPublic(n),i=this.hashInt(t.Rencoded(),r.pubBytes(),e),o=this.g.mul(t.S());return t.R().add(r.pub().mul(i)).eq(o)},f.prototype.hashInt=function(){for(var e=this.hash(),t=0;t<arguments.length;t++)e.update(arguments[t]);return o.intFromLE(e.digest()).umod(this.curve.n)},f.prototype.keyFromPublic=function(e){return u.fromPublic(this,e)},f.prototype.keyFromSecret=function(e){return u.fromSecret(this,e)},f.prototype.makeSignature=function(e){return e instanceof c?e:new c(this,e)},f.prototype.encodePoint=function(e){var t=e.getY().toArray("le",this.encodingLength);return t[this.encodingLength-1]|=e.getX().isOdd()?128:0,t},f.prototype.decodePoint=function(e){var t=(e=o.parseBytes(e)).length-1,n=e.slice(0,t).concat(-129&e[t]),r=0!=(128&e[t]),i=o.intFromLE(n);return this.curve.pointFromY(i,r)},f.prototype.encodeInt=function(e){return e.toArray("le",this.encodingLength)},f.prototype.decodeInt=function(e){return o.intFromLE(e)},f.prototype.isPoint=function(e){return e instanceof this.pointClass}},function(e,t,n){"use strict";var r=n(47),i=r.assert,o=r.parseBytes,s=r.cachedProperty;function a(e,t){this.eddsa=e,this._secret=o(t.secret),e.isPoint(t.pub)?this._pub=t.pub:this._pubBytes=o(t.pub)}a.fromPublic=function(e,t){return t instanceof a?t:new a(e,{pub:t})},a.fromSecret=function(e,t){return t instanceof a?t:new a(e,{secret:t})},a.prototype.secret=function(){return this._secret},s(a,"pubBytes",(function(){return this.eddsa.encodePoint(this.pub())})),s(a,"pub",(function(){return this._pubBytes?this.eddsa.decodePoint(this._pubBytes):this.eddsa.g.mul(this.priv())})),s(a,"privBytes",(function(){var e=this.eddsa,t=this.hash(),n=e.encodingLength-1,r=t.slice(0,e.encodingLength);return r[0]&=248,r[n]&=127,r[n]|=64,r})),s(a,"priv",(function(){return this.eddsa.decodeInt(this.privBytes())})),s(a,"hash",(function(){return this.eddsa.hash().update(this.secret()).digest()})),s(a,"messagePrefix",(function(){return this.hash().slice(this.eddsa.encodingLength)})),a.prototype.sign=function(e){return i(this._secret,"KeyPair can only verify"),this.eddsa.sign(e,this)},a.prototype.verify=function(e,t){return this.eddsa.verify(e,t,this)},a.prototype.getSecret=function(e){return i(this._secret,"KeyPair is public only"),r.encode(this.secret(),e)},a.prototype.getPublic=function(e){return r.encode(this.pubBytes(),e)},e.exports=a},function(e,t,n){"use strict";var r=n(29),i=n(47),o=i.assert,s=i.cachedProperty,a=i.parseBytes;function u(e,t){this.eddsa=e,"object"!=typeof t&&(t=a(t)),Array.isArray(t)&&(t={R:t.slice(0,e.encodingLength),S:t.slice(e.encodingLength)}),o(t.R&&t.S,"Signature without R or S"),e.isPoint(t.R)&&(this._R=t.R),t.S instanceof r&&(this._S=t.S),this._Rencoded=Array.isArray(t.R)?t.R:t.Rencoded,this._Sencoded=Array.isArray(t.S)?t.S:t.Sencoded}s(u,"S",(function(){return this.eddsa.decodeInt(this.Sencoded())})),s(u,"R",(function(){return this.eddsa.decodePoint(this.Rencoded())})),s(u,"Rencoded",(function(){return this.eddsa.encodePoint(this.R())})),s(u,"Sencoded",(function(){return this.eddsa.encodeInt(this.S())})),u.prototype.toBytes=function(){return this.Rencoded().concat(this.Sencoded())},u.prototype.toHex=function(){return i.encode(this.toBytes(),"hex").toUpperCase()},e.exports=u},function(e,t){},function(e,t,n){"use strict";var r=n(204);t.certificate=n(356);var i=r.define("RSAPrivateKey",(function(){this.seq().obj(this.key("version").int(),this.key("modulus").int(),this.key("publicExponent").int(),this.key("privateExponent").int(),this.key("prime1").int(),this.key("prime2").int(),this.key("exponent1").int(),this.key("exponent2").int(),this.key("coefficient").int())}));t.RSAPrivateKey=i;var o=r.define("RSAPublicKey",(function(){this.seq().obj(this.key("modulus").int(),this.key("publicExponent").int())}));t.RSAPublicKey=o;var s=r.define("SubjectPublicKeyInfo",(function(){this.seq().obj(this.key("algorithm").use(a),this.key("subjectPublicKey").bitstr())}));t.PublicKey=s;var a=r.define("AlgorithmIdentifier",(function(){this.seq().obj(this.key("algorithm").objid(),this.key("none").null_().optional(),this.key("curve").objid().optional(),this.key("params").seq().obj(this.key("p").int(),this.key("q").int(),this.key("g").int()).optional())})),u=r.define("PrivateKeyInfo",(function(){this.seq().obj(this.key("version").int(),this.key("algorithm").use(a),this.key("subjectPrivateKey").octstr())}));t.PrivateKey=u;var c=r.define("EncryptedPrivateKeyInfo",(function(){this.seq().obj(this.key("algorithm").seq().obj(this.key("id").objid(),this.key("decrypt").seq().obj(this.key("kde").seq().obj(this.key("id").objid(),this.key("kdeparams").seq().obj(this.key("salt").octstr(),this.key("iters").int())),this.key("cipher").seq().obj(this.key("algo").objid(),this.key("iv").octstr()))),this.key("subjectPrivateKey").octstr())}));t.EncryptedPrivateKey=c;var f=r.define("DSAPrivateKey",(function(){this.seq().obj(this.key("version").int(),this.key("p").int(),this.key("q").int(),this.key("g").int(),this.key("pub_key").int(),this.key("priv_key").int())}));t.DSAPrivateKey=f,t.DSAparam=r.define("DSAparam",(function(){this.int()}));var l=r.define("ECPrivateKey",(function(){this.seq().obj(this.key("version").int(),this.key("privateKey").octstr(),this.key("parameters").optional().explicit(0).use(d),this.key("publicKey").optional().explicit(1).bitstr())}));t.ECPrivateKey=l;var d=r.define("ECParameters",(function(){this.choice({namedCurve:this.objid()})}));t.signature=r.define("signature",(function(){this.seq().obj(this.key("r").int(),this.key("s").int())}))},function(e,t,n){"use strict";const r=n(205),i=n(207),o=n(7);function s(e,t){this.name=e,this.body=t,this.decoders={},this.encoders={}}t.define=function(e,t){return new s(e,t)},s.prototype._createNamed=function(e){const t=this.name;function n(e){this._initNamed(e,t)}return o(n,e),n.prototype._initNamed=function(t,n){e.call(this,t,n)},new n(this)},s.prototype._getDecoder=function(e){return e=e||"der",this.decoders.hasOwnProperty(e)||(this.decoders[e]=this._createNamed(i[e])),this.decoders[e]},s.prototype.decode=function(e,t,n){return this._getDecoder(t).decode(e,n)},s.prototype._getEncoder=function(e){return e=e||"der",this.encoders.hasOwnProperty(e)||(this.encoders[e]=this._createNamed(r[e])),this.encoders[e]},s.prototype.encode=function(e,t,n){return this._getEncoder(t).encode(e,n)}},function(e,t,n){"use strict";const r=n(7),i=n(206);function o(e){i.call(this,e),this.enc="pem"}r(o,i),e.exports=o,o.prototype.encode=function(e,t){const n=i.prototype.encode.call(this,e).toString("base64"),r=["-----BEGIN "+t.label+"-----"];for(let e=0;e<n.length;e+=64)r.push(n.slice(e,e+64));return r.push("-----END "+t.label+"-----"),r.join("\n")}},function(e,t,n){"use strict";const r=n(7),i=n(130).Buffer,o=n(208);function s(e){o.call(this,e),this.enc="pem"}r(s,o),e.exports=s,s.prototype.decode=function(e,t){const n=e.toString().split(/[\r\n]+/g),r=t.label.toUpperCase(),s=/^-----(BEGIN|END) ([^-]+)-----$/;let a=-1,u=-1;for(let e=0;e<n.length;e++){const t=n[e].match(s);if(null!==t&&t[2]===r){if(-1!==a){if("END"!==t[1])break;u=e;break}if("BEGIN"!==t[1])break;a=e}}if(-1===a||-1===u)throw new Error("PEM section not found for: "+r);const c=n.slice(a+1,u).join("");c.replace(/[^a-z0-9+/=]+/gi,"");const f=i.from(c,"base64");return o.prototype.decode.call(this,f,t)}},function(e,t,n){"use strict";const r=t;r.Reporter=n(132).Reporter,r.DecoderBuffer=n(83).DecoderBuffer,r.EncoderBuffer=n(83).EncoderBuffer,r.Node=n(131)},function(e,t,n){"use strict";const r=t;r._reverse=function(e){const t={};return Object.keys(e).forEach((function(n){(0|n)==n&&(n|=0);const r=e[n];t[r]=n})),t},r.der=n(133)},function(e,t,n){"use strict";var r=n(204),i=r.define("Time",(function(){this.choice({utcTime:this.utctime(),generalTime:this.gentime()})})),o=r.define("AttributeTypeValue",(function(){this.seq().obj(this.key("type").objid(),this.key("value").any())})),s=r.define("AlgorithmIdentifier",(function(){this.seq().obj(this.key("algorithm").objid(),this.key("parameters").optional(),this.key("curve").objid().optional())})),a=r.define("SubjectPublicKeyInfo",(function(){this.seq().obj(this.key("algorithm").use(s),this.key("subjectPublicKey").bitstr())})),u=r.define("RelativeDistinguishedName",(function(){this.setof(o)})),c=r.define("RDNSequence",(function(){this.seqof(u)})),f=r.define("Name",(function(){this.choice({rdnSequence:this.use(c)})})),l=r.define("Validity",(function(){this.seq().obj(this.key("notBefore").use(i),this.key("notAfter").use(i))})),d=r.define("Extension",(function(){this.seq().obj(this.key("extnID").objid(),this.key("critical").bool().def(!1),this.key("extnValue").octstr())})),h=r.define("TBSCertificate",(function(){this.seq().obj(this.key("version").explicit(0).int().optional(),this.key("serialNumber").int(),this.key("signature").use(s),this.key("issuer").use(f),this.key("validity").use(l),this.key("subject").use(f),this.key("subjectPublicKeyInfo").use(a),this.key("issuerUniqueID").implicit(1).bitstr().optional(),this.key("subjectUniqueID").implicit(2).bitstr().optional(),this.key("extensions").explicit(3).seqof(d).optional())})),p=r.define("X509Certificate",(function(){this.seq().obj(this.key("tbsCertificate").use(h),this.key("signatureAlgorithm").use(s),this.key("signatureValue").bitstr())}));e.exports=p},function(e){e.exports=JSON.parse('{"2.16.840.1.101.3.4.1.1":"aes-128-ecb","2.16.840.1.101.3.4.1.2":"aes-128-cbc","2.16.840.1.101.3.4.1.3":"aes-128-ofb","2.16.840.1.101.3.4.1.4":"aes-128-cfb","2.16.840.1.101.3.4.1.21":"aes-192-ecb","2.16.840.1.101.3.4.1.22":"aes-192-cbc","2.16.840.1.101.3.4.1.23":"aes-192-ofb","2.16.840.1.101.3.4.1.24":"aes-192-cfb","2.16.840.1.101.3.4.1.41":"aes-256-ecb","2.16.840.1.101.3.4.1.42":"aes-256-cbc","2.16.840.1.101.3.4.1.43":"aes-256-ofb","2.16.840.1.101.3.4.1.44":"aes-256-cfb"}')},function(e,t,n){var r=/Proc-Type: 4,ENCRYPTED[\n\r]+DEK-Info: AES-((?:128)|(?:192)|(?:256))-CBC,([0-9A-H]+)[\n\r]+([0-9A-z\n\r+/=]+)[\n\r]+/m,i=/^-----BEGIN ((?:.*? KEY)|CERTIFICATE)-----/m,o=/^-----BEGIN ((?:.*? KEY)|CERTIFICATE)-----([0-9A-z\n\r+/=]+)-----END \1-----$/m,s=n(94),a=n(122),u=n(8).Buffer;e.exports=function(e,t){var n,c=e.toString(),f=c.match(r);if(f){var l="aes"+f[1],d=u.from(f[2],"hex"),h=u.from(f[3].replace(/[\r\n]/g,""),"base64"),p=s(t,d.slice(0,8),parseInt(f[1],10)).key,v=[],g=a.createDecipheriv(l,p,d);v.push(g.update(h)),v.push(g.final()),n=u.concat(v)}else{var m=c.match(o);n=u.from(m[2].replace(/[\r\n]/g,""),"base64")}return{tag:c.match(i)[1],data:n}}},function(e,t,n){var r=n(8).Buffer,i=n(203),o=n(127).ec,s=n(96),a=n(209);function u(e,t){if(e.cmpn(0)<=0)throw new Error("invalid sig");if(e.cmp(t)>=t)throw new Error("invalid sig")}e.exports=function(e,t,n,c,f){var l=s(n);if("ec"===l.type){if("ecdsa"!==c&&"ecdsa/rsa"!==c)throw new Error("wrong public key type");return function(e,t,n){var r=a[n.data.algorithm.curve.join(".")];if(!r)throw new Error("unknown curve "+n.data.algorithm.curve.join("."));var i=new o(r),s=n.data.subjectPrivateKey.data;return i.verify(t,e,s)}(e,t,l)}if("dsa"===l.type){if("dsa"!==c)throw new Error("wrong public key type");return function(e,t,n){var r=n.data.p,o=n.data.q,a=n.data.g,c=n.data.pub_key,f=s.signature.decode(e,"der"),l=f.s,d=f.r;u(l,o),u(d,o);var h=i.mont(r),p=l.invm(o);return 0===a.toRed(h).redPow(new i(t).mul(p).mod(o)).fromRed().mul(c.toRed(h).redPow(d.mul(p).mod(o)).fromRed()).mod(r).mod(o).cmp(d)}(e,t,l)}if("rsa"!==c&&"ecdsa/rsa"!==c)throw new Error("wrong public key type");t=r.concat([f,t]);for(var d=l.modulus.byteLength(),h=[1],p=0;t.length+h.length+2<d;)h.push(255),p++;h.push(0);for(var v=-1;++v<t.length;)h.push(t[v]);h=r.from(h);var g=i.mont(l.modulus);e=(e=new i(e).toRed(g)).redPow(new i(l.publicExponent)),e=r.from(e.fromRed().toArray());var m=p<8?1:0;for(d=Math.min(e.length,h.length),e.length!==h.length&&(m=1),v=-1;++v<d;)m|=e[v]^h[v];return 0===m}},function(e,t,n){(function(t){var r=n(127),i=n(29);e.exports=function(e){return new s(e)};var o={secp256k1:{name:"secp256k1",byteLength:32},secp224r1:{name:"p224",byteLength:28},prime256v1:{name:"p256",byteLength:32},prime192v1:{name:"p192",byteLength:24},ed25519:{name:"ed25519",byteLength:32},secp384r1:{name:"p384",byteLength:48},secp521r1:{name:"p521",byteLength:66}};function s(e){this.curveType=o[e],this.curveType||(this.curveType={name:e}),this.curve=new r.ec(this.curveType.name),this.keys=void 0}function a(e,n,r){Array.isArray(e)||(e=e.toArray());var i=new t(e);if(r&&i.length<r){var o=new t(r-i.length);o.fill(0),i=t.concat([o,i])}return n?i.toString(n):i}o.p224=o.secp224r1,o.p256=o.secp256r1=o.prime256v1,o.p192=o.secp192r1=o.prime192v1,o.p384=o.secp384r1,o.p521=o.secp521r1,s.prototype.generateKeys=function(e,t){return this.keys=this.curve.genKeyPair(),this.getPublicKey(e,t)},s.prototype.computeSecret=function(e,n,r){return n=n||"utf8",t.isBuffer(e)||(e=new t(e,n)),a(this.curve.keyFromPublic(e).getPublic().mul(this.keys.getPrivate()).getX(),r,this.curveType.byteLength)},s.prototype.getPublicKey=function(e,t){var n=this.keys.getPublic("compressed"===t,!0);return"hybrid"===t&&(n[n.length-1]%2?n[0]=7:n[0]=6),a(n,e)},s.prototype.getPrivateKey=function(e){return a(this.keys.getPrivate(),e)},s.prototype.setPublicKey=function(e,n){return n=n||"utf8",t.isBuffer(e)||(e=new t(e,n)),this.keys._importPublic(e),this},s.prototype.setPrivateKey=function(e,n){n=n||"utf8",t.isBuffer(e)||(e=new t(e,n));var r=new i(e);return r=r.toString(16),this.keys=this.curve.genKeyPair(),this.keys._importPrivate(r),this}}).call(this,n(6).Buffer)},function(e,t,n){t.publicEncrypt=n(362),t.privateDecrypt=n(363),t.privateEncrypt=function(e,n){return t.publicEncrypt(e,n,!0)},t.publicDecrypt=function(e,n){return t.privateDecrypt(e,n,!0)}},function(e,t,n){var r=n(96),i=n(66),o=n(79),s=n(210),a=n(211),u=n(29),c=n(212),f=n(126),l=n(8).Buffer;e.exports=function(e,t,n){var d;d=e.padding?e.padding:n?1:4;var h,p=r(e);if(4===d)h=function(e,t){var n=e.modulus.byteLength(),r=t.length,c=o("sha1").update(l.alloc(0)).digest(),f=c.length,d=2*f;if(r>n-d-2)throw new Error("message too long");var h=l.alloc(n-r-d-2),p=n-f-1,v=i(f),g=a(l.concat([c,h,l.alloc(1,1),t],p),s(v,p)),m=a(v,s(g,f));return new u(l.concat([l.alloc(1),m,g],n))}(p,t);else if(1===d)h=function(e,t,n){var r,o=t.length,s=e.modulus.byteLength();if(o>s-11)throw new Error("message too long");r=n?l.alloc(s-o-3,255):function(e){var t,n=l.allocUnsafe(e),r=0,o=i(2*e),s=0;for(;r<e;)s===o.length&&(o=i(2*e),s=0),(t=o[s++])&&(n[r++]=t);return n}(s-o-3);return new u(l.concat([l.from([0,n?1:2]),r,l.alloc(1),t],s))}(p,t,n);else{if(3!==d)throw new Error("unknown padding");if((h=new u(t)).cmp(p.modulus)>=0)throw new Error("data too long for modulus")}return n?f(h,p):c(h,p)}},function(e,t,n){var r=n(96),i=n(210),o=n(211),s=n(29),a=n(126),u=n(79),c=n(212),f=n(8).Buffer;e.exports=function(e,t,n){var l;l=e.padding?e.padding:n?1:4;var d,h=r(e),p=h.modulus.byteLength();if(t.length>p||new s(t).cmp(h.modulus)>=0)throw new Error("decryption error");d=n?c(new s(t),h):a(t,h);var v=f.alloc(p-d.length);if(d=f.concat([v,d],p),4===l)return function(e,t){var n=e.modulus.byteLength(),r=u("sha1").update(f.alloc(0)).digest(),s=r.length;if(0!==t[0])throw new Error("decryption error");var a=t.slice(1,s+1),c=t.slice(s+1),l=o(a,i(c,s)),d=o(c,i(l,n-s-1));if(function(e,t){e=f.from(e),t=f.from(t);var n=0,r=e.length;e.length!==t.length&&(n++,r=Math.min(e.length,t.length));var i=-1;for(;++i<r;)n+=e[i]^t[i];return n}(r,d.slice(0,s)))throw new Error("decryption error");var h=s;for(;0===d[h];)h++;if(1!==d[h++])throw new Error("decryption error");return d.slice(h)}(h,d);if(1===l)return function(e,t,n){var r=t.slice(0,2),i=2,o=0;for(;0!==t[i++];)if(i>=t.length){o++;break}var s=t.slice(2,i-1);("0002"!==r.toString("hex")&&!n||"0001"!==r.toString("hex")&&n)&&o++;s.length<8&&o++;if(o)throw new Error("decryption error");return t.slice(i)}(0,d,n);if(3===l)return d;throw new Error("unknown padding")}},function(e,t,n){"use strict";(function(e,r){function i(){throw new Error("secure random number generation not supported by this browser\nuse chrome, FireFox or Internet Explorer 11")}var o=n(8),s=n(66),a=o.Buffer,u=o.kMaxLength,c=e.crypto||e.msCrypto,f=Math.pow(2,32)-1;function l(e,t){if("number"!=typeof e||e!=e)throw new TypeError("offset must be a number");if(e>f||e<0)throw new TypeError("offset must be a uint32");if(e>u||e>t)throw new RangeError("offset out of range")}function d(e,t,n){if("number"!=typeof e||e!=e)throw new TypeError("size must be a number");if(e>f||e<0)throw new TypeError("size must be a uint32");if(e+t>n||e>u)throw new RangeError("buffer too small")}function h(e,t,n,i){if(r.browser){var o=e.buffer,a=new Uint8Array(o,t,n);return c.getRandomValues(a),i?void r.nextTick((function(){i(null,e)})):e}if(!i)return s(n).copy(e,t),e;s(n,(function(n,r){if(n)return i(n);r.copy(e,t),i(null,e)}))}c&&c.getRandomValues||!r.browser?(t.randomFill=function(t,n,r,i){if(!(a.isBuffer(t)||t instanceof e.Uint8Array))throw new TypeError('"buf" argument must be a Buffer or Uint8Array');if("function"==typeof n)i=n,n=0,r=t.length;else if("function"==typeof r)i=r,r=t.length-n;else if("function"!=typeof i)throw new TypeError('"cb" argument must be a function');return l(n,t.length),d(r,n,t.length),h(t,n,r,i)},t.randomFillSync=function(t,n,r){void 0===n&&(n=0);if(!(a.isBuffer(t)||t instanceof e.Uint8Array))throw new TypeError('"buf" argument must be a Buffer or Uint8Array');l(n,t.length),void 0===r&&(r=t.length-n);return d(r,n,t.length),h(t,n,r)}):(t.randomFill=i,t.randomFillSync=i)}).call(this,n(31),n(20))},function(e,t,n){e.exports=self.fetch||(self.fetch=n(213).default||n(213))},function(e,t,n){(function(e,r){var i;/*! https://mths.be/punycode v1.4.1 by @mathias */!function(o){t&&t.nodeType,e&&e.nodeType;var s="object"==typeof r&&r;s.global!==s&&s.window!==s&&s.self;var a,u=2147483647,c=/^xn--/,f=/[^\x20-\x7E]/,l=/[\x2E\u3002\uFF0E\uFF61]/g,d={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},h=Math.floor,p=String.fromCharCode;function v(e){throw new RangeError(d[e])}function g(e,t){for(var n=e.length,r=[];n--;)r[n]=t(e[n]);return r}function m(e,t){var n=e.split("@"),r="";return n.length>1&&(r=n[0]+"@",e=n[1]),r+g((e=e.replace(l,".")).split("."),t).join(".")}function b(e){for(var t,n,r=[],i=0,o=e.length;i<o;)(t=e.charCodeAt(i++))>=55296&&t<=56319&&i<o?56320==(64512&(n=e.charCodeAt(i++)))?r.push(((1023&t)<<10)+(1023&n)+65536):(r.push(t),i--):r.push(t);return r}function y(e){return g(e,(function(e){var t="";return e>65535&&(t+=p((e-=65536)>>>10&1023|55296),e=56320|1023&e),t+=p(e)})).join("")}function w(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function _(e,t,n){var r=0;for(e=n?h(e/700):e>>1,e+=h(e/t);e>455;r+=36)e=h(e/35);return h(r+36*e/(e+38))}function S(e){var t,n,r,i,o,s,a,c,f,l,d,p=[],g=e.length,m=0,b=128,w=72;for((n=e.lastIndexOf("-"))<0&&(n=0),r=0;r<n;++r)e.charCodeAt(r)>=128&&v("not-basic"),p.push(e.charCodeAt(r));for(i=n>0?n+1:0;i<g;){for(o=m,s=1,a=36;i>=g&&v("invalid-input"),((c=(d=e.charCodeAt(i++))-48<10?d-22:d-65<26?d-65:d-97<26?d-97:36)>=36||c>h((u-m)/s))&&v("overflow"),m+=c*s,!(c<(f=a<=w?1:a>=w+26?26:a-w));a+=36)s>h(u/(l=36-f))&&v("overflow"),s*=l;w=_(m-o,t=p.length+1,0==o),h(m/t)>u-b&&v("overflow"),b+=h(m/t),m%=t,p.splice(m++,0,b)}return y(p)}function E(e){var t,n,r,i,o,s,a,c,f,l,d,g,m,y,S,E=[];for(g=(e=b(e)).length,t=128,n=0,o=72,s=0;s<g;++s)(d=e[s])<128&&E.push(p(d));for(r=i=E.length,i&&E.push("-");r<g;){for(a=u,s=0;s<g;++s)(d=e[s])>=t&&d<a&&(a=d);for(a-t>h((u-n)/(m=r+1))&&v("overflow"),n+=(a-t)*m,t=a,s=0;s<g;++s)if((d=e[s])<t&&++n>u&&v("overflow"),d==t){for(c=n,f=36;!(c<(l=f<=o?1:f>=o+26?26:f-o));f+=36)S=c-l,y=36-l,E.push(p(w(l+S%y,0))),c=h(S/y);E.push(p(w(c,0))),o=_(n,m,r==i),n=0,++r}++n,++t}return E.join("")}a={version:"1.4.1",ucs2:{decode:b,encode:y},decode:S,encode:E,toASCII:function(e){return m(e,(function(e){return f.test(e)?"xn--"+E(e):e}))},toUnicode:function(e){return m(e,(function(e){return c.test(e)?S(e.slice(4).toLowerCase()):e}))}},void 0===(i=function(){return a}.call(t,n,t,e))||(e.exports=i)}()}).call(this,n(57)(e),n(31))},function(e,t,n){"use strict";e.exports={isString:function(e){return"string"==typeof e},isObject:function(e){return"object"==typeof e&&null!==e},isNull:function(e){return null===e},isNullOrUndefined:function(e){return null==e}}},function(e,t,n){"use strict";t.decode=t.parse=n(369),t.encode=t.stringify=n(370)},function(e,t,n){"use strict";function r(e,t){return Object.prototype.hasOwnProperty.call(e,t)}e.exports=function(e,t,n,o){t=t||"&",n=n||"=";var s={};if("string"!=typeof e||0===e.length)return s;var a=/\+/g;e=e.split(t);var u=1e3;o&&"number"==typeof o.maxKeys&&(u=o.maxKeys);var c=e.length;u>0&&c>u&&(c=u);for(var f=0;f<c;++f){var l,d,h,p,v=e[f].replace(a,"%20"),g=v.indexOf(n);g>=0?(l=v.substr(0,g),d=v.substr(g+1)):(l=v,d=""),h=decodeURIComponent(l),p=decodeURIComponent(d),r(s,h)?i(s[h])?s[h].push(p):s[h]=[s[h],p]:s[h]=p}return s};var i=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)}},function(e,t,n){"use strict";var r=function(e){switch(typeof e){case"string":return e;case"boolean":return e?"true":"false";case"number":return isFinite(e)?e:"";default:return""}};e.exports=function(e,t,n,a){return t=t||"&",n=n||"=",null===e&&(e=void 0),"object"==typeof e?o(s(e),(function(s){var a=encodeURIComponent(r(s))+n;return i(e[s])?o(e[s],(function(e){return a+encodeURIComponent(r(e))})).join(t):a+encodeURIComponent(r(e[s]))})).join(t):a?encodeURIComponent(r(a))+n+encodeURIComponent(r(e)):""};var i=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)};function o(e,t){if(e.map)return e.map(t);for(var n=[],r=0;r<e.length;r++)n.push(t(e[r],r));return n}var s=Object.keys||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.push(n);return t}},function(e,t,n){var r,i,o=n(214),s=n(215),a=0,u=0;e.exports=function(e,t,n){var c=t&&n||0,f=t||[],l=(e=e||{}).node||r,d=void 0!==e.clockseq?e.clockseq:i;if(null==l||null==d){var h=o();null==l&&(l=r=[1|h[0],h[1],h[2],h[3],h[4],h[5]]),null==d&&(d=i=16383&(h[6]<<8|h[7]))}var p=void 0!==e.msecs?e.msecs:(new Date).getTime(),v=void 0!==e.nsecs?e.nsecs:u+1,g=p-a+(v-u)/1e4;if(g<0&&void 0===e.clockseq&&(d=d+1&16383),(g<0||p>a)&&void 0===e.nsecs&&(v=0),v>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");a=p,u=v,i=d;var m=(1e4*(268435455&(p+=122192928e5))+v)%4294967296;f[c++]=m>>>24&255,f[c++]=m>>>16&255,f[c++]=m>>>8&255,f[c++]=255&m;var b=p/4294967296*1e4&268435455;f[c++]=b>>>8&255,f[c++]=255&b,f[c++]=b>>>24&15|16,f[c++]=b>>>16&255,f[c++]=d>>>8|128,f[c++]=255&d;for(var y=0;y<6;++y)f[c+y]=l[y];return t||s(f)}},function(e,t,n){var r=n(214),i=n(215);e.exports=function(e,t,n){var o=t&&n||0;"string"==typeof e&&(t="binary"===e?new Array(16):null,e=null);var s=(e=e||{}).random||(e.rng||r)();if(s[6]=15&s[6]|64,s[8]=63&s[8]|128,t)for(var a=0;a<16;++a)t[o+a]=s[a];return t||i(s)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Sha256=void 0;var r=n(216),i=n(219),o=n(374),s=n(377),a=n(379),u=n(134),c=function(){function e(e){s.supportsWebCrypto(u.locateWindow())?this.hash=new i.Sha256(e):a.isMsWindow(u.locateWindow())?this.hash=new r.Sha256(e):this.hash=new o.Sha256(e)}return e.prototype.update=function(e,t){this.hash.update(e,t)},e.prototype.digest=function(){return this.hash.digest()},e}();t.Sha256=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),n(1).__exportStar(n(375),t)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Sha256=void 0;var r=n(1),i=n(220),o=n(376),s=n(497),a=function(){function e(e){if(this.hash=new o.RawSha256,e){this.outer=new o.RawSha256;var t=function(e){var t=u(e);if(t.byteLength>i.BLOCK_SIZE){var n=new o.RawSha256;n.update(t),t=n.digest()}var r=new Uint8Array(i.BLOCK_SIZE);return r.set(t),r}(e),n=new Uint8Array(i.BLOCK_SIZE);n.set(t);for(var r=0;r<i.BLOCK_SIZE;r++)t[r]^=54,n[r]^=92;this.hash.update(t),this.outer.update(n);for(r=0;r<t.byteLength;r++)t[r]=0}}return e.prototype.update=function(e){if(!function(e){if("string"==typeof e)return 0===e.length;return 0===e.byteLength}(e)&&!this.error)try{this.hash.update(u(e))}catch(e){this.error=e}},e.prototype.digestSync=function(){if(this.error)throw this.error;return this.outer?(this.outer.finished||this.outer.update(this.hash.digest()),this.outer.digest()):this.hash.digest()},e.prototype.digest=function(){return r.__awaiter(this,void 0,void 0,(function(){return r.__generator(this,(function(e){return[2,this.digestSync()]}))}))},e}();function u(e){return"string"==typeof e?s.fromUtf8(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength/Uint8Array.BYTES_PER_ELEMENT):new Uint8Array(e)}t.Sha256=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RawSha256=void 0;var r=n(220),i=function(){function e(){this.state=Int32Array.from(r.INIT),this.temp=new Int32Array(64),this.buffer=new Uint8Array(64),this.bufferLength=0,this.bytesHashed=0,this.finished=!1}return e.prototype.update=function(e){if(this.finished)throw new Error("Attempted to update an already finished hash.");var t=0,n=e.byteLength;if(this.bytesHashed+=n,8*this.bytesHashed>r.MAX_HASHABLE_LENGTH)throw new Error("Cannot hash more than 2^53 - 1 bits");for(;n>0;)this.buffer[this.bufferLength++]=e[t++],n--,this.bufferLength===r.BLOCK_SIZE&&(this.hashBuffer(),this.bufferLength=0)},e.prototype.digest=function(){if(!this.finished){var e=8*this.bytesHashed,t=new DataView(this.buffer.buffer,this.buffer.byteOffset,this.buffer.byteLength),n=this.bufferLength;if(t.setUint8(this.bufferLength++,128),n%r.BLOCK_SIZE>=r.BLOCK_SIZE-8){for(var i=this.bufferLength;i<r.BLOCK_SIZE;i++)t.setUint8(i,0);this.hashBuffer(),this.bufferLength=0}for(i=this.bufferLength;i<r.BLOCK_SIZE-8;i++)t.setUint8(i,0);t.setUint32(r.BLOCK_SIZE-8,Math.floor(e/4294967296),!0),t.setUint32(r.BLOCK_SIZE-4,e),this.hashBuffer(),this.finished=!0}var o=new Uint8Array(r.DIGEST_LENGTH);for(i=0;i<8;i++)o[4*i]=this.state[i]>>>24&255,o[4*i+1]=this.state[i]>>>16&255,o[4*i+2]=this.state[i]>>>8&255,o[4*i+3]=this.state[i]>>>0&255;return o},e.prototype.hashBuffer=function(){for(var e=this.buffer,t=this.state,n=t[0],i=t[1],o=t[2],s=t[3],a=t[4],u=t[5],c=t[6],f=t[7],l=0;l<r.BLOCK_SIZE;l++){if(l<16)this.temp[l]=(255&e[4*l])<<24|(255&e[4*l+1])<<16|(255&e[4*l+2])<<8|255&e[4*l+3];else{var d=this.temp[l-2],h=(d>>>17|d<<15)^(d>>>19|d<<13)^d>>>10,p=((d=this.temp[l-15])>>>7|d<<25)^(d>>>18|d<<14)^d>>>3;this.temp[l]=(h+this.temp[l-7]|0)+(p+this.temp[l-16]|0)}var v=(((a>>>6|a<<26)^(a>>>11|a<<21)^(a>>>25|a<<7))+(a&u^~a&c)|0)+(f+(r.KEY[l]+this.temp[l]|0)|0)|0,g=((n>>>2|n<<30)^(n>>>13|n<<19)^(n>>>22|n<<10))+(n&i^n&o^i&o)|0;f=c,c=u,u=a,a=s+v|0,s=o,o=i,i=n,n=v+g|0}t[0]+=n,t[1]+=i,t[2]+=o,t[3]+=s,t[4]+=a,t[5]+=u,t[6]+=c,t[7]+=f},e}();t.RawSha256=i},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),n(1).__exportStar(n(378),t)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.supportsZeroByteGCM=t.supportsSubtleCrypto=t.supportsSecureRandom=t.supportsWebCrypto=void 0;var r=n(1),i=["decrypt","digest","encrypt","exportKey","generateKey","importKey","sign","verify"];function o(e){return"object"==typeof e&&"object"==typeof e.crypto&&"function"==typeof e.crypto.getRandomValues}function s(e){return e&&i.every((function(t){return"function"==typeof e[t]}))}t.supportsWebCrypto=function(e){return!(!o(e)||"object"!=typeof e.crypto.subtle)&&s(e.crypto.subtle)},t.supportsSecureRandom=o,t.supportsSubtleCrypto=s,t.supportsZeroByteGCM=function(e){return r.__awaiter(this,void 0,void 0,(function(){var t;return r.__generator(this,(function(n){switch(n.label){case 0:if(!s(e))return[2,!1];n.label=1;case 1:return n.trys.push([1,4,,5]),[4,e.generateKey({name:"AES-GCM",length:128},!1,["encrypt"])];case 2:return t=n.sent(),[4,e.encrypt({name:"AES-GCM",iv:new Uint8Array(Array(12)),additionalData:new Uint8Array(Array(16)),tagLength:128},t,new Uint8Array(0))];case 3:return[2,16===n.sent().byteLength];case 4:return n.sent(),[2,!1];case 5:return[2]}}))}))}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(1);r.__exportStar(n(380),t),r.__exportStar(n(381),t),r.__exportStar(n(382),t),r.__exportStar(n(383),t),r.__exportStar(n(384),t)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isMsWindow=void 0;var r=["decrypt","digest","encrypt","exportKey","generateKey","importKey","sign","verify"];t.isMsWindow=function(e){if(function(e){return"MSInputMethodContext"in e&&"msCrypto"in e}(e)&&void 0!==e.msCrypto.subtle){var t=e.msCrypto,n=t.getRandomValues,i=t.subtle;return r.map((function(e){return i[e]})).concat(n).every((function(e){return"function"==typeof e}))}return!1}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),i=n(222),o=n(386),s=n(494),a=function(){function e(e){if(this.hash=new o.RawSha256,e){this.outer=new o.RawSha256;var t=function(e){var t=u(e);if(t.byteLength>i.BLOCK_SIZE){var n=new o.RawSha256;n.update(t),t=n.digest()}var r=new Uint8Array(i.BLOCK_SIZE);return r.set(t),r}(e),n=new Uint8Array(i.BLOCK_SIZE);n.set(t);for(var r=0;r<i.BLOCK_SIZE;r++)t[r]^=54,n[r]^=92;this.hash.update(t),this.outer.update(n);for(r=0;r<t.byteLength;r++)t[r]=0}}return e.prototype.update=function(e){if(!function(e){if("string"==typeof e)return 0===e.length;return 0===e.byteLength}(e)&&!this.error)try{this.hash.update(u(e))}catch(e){this.error=e}},e.prototype.digestSync=function(){if(this.error)throw this.error;return this.outer?(this.outer.finished||this.outer.update(this.hash.digest()),this.outer.digest()):this.hash.digest()},e.prototype.digest=function(){return r.__awaiter(this,void 0,void 0,(function(){return r.__generator(this,(function(e){return[2,this.digestSync()]}))}))},e}();function u(e){return"string"==typeof e?s.fromUtf8(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength/Uint8Array.BYTES_PER_ELEMENT):new Uint8Array(e)}t.Sha256=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(222),i=function(){function e(){this.state=Int32Array.from(r.INIT),this.temp=new Int32Array(64),this.buffer=new Uint8Array(64),this.bufferLength=0,this.bytesHashed=0,this.finished=!1}return e.prototype.update=function(e){if(this.finished)throw new Error("Attempted to update an already finished hash.");var t=0,n=e.byteLength;if(this.bytesHashed+=n,8*this.bytesHashed>r.MAX_HASHABLE_LENGTH)throw new Error("Cannot hash more than 2^53 - 1 bits");for(;n>0;)this.buffer[this.bufferLength++]=e[t++],n--,this.bufferLength===r.BLOCK_SIZE&&(this.hashBuffer(),this.bufferLength=0)},e.prototype.digest=function(){if(!this.finished){var e=8*this.bytesHashed,t=new DataView(this.buffer.buffer,this.buffer.byteOffset,this.buffer.byteLength),n=this.bufferLength;if(t.setUint8(this.bufferLength++,128),n%r.BLOCK_SIZE>=r.BLOCK_SIZE-8){for(var i=this.bufferLength;i<r.BLOCK_SIZE;i++)t.setUint8(i,0);this.hashBuffer(),this.bufferLength=0}for(i=this.bufferLength;i<r.BLOCK_SIZE-8;i++)t.setUint8(i,0);t.setUint32(r.BLOCK_SIZE-8,Math.floor(e/4294967296),!0),t.setUint32(r.BLOCK_SIZE-4,e),this.hashBuffer(),this.finished=!0}var o=new Uint8Array(r.DIGEST_LENGTH);for(i=0;i<8;i++)o[4*i]=this.state[i]>>>24&255,o[4*i+1]=this.state[i]>>>16&255,o[4*i+2]=this.state[i]>>>8&255,o[4*i+3]=this.state[i]>>>0&255;return o},e.prototype.hashBuffer=function(){for(var e=this.buffer,t=this.state,n=t[0],i=t[1],o=t[2],s=t[3],a=t[4],u=t[5],c=t[6],f=t[7],l=0;l<r.BLOCK_SIZE;l++){if(l<16)this.temp[l]=(255&e[4*l])<<24|(255&e[4*l+1])<<16|(255&e[4*l+2])<<8|255&e[4*l+3];else{var d=this.temp[l-2],h=(d>>>17|d<<15)^(d>>>19|d<<13)^d>>>10,p=((d=this.temp[l-15])>>>7|d<<25)^(d>>>18|d<<14)^d>>>3;this.temp[l]=(h+this.temp[l-7]|0)+(p+this.temp[l-16]|0)}var v=(((a>>>6|a<<26)^(a>>>11|a<<21)^(a>>>25|a<<7))+(a&u^~a&c)|0)+(f+(r.KEY[l]+this.temp[l]|0)|0)|0,g=((n>>>2|n<<30)^(n>>>13|n<<19)^(n>>>22|n<<10))+(n&i^n&o^i&o)|0;f=c,c=u,u=a,a=s+v|0,s=o,o=i,i=n,n=v+g|0}t[0]+=n,t[1]+=i,t[2]+=o,t[3]+=s,t[4]+=a,t[5]+=u,t[6]+=c,t[7]+=f},e}();t.RawSha256=i},function(e,t,n){var r=n(388),i=n(419);e.exports=function(e,t){for(var n=0,o=(t=r(t,e)).length;null!=e&&n<o;)e=e[i(t[n++])];return n&&n==o?e:void 0}},function(e,t,n){var r=n(61),i=n(389),o=n(392),s=n(416);e.exports=function(e,t){return r(e)?e:i(e,t)?[e]:o(s(e))}},function(e,t,n){var r=n(61),i=n(135),o=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,s=/^\w*$/;e.exports=function(e,t){if(r(e))return!1;var n=typeof e;return!("number"!=n&&"symbol"!=n&&"boolean"!=n&&null!=e&&!i(e))||(s.test(e)||!o.test(e)||null!=t&&e in Object(t))}},function(e,t,n){var r=n(97),i=Object.prototype,o=i.hasOwnProperty,s=i.toString,a=r?r.toStringTag:void 0;e.exports=function(e){var t=o.call(e,a),n=e[a];try{e[a]=void 0;var r=!0}catch(e){}var i=s.call(e);return r&&(t?e[a]=n:delete e[a]),i}},function(e,t){var n=Object.prototype.toString;e.exports=function(e){return n.call(e)}},function(e,t,n){var r=n(393),i=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,o=/\\(\\)?/g,s=r((function(e){var t=[];return 46===e.charCodeAt(0)&&t.push(""),e.replace(i,(function(e,n,r,i){t.push(r?i.replace(o,"$1"):n||e)})),t}));e.exports=s},function(e,t,n){var r=n(394);e.exports=function(e){var t=r(e,(function(e){return 500===n.size&&n.clear(),e})),n=t.cache;return t}},function(e,t,n){var r=n(136);function i(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw new TypeError("Expected a function");var n=function(){var r=arguments,i=t?t.apply(this,r):r[0],o=n.cache;if(o.has(i))return o.get(i);var s=e.apply(this,r);return n.cache=o.set(i,s)||o,s};return n.cache=new(i.Cache||r),n}i.Cache=r,e.exports=i},function(e,t,n){var r=n(396),i=n(99),o=n(137);e.exports=function(){this.size=0,this.__data__={hash:new r,map:new(o||i),string:new r}}},function(e,t,n){var r=n(397),i=n(402),o=n(403),s=n(404),a=n(405);function u(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t<n;){var r=e[t];this.set(r[0],r[1])}}u.prototype.clear=r,u.prototype.delete=i,u.prototype.get=o,u.prototype.has=s,u.prototype.set=a,e.exports=u},function(e,t,n){var r=n(98);e.exports=function(){this.__data__=r?r(null):{},this.size=0}},function(e,t,n){var r=n(224),i=n(399),o=n(225),s=n(226),a=/^\[object .+?Constructor\]$/,u=Function.prototype,c=Object.prototype,f=u.toString,l=c.hasOwnProperty,d=RegExp("^"+f.call(l).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");e.exports=function(e){return!(!o(e)||i(e))&&(r(e)?d:a).test(s(e))}},function(e,t,n){var r,i=n(400),o=(r=/[^.]+$/.exec(i&&i.keys&&i.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"";e.exports=function(e){return!!o&&o in e}},function(e,t,n){var r=n(53)["__core-js_shared__"];e.exports=r},function(e,t){e.exports=function(e,t){return null==e?void 0:e[t]}},function(e,t){e.exports=function(e){var t=this.has(e)&&delete this.__data__[e];return this.size-=t?1:0,t}},function(e,t,n){var r=n(98),i=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;if(r){var n=t[e];return"__lodash_hash_undefined__"===n?void 0:n}return i.call(t,e)?t[e]:void 0}},function(e,t,n){var r=n(98),i=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;return r?void 0!==t[e]:i.call(t,e)}},function(e,t,n){var r=n(98);e.exports=function(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,n[e]=r&&void 0===t?"__lodash_hash_undefined__":t,this}},function(e,t){e.exports=function(){this.__data__=[],this.size=0}},function(e,t,n){var r=n(100),i=Array.prototype.splice;e.exports=function(e){var t=this.__data__,n=r(t,e);return!(n<0)&&(n==t.length-1?t.pop():i.call(t,n,1),--this.size,!0)}},function(e,t,n){var r=n(100);e.exports=function(e){var t=this.__data__,n=r(t,e);return n<0?void 0:t[n][1]}},function(e,t,n){var r=n(100);e.exports=function(e){return r(this.__data__,e)>-1}},function(e,t,n){var r=n(100);e.exports=function(e,t){var n=this.__data__,i=r(n,e);return i<0?(++this.size,n.push([e,t])):n[i][1]=t,this}},function(e,t,n){var r=n(101);e.exports=function(e){var t=r(this,e).delete(e);return this.size-=t?1:0,t}},function(e,t){e.exports=function(e){var t=typeof e;return"string"==t||"number"==t||"symbol"==t||"boolean"==t?"__proto__"!==e:null===e}},function(e,t,n){var r=n(101);e.exports=function(e){return r(this,e).get(e)}},function(e,t,n){var r=n(101);e.exports=function(e){return r(this,e).has(e)}},function(e,t,n){var r=n(101);e.exports=function(e,t){var n=r(this,e),i=n.size;return n.set(e,t),this.size+=n.size==i?0:1,this}},function(e,t,n){var r=n(417);e.exports=function(e){return null==e?"":r(e)}},function(e,t,n){var r=n(97),i=n(418),o=n(61),s=n(135),a=r?r.prototype:void 0,u=a?a.toString:void 0;e.exports=function e(t){if("string"==typeof t)return t;if(o(t))return i(t,e)+"";if(s(t))return u?u.call(t):"";var n=t+"";return"0"==n&&1/t==-1/0?"-0":n}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,i=Array(r);++n<r;)i[n]=t(e[n],n,e);return i}},function(e,t,n){var r=n(135);e.exports=function(e){if("string"==typeof e||r(e))return e;var t=e+"";return"0"==t&&1/e==-1/0?"-0":t}},function(e,t,n){var r=n(421)(Object.keys,Object);e.exports=r},function(e,t){e.exports=function(e,t){return function(n){return e(t(n))}}},function(e,t,n){var r=n(72)(n(53),"DataView");e.exports=r},function(e,t,n){var r=n(72)(n(53),"Promise");e.exports=r},function(e,t,n){var r=n(72)(n(53),"Set");e.exports=r},function(e,t,n){var r=n(72)(n(53),"WeakMap");e.exports=r},function(e,t,n){var r=n(84),i=n(85);e.exports=function(e){return i(e)&&"[object Arguments]"==r(e)}},function(e,t){e.exports=function(){return!1}},function(e,t,n){var r=n(84),i=n(233),o=n(85),s={};s["[object Float32Array]"]=s["[object Float64Array]"]=s["[object Int8Array]"]=s["[object Int16Array]"]=s["[object Int32Array]"]=s["[object Uint8Array]"]=s["[object Uint8ClampedArray]"]=s["[object Uint16Array]"]=s["[object Uint32Array]"]=!0,s["[object Arguments]"]=s["[object Array]"]=s["[object ArrayBuffer]"]=s["[object Boolean]"]=s["[object DataView]"]=s["[object Date]"]=s["[object Error]"]=s["[object Function]"]=s["[object Map]"]=s["[object Number]"]=s["[object Object]"]=s["[object RegExp]"]=s["[object Set]"]=s["[object String]"]=s["[object WeakMap]"]=!1,e.exports=function(e){return o(e)&&i(e.length)&&!!s[r(e)]}},function(e,t){e.exports=function(e){return function(t){return e(t)}}},function(e,t,n){(function(e){var r=n(223),i=t&&!t.nodeType&&t,o=i&&"object"==typeof e&&e&&!e.nodeType&&e,s=o&&o.exports===i&&r.process,a=function(){try{var e=o&&o.require&&o.require("util").types;return e||s&&s.binding&&s.binding("util")}catch(e){}}();e.exports=a}).call(this,n(57)(e))},function(e,t,n){var r=n(432),i=n(85);e.exports=function e(t,n,o,s,a){return t===n||(null==t||null==n||!i(t)&&!i(n)?t!=t&&n!=n:r(t,n,o,s,e,a))}},function(e,t,n){var r=n(433),i=n(234),o=n(444),s=n(448),a=n(230),u=n(61),c=n(138),f=n(139),l="[object Object]",d=Object.prototype.hasOwnProperty;e.exports=function(e,t,n,h,p,v){var g=u(e),m=u(t),b=g?"[object Array]":a(e),y=m?"[object Array]":a(t),w=(b="[object Arguments]"==b?l:b)==l,_=(y="[object Arguments]"==y?l:y)==l,S=b==y;if(S&&c(e)){if(!c(t))return!1;g=!0,w=!1}if(S&&!w)return v||(v=new r),g||f(e)?i(e,t,n,h,p,v):o(e,t,b,n,h,p,v);if(!(1&n)){var E=w&&d.call(e,"__wrapped__"),M=_&&d.call(t,"__wrapped__");if(E||M){var A=E?e.value():e,I=M?t.value():t;return v||(v=new r),p(A,I,n,h,v)}}return!!S&&(v||(v=new r),s(e,t,n,h,p,v))}},function(e,t,n){var r=n(99),i=n(434),o=n(435),s=n(436),a=n(437),u=n(438);function c(e){var t=this.__data__=new r(e);this.size=t.size}c.prototype.clear=i,c.prototype.delete=o,c.prototype.get=s,c.prototype.has=a,c.prototype.set=u,e.exports=c},function(e,t,n){var r=n(99);e.exports=function(){this.__data__=new r,this.size=0}},function(e,t){e.exports=function(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}},function(e,t){e.exports=function(e){return this.__data__.get(e)}},function(e,t){e.exports=function(e){return this.__data__.has(e)}},function(e,t,n){var r=n(99),i=n(137),o=n(136);e.exports=function(e,t){var n=this.__data__;if(n instanceof r){var s=n.__data__;if(!i||s.length<199)return s.push([e,t]),this.size=++n.size,this;n=this.__data__=new o(s)}return n.set(e,t),this.size=n.size,this}},function(e,t,n){var r=n(136),i=n(440),o=n(441);function s(e){var t=-1,n=null==e?0:e.length;for(this.__data__=new r;++t<n;)this.add(e[t])}s.prototype.add=s.prototype.push=i,s.prototype.has=o,e.exports=s},function(e,t){e.exports=function(e){return this.__data__.set(e,"__lodash_hash_undefined__"),this}},function(e,t){e.exports=function(e){return this.__data__.has(e)}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length;++n<r;)if(t(e[n],n,e))return!0;return!1}},function(e,t){e.exports=function(e,t){return e.has(t)}},function(e,t,n){var r=n(97),i=n(445),o=n(227),s=n(234),a=n(446),u=n(447),c=r?r.prototype:void 0,f=c?c.valueOf:void 0;e.exports=function(e,t,n,r,c,l,d){switch(n){case"[object DataView]":if(e.byteLength!=t.byteLength||e.byteOffset!=t.byteOffset)return!1;e=e.buffer,t=t.buffer;case"[object ArrayBuffer]":return!(e.byteLength!=t.byteLength||!l(new i(e),new i(t)));case"[object Boolean]":case"[object Date]":case"[object Number]":return o(+e,+t);case"[object Error]":return e.name==t.name&&e.message==t.message;case"[object RegExp]":case"[object String]":return e==t+"";case"[object Map]":var h=a;case"[object Set]":var p=1&r;if(h||(h=u),e.size!=t.size&&!p)return!1;var v=d.get(e);if(v)return v==t;r|=2,d.set(e,t);var g=s(h(e),h(t),r,c,l,d);return d.delete(e),g;case"[object Symbol]":if(f)return f.call(e)==f.call(t)}return!1}},function(e,t,n){var r=n(53).Uint8Array;e.exports=r},function(e,t){e.exports=function(e){var t=-1,n=Array(e.size);return e.forEach((function(e,r){n[++t]=[r,e]})),n}},function(e,t){e.exports=function(e){var t=-1,n=Array(e.size);return e.forEach((function(e){n[++t]=e})),n}},function(e,t,n){var r=n(449),i=Object.prototype.hasOwnProperty;e.exports=function(e,t,n,o,s,a){var u=1&n,c=r(e),f=c.length;if(f!=r(t).length&&!u)return!1;for(var l=f;l--;){var d=c[l];if(!(u?d in t:i.call(t,d)))return!1}var h=a.get(e),p=a.get(t);if(h&&p)return h==t&&p==e;var v=!0;a.set(e,t),a.set(t,e);for(var g=u;++l<f;){var m=e[d=c[l]],b=t[d];if(o)var y=u?o(b,m,d,t,e,a):o(m,b,d,e,t,a);if(!(void 0===y?m===b||s(m,b,n,o,a):y)){v=!1;break}g||(g="constructor"==d)}if(v&&!g){var w=e.constructor,_=t.constructor;w==_||!("constructor"in e)||!("constructor"in t)||"function"==typeof w&&w instanceof w&&"function"==typeof _&&_ instanceof _||(v=!1)}return a.delete(e),a.delete(t),v}},function(e,t,n){var r=n(450),i=n(452),o=n(455);e.exports=function(e){return r(e,o,i)}},function(e,t,n){var r=n(451),i=n(61);e.exports=function(e,t,n){var o=t(e);return i(e)?o:r(o,n(e))}},function(e,t){e.exports=function(e,t){for(var n=-1,r=t.length,i=e.length;++n<r;)e[i+n]=t[n];return e}},function(e,t,n){var r=n(453),i=n(454),o=Object.prototype.propertyIsEnumerable,s=Object.getOwnPropertySymbols,a=s?function(e){return null==e?[]:(e=Object(e),r(s(e),(function(t){return o.call(e,t)})))}:i;e.exports=a},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,i=0,o=[];++n<r;){var s=e[n];t(s,n,e)&&(o[i++]=s)}return o}},function(e,t){e.exports=function(){return[]}},function(e,t,n){var r=n(456),i=n(228),o=n(232);e.exports=function(e){return o(e)?r(e):i(e)}},function(e,t,n){var r=n(457),i=n(231),o=n(61),s=n(138),a=n(458),u=n(139),c=Object.prototype.hasOwnProperty;e.exports=function(e,t){var n=o(e),f=!n&&i(e),l=!n&&!f&&s(e),d=!n&&!f&&!l&&u(e),h=n||f||l||d,p=h?r(e.length,String):[],v=p.length;for(var g in e)!t&&!c.call(e,g)||h&&("length"==g||l&&("offset"==g||"parent"==g)||d&&("buffer"==g||"byteLength"==g||"byteOffset"==g)||a(g,v))||p.push(g);return p}},function(e,t){e.exports=function(e,t){for(var n=-1,r=Array(e);++n<e;)r[n]=t(n);return r}},function(e,t){var n=/^(?:0|[1-9]\d*)$/;e.exports=function(e,t){var r=typeof e;return!!(t=null==t?9007199254740991:t)&&("number"==r||"symbol"!=r&&n.test(e))&&e>-1&&e%1==0&&e<t}},function(e,t,n){"use strict";const r=n(54),i=function(e,t,n){const o={};if((!e.child||r.isEmptyObject(e.child))&&(!e.attrsMap||r.isEmptyObject(e.attrsMap)))return r.isExist(e.val)?e.val:"";if(r.isExist(e.val)&&("string"!=typeof e.val||""!==e.val&&e.val!==t.cdataPositionChar)){const i=r.isTagNameInArrayMode(e.tagname,t.arrayMode,n);o[t.textNodeName]=i?[e.val]:e.val}r.merge(o,e.attrsMap,t.arrayMode);const s=Object.keys(e.child);for(let a=0;a<s.length;a++){const u=s[a];if(e.child[u]&&e.child[u].length>1){o[u]=[];for(let n in e.child[u])e.child[u].hasOwnProperty(n)&&o[u].push(i(e.child[u][n],t,u))}else{const s=i(e.child[u][0],t,u),a=!0===t.arrayMode&&"object"==typeof s||r.isTagNameInArrayMode(u,t.arrayMode,n);o[u]=a?[s]:s}}return o};t.convertToJson=i},function(e,t,n){"use strict";e.exports=function(e,t,n){this.tagname=e,this.parent=t,this.child={},this.attrsMap={},this.val=n,this.addChild=function(e){Array.isArray(this.child[e.tagname])?this.child[e.tagname].push(e):this.child[e.tagname]=[e]}}},function(e,t,n){"use strict";const r=n(54),i={allowBooleanAttributes:!1},o=["allowBooleanAttributes"];function s(e,t){for(var n=t;t<e.length;t++)if("?"!=e[t]&&" "!=e[t]);else{var r=e.substr(n,t-n);if(t>5&&"xml"===r)return d("InvalidXml","XML declaration allowed only at the start of the document.",p(e,t));if("?"==e[t]&&">"==e[t+1]){t++;break}}return t}function a(e,t){if(e.length>t+5&&"-"===e[t+1]&&"-"===e[t+2]){for(t+=3;t<e.length;t++)if("-"===e[t]&&"-"===e[t+1]&&">"===e[t+2]){t+=2;break}}else if(e.length>t+8&&"D"===e[t+1]&&"O"===e[t+2]&&"C"===e[t+3]&&"T"===e[t+4]&&"Y"===e[t+5]&&"P"===e[t+6]&&"E"===e[t+7]){let n=1;for(t+=8;t<e.length;t++)if("<"===e[t])n++;else if(">"===e[t]&&(n--,0===n))break}else if(e.length>t+9&&"["===e[t+1]&&"C"===e[t+2]&&"D"===e[t+3]&&"A"===e[t+4]&&"T"===e[t+5]&&"A"===e[t+6]&&"["===e[t+7])for(t+=8;t<e.length;t++)if("]"===e[t]&&"]"===e[t+1]&&">"===e[t+2]){t+=2;break}return t}t.validate=function(e,t){t=r.buildOptions(t,i,o);const n=[];let c=!1,h=!1;"\ufeff"===e[0]&&(e=e.substr(1));for(let i=0;i<e.length;i++)if("<"===e[i]&&"?"===e[i+1]){if(i+=2,i=s(e,i),i.err)return i}else{if("<"!==e[i]){if(" "===e[i]||"\t"===e[i]||"\n"===e[i]||"\r"===e[i])continue;return d("InvalidChar","char '"+e[i]+"' is not expected.",p(e,i))}if(i++,"!"===e[i]){i=a(e,i);continue}{let o=!1;"/"===e[i]&&(o=!0,i++);let g="";for(;i<e.length&&">"!==e[i]&&" "!==e[i]&&"\t"!==e[i]&&"\n"!==e[i]&&"\r"!==e[i];i++)g+=e[i];if(g=g.trim(),"/"===g[g.length-1]&&(g=g.substring(0,g.length-1),i--),v=g,!r.isName(v)){let t;return t=0===g.trim().length?"There is an unnecessary space between tag name and backward slash '</ ..'.":"Tag '"+g+"' is an invalid name.",d("InvalidTag",t,p(e,i))}const m=u(e,i);if(!1===m)return d("InvalidAttr","Attributes for '"+g+"' have open quote.",p(e,i));let b=m.value;if(i=m.index,"/"===b[b.length-1]){b=b.substring(0,b.length-1);const n=f(b,t);if(!0!==n)return d(n.err.code,n.err.msg,p(e,i-b.length+n.err.line));c=!0}else if(o){if(!m.tagClosed)return d("InvalidTag","Closing tag '"+g+"' doesn't have proper closing.",p(e,i));if(b.trim().length>0)return d("InvalidTag","Closing tag '"+g+"' can't have attributes or invalid starting.",p(e,i));{const t=n.pop();if(g!==t)return d("InvalidTag","Closing tag '"+t+"' is expected inplace of '"+g+"'.",p(e,i));0==n.length&&(h=!0)}}else{const r=f(b,t);if(!0!==r)return d(r.err.code,r.err.msg,p(e,i-b.length+r.err.line));if(!0===h)return d("InvalidXml","Multiple possible root nodes found.",p(e,i));n.push(g),c=!0}for(i++;i<e.length;i++)if("<"===e[i]){if("!"===e[i+1]){i++,i=a(e,i);continue}if("?"!==e[i+1])break;if(i=s(e,++i),i.err)return i}else if("&"===e[i]){const t=l(e,i);if(-1==t)return d("InvalidChar","char '&' is not expected.",p(e,i));i=t}"<"===e[i]&&i--}}var v;return c?!(n.length>0)||d("InvalidXml","Invalid '"+JSON.stringify(n,null,4).replace(/\r?\n/g,"")+"' found.",1):d("InvalidXml","Start tag expected.",1)};function u(e,t){let n="",r="",i=!1;for(;t<e.length;t++){if('"'===e[t]||"'"===e[t])if(""===r)r=e[t];else{if(r!==e[t])continue;r=""}else if(">"===e[t]&&""===r){i=!0;break}n+=e[t]}return""===r&&{value:n,index:t,tagClosed:i}}const c=new RegExp("(\\s*)([^\\s=]+)(\\s*=)?(\\s*(['\"])(([\\s\\S])*?)\\5)?","g");function f(e,t){const n=r.getAllMatches(e,c),i={};for(let r=0;r<n.length;r++){if(0===n[r][1].length)return d("InvalidAttr","Attribute '"+n[r][2]+"' has no space in starting.",v(e,n[r][0]));if(void 0===n[r][3]&&!t.allowBooleanAttributes)return d("InvalidAttr","boolean attribute '"+n[r][2]+"' is not allowed.",v(e,n[r][0]));const o=n[r][2];if(!h(o))return d("InvalidAttr","Attribute '"+o+"' is an invalid name.",v(e,n[r][0]));if(i.hasOwnProperty(o))return d("InvalidAttr","Attribute '"+o+"' is repeated.",v(e,n[r][0]));i[o]=1}return!0}function l(e,t){if(";"===e[++t])return-1;if("#"===e[t])return function(e,t){let n=/\d/;for("x"===e[t]&&(t++,n=/[\da-fA-F]/);t<e.length;t++){if(";"===e[t])return t;if(!e[t].match(n))break}return-1}(e,++t);let n=0;for(;t<e.length;t++,n++)if(!(e[t].match(/\w/)&&n<20)){if(";"===e[t])break;return-1}return t}function d(e,t,n){return{err:{code:e,msg:t,line:n}}}function h(e){return r.isName(e)}function p(e,t){return e.substring(0,t).split(/\r?\n/).length}function v(e,t){return e.indexOf(t)+t.length}},function(e,t,n){"use strict";const r=function(e){return String.fromCharCode(e)},i={nilChar:r(176),missingChar:r(201),nilPremitive:r(175),missingPremitive:r(200),emptyChar:r(178),emptyValue:r(177),boundryChar:r(179),objStart:r(198),arrStart:r(204),arrayEnd:r(185)},o=[i.nilChar,i.nilPremitive,i.missingChar,i.missingPremitive,i.boundryChar,i.emptyChar,i.emptyValue,i.arrayEnd,i.objStart,i.arrStart],s=function(e,t,n){if("string"==typeof t)return e&&e[0]&&void 0!==e[0].val?a(e[0].val,t):a(e,t);{const o=void 0===(r=e)?i.missingChar:null===r?i.nilChar:!(r.child&&0===Object.keys(r.child).length&&(!r.attrsMap||0===Object.keys(r.attrsMap).length))||i.emptyChar;if(!0===o){let r="";if(Array.isArray(t)){r+=i.arrStart;const o=t[0],c=e.length;if("string"==typeof o)for(let t=0;t<c;t++){const n=a(e[t].val,o);r=u(r,n)}else for(let t=0;t<c;t++){const i=s(e[t],o,n);r=u(r,i)}r+=i.arrayEnd}else{r+=i.objStart;const o=Object.keys(t);Array.isArray(e)&&(e=e[0]);for(let i in o){const a=o[i];let c;c=!n.ignoreAttributes&&e.attrsMap&&e.attrsMap[a]?s(e.attrsMap[a],t[a],n):a===n.textNodeName?s(e.val,t[a],n):s(e.child[a],t[a],n),r=u(r,c)}}return r}return o}var r},a=function(e){switch(e){case void 0:return i.missingPremitive;case null:return i.nilPremitive;case"":return i.emptyValue;default:return e}},u=function(e,t){return c(t[0])||c(e[e.length-1])||(e+=i.boundryChar),e+t},c=function(e){return-1!==o.indexOf(e)};const f=n(102),l=n(54).buildOptions;t.convert2nimn=function(e,t,n){return n=l(n,f.defaultOptions,f.props),s(e,t,n)}},function(e,t,n){"use strict";const r=n(54),i=n(54).buildOptions,o=n(102),s=function(e,t,n){let i="{";const o=Object.keys(e.child);for(let n=0;n<o.length;n++){var a=o[n];if(e.child[a]&&e.child[a].length>1){for(var u in i+='"'+a+'" : [ ',e.child[a])i+=s(e.child[a][u],t)+" , ";i=i.substr(0,i.length-1)+" ] "}else i+='"'+a+'" : '+s(e.child[a][0],t)+" ,"}return r.merge(i,e.attrsMap),r.isEmptyObject(i)?r.isExist(e.val)?e.val:"":(r.isExist(e.val)&&("string"!=typeof e.val||""!==e.val&&e.val!==t.cdataPositionChar)&&(i+='"'+t.textNodeName+'" : '+(!0!==(c=e.val)&&!1!==c&&isNaN(c)?'"'+c+'"':c)),","===i[i.length-1]&&(i=i.substr(0,i.length-2)),i+"}");var c};t.convertToJsonString=function(e,t){return(t=i(t,o.defaultOptions,o.props)).indentBy=t.indentBy||"",s(e,t,0)}},function(e,t,n){"use strict";const r=n(54).buildOptions,i={attributeNamePrefix:"@_",attrNodeName:!1,textNodeName:"#text",ignoreAttributes:!0,cdataTagName:!1,cdataPositionChar:"\\c",format:!1,indentBy:" ",supressEmptyNode:!1,tagValueProcessor:function(e){return e},attrValueProcessor:function(e){return e}},o=["attributeNamePrefix","attrNodeName","textNodeName","ignoreAttributes","cdataTagName","cdataPositionChar","format","indentBy","supressEmptyNode","tagValueProcessor","attrValueProcessor"];function s(e){this.options=r(e,i,o),this.options.ignoreAttributes||this.options.attrNodeName?this.isAttribute=function(){return!1}:(this.attrPrefixLen=this.options.attributeNamePrefix.length,this.isAttribute=p),this.options.cdataTagName?this.isCDATA=v:this.isCDATA=function(){return!1},this.replaceCDATAstr=a,this.replaceCDATAarr=u,this.options.format?(this.indentate=h,this.tagEndChar=">\n",this.newLine="\n"):(this.indentate=function(){return""},this.tagEndChar=">",this.newLine=""),this.options.supressEmptyNode?(this.buildTextNode=d,this.buildObjNode=f):(this.buildTextNode=l,this.buildObjNode=c),this.buildTextValNode=l,this.buildObjectNode=c}function a(e,t){return e=this.options.tagValueProcessor(""+e),""===this.options.cdataPositionChar||""===e?e+"<![CDATA["+t+"]]"+this.tagEndChar:e.replace(this.options.cdataPositionChar,"<![CDATA["+t+"]]"+this.tagEndChar)}function u(e,t){if(e=this.options.tagValueProcessor(""+e),""===this.options.cdataPositionChar||""===e)return e+"<![CDATA["+t.join("]]><![CDATA[")+"]]"+this.tagEndChar;for(let n in t)e=e.replace(this.options.cdataPositionChar,"<![CDATA["+t[n]+"]]>");return e+this.newLine}function c(e,t,n,r){return n&&!e.includes("<")?this.indentate(r)+"<"+t+n+">"+e+"</"+t+this.tagEndChar:this.indentate(r)+"<"+t+n+this.tagEndChar+e+this.indentate(r)+"</"+t+this.tagEndChar}function f(e,t,n,r){return""!==e?this.buildObjectNode(e,t,n,r):this.indentate(r)+"<"+t+n+"/"+this.tagEndChar}function l(e,t,n,r){return this.indentate(r)+"<"+t+n+">"+this.options.tagValueProcessor(e)+"</"+t+this.tagEndChar}function d(e,t,n,r){return""!==e?this.buildTextValNode(e,t,n,r):this.indentate(r)+"<"+t+n+"/"+this.tagEndChar}function h(e){return this.options.indentBy.repeat(e)}function p(e){return!!e.startsWith(this.options.attributeNamePrefix)&&e.substr(this.attrPrefixLen)}function v(e){return e===this.options.cdataTagName}s.prototype.parse=function(e){return this.j2x(e,0).val},s.prototype.j2x=function(e,t){let n="",r="";const i=Object.keys(e),o=i.length;for(let s=0;s<o;s++){const o=i[s];if(void 0===e[o]);else if(null===e[o])r+=this.indentate(t)+"<"+o+"/"+this.tagEndChar;else if(e[o]instanceof Date)r+=this.buildTextNode(e[o],o,"",t);else if("object"!=typeof e[o]){const i=this.isAttribute(o);i?n+=" "+i+'="'+this.options.attrValueProcessor(""+e[o])+'"':this.isCDATA(o)?e[this.options.textNodeName]?r+=this.replaceCDATAstr(e[this.options.textNodeName],e[o]):r+=this.replaceCDATAstr("",e[o]):o===this.options.textNodeName?e[this.options.cdataTagName]||(r+=this.options.tagValueProcessor(""+e[o])):r+=this.buildTextNode(e[o],o,"",t)}else if(Array.isArray(e[o]))if(this.isCDATA(o))r+=this.indentate(t),e[this.options.textNodeName]?r+=this.replaceCDATAarr(e[this.options.textNodeName],e[o]):r+=this.replaceCDATAarr("",e[o]);else{const n=e[o].length;for(let i=0;i<n;i++){const n=e[o][i];if(void 0===n);else if(null===n)r+=this.indentate(t)+"<"+o+"/"+this.tagEndChar;else if("object"==typeof n){const e=this.j2x(n,t+1);r+=this.buildObjNode(e.val,o,e.attrStr,t)}else r+=this.buildTextNode(n,o,"",t)}}else if(this.options.attrNodeName&&o===this.options.attrNodeName){const t=Object.keys(e[o]),r=t.length;for(let i=0;i<r;i++)n+=" "+t[i]+'="'+this.options.attrValueProcessor(""+e[o][t[i]])+'"'}else{const n=this.j2x(e[o],t+1);r+=this.buildObjNode(n.val,o,n.attrStr,t)}}return{attrStr:n,val:r}},e.exports=s},function(e,t,n){"use strict";var r=n(45),i=n(235),o=n(466),s=n(241);function a(e){var t=new o(e),n=i(o.prototype.request,t);return r.extend(n,o.prototype,t),r.extend(n,t),n}var u=a(n(238));u.Axios=o,u.create=function(e){return a(s(u.defaults,e))},u.Cancel=n(242),u.CancelToken=n(479),u.isCancel=n(237),u.all=function(e){return Promise.all(e)},u.spread=n(480),u.isAxiosError=n(481),e.exports=u,e.exports.default=u},function(e,t,n){"use strict";var r=n(45),i=n(236),o=n(467),s=n(468),a=n(241);function u(e){this.defaults=e,this.interceptors={request:new o,response:new o}}u.prototype.request=function(e){"string"==typeof e?(e=arguments[1]||{}).url=arguments[0]:e=e||{},(e=a(this.defaults,e)).method?e.method=e.method.toLowerCase():this.defaults.method?e.method=this.defaults.method.toLowerCase():e.method="get";var t=[s,void 0],n=Promise.resolve(e);for(this.interceptors.request.forEach((function(e){t.unshift(e.fulfilled,e.rejected)})),this.interceptors.response.forEach((function(e){t.push(e.fulfilled,e.rejected)}));t.length;)n=n.then(t.shift(),t.shift());return n},u.prototype.getUri=function(e){return e=a(this.defaults,e),i(e.url,e.params,e.paramsSerializer).replace(/^\?/,"")},r.forEach(["delete","get","head","options"],(function(e){u.prototype[e]=function(t,n){return this.request(a(n||{},{method:e,url:t,data:(n||{}).data}))}})),r.forEach(["post","put","patch"],(function(e){u.prototype[e]=function(t,n,r){return this.request(a(r||{},{method:e,url:t,data:n}))}})),e.exports=u},function(e,t,n){"use strict";var r=n(45);function i(){this.handlers=[]}i.prototype.use=function(e,t){return this.handlers.push({fulfilled:e,rejected:t}),this.handlers.length-1},i.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)},i.prototype.forEach=function(e){r.forEach(this.handlers,(function(t){null!==t&&e(t)}))},e.exports=i},function(e,t,n){"use strict";var r=n(45),i=n(469),o=n(237),s=n(238);function a(e){e.cancelToken&&e.cancelToken.throwIfRequested()}e.exports=function(e){return a(e),e.headers=e.headers||{},e.data=i(e.data,e.headers,e.transformRequest),e.headers=r.merge(e.headers.common||{},e.headers[e.method]||{},e.headers),r.forEach(["delete","get","head","post","put","patch","common"],(function(t){delete e.headers[t]})),(e.adapter||s.adapter)(e).then((function(t){return a(e),t.data=i(t.data,t.headers,e.transformResponse),t}),(function(t){return o(t)||(a(e),t&&t.response&&(t.response.data=i(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)}))}},function(e,t,n){"use strict";var r=n(45);e.exports=function(e,t,n){return r.forEach(n,(function(n){e=n(e,t)})),e}},function(e,t,n){"use strict";var r=n(45);e.exports=function(e,t){r.forEach(e,(function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])}))}},function(e,t,n){"use strict";var r=n(240);e.exports=function(e,t,n){var i=n.config.validateStatus;n.status&&i&&!i(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},function(e,t,n){"use strict";e.exports=function(e,t,n,r,i){return e.config=t,n&&(e.code=n),e.request=r,e.response=i,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},function(e,t,n){"use strict";var r=n(45);e.exports=r.isStandardBrowserEnv()?{write:function(e,t,n,i,o,s){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(i)&&a.push("path="+i),r.isString(o)&&a.push("domain="+o),!0===s&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}:{write:function(){},read:function(){return null},remove:function(){}}},function(e,t,n){"use strict";var r=n(475),i=n(476);e.exports=function(e,t){return e&&!r(t)?i(e,t):t}},function(e,t,n){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t,n){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t,n){"use strict";var r=n(45),i=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,o,s={};return e?(r.forEach(e.split("\n"),(function(e){if(o=e.indexOf(":"),t=r.trim(e.substr(0,o)).toLowerCase(),n=r.trim(e.substr(o+1)),t){if(s[t]&&i.indexOf(t)>=0)return;s[t]="set-cookie"===t?(s[t]?s[t]:[]).concat([n]):s[t]?s[t]+", "+n:n}})),s):s}},function(e,t,n){"use strict";var r=n(45);e.exports=r.isStandardBrowserEnv()?function(){var e,t=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");function i(e){var r=e;return t&&(n.setAttribute("href",r),r=n.href),n.setAttribute("href",r),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:"/"===n.pathname.charAt(0)?n.pathname:"/"+n.pathname}}return e=i(window.location.href),function(t){var n=r.isString(t)?i(t):t;return n.protocol===e.protocol&&n.host===e.host}}():function(){return!0}},function(e,t,n){"use strict";var r=n(242);function i(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise((function(e){t=e}));var n=this;e((function(e){n.reason||(n.reason=new r(e),t(n.reason))}))}i.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},i.source=function(){var e;return{token:new i((function(t){e=t})),cancel:e}},e.exports=i},function(e,t,n){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}},function(e,t,n){"use strict";e.exports=function(e){return"object"==typeof e&&!0===e.isAxiosError}},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function o(e,t,n){return t&&i(e.prototype,t),n&&i(e,n),e}Object.defineProperty(t,"__esModule",{value:!0}),t.Observable=void 0;var s=function(){return"function"==typeof Symbol},a=function(e){return s()&&Boolean(Symbol[e])},u=function(e){return a(e)?Symbol[e]:"@@"+e};s()&&!a("observable")&&(Symbol.observable=Symbol("observable"));var c=u("iterator"),f=u("observable"),l=u("species");function d(e,t){var n=e[t];if(null!=n){if("function"!=typeof n)throw new TypeError(n+" is not a function");return n}}function h(e){var t=e.constructor;return void 0!==t&&null===(t=t[l])&&(t=void 0),void 0!==t?t:E}function p(e){return e instanceof E}function v(e){v.log?v.log(e):setTimeout((function(){throw e}))}function g(e){Promise.resolve().then((function(){try{e()}catch(e){v(e)}}))}function m(e){var t=e._cleanup;if(void 0!==t&&(e._cleanup=void 0,t))try{if("function"==typeof t)t();else{var n=d(t,"unsubscribe");n&&n.call(t)}}catch(e){v(e)}}function b(e){e._observer=void 0,e._queue=void 0,e._state="closed"}function y(e,t,n){e._state="running";var r=e._observer;try{var i=d(r,t);switch(t){case"next":i&&i.call(r,n);break;case"error":if(b(e),!i)throw n;i.call(r,n);break;case"complete":b(e),i&&i.call(r)}}catch(e){v(e)}"closed"===e._state?m(e):"running"===e._state&&(e._state="ready")}function w(e,t,n){if("closed"!==e._state){if("buffering"!==e._state)return"ready"!==e._state?(e._state="buffering",e._queue=[{type:t,value:n}],void g((function(){return function(e){var t=e._queue;if(t){e._queue=void 0,e._state="ready";for(var n=0;n<t.length&&(y(e,t[n].type,t[n].value),"closed"!==e._state);++n);}}(e)}))):void y(e,t,n);e._queue.push({type:t,value:n})}}var _=function(){function e(t,n){r(this,e),this._cleanup=void 0,this._observer=t,this._queue=void 0,this._state="initializing";var i=new S(this);try{this._cleanup=n.call(void 0,i)}catch(e){i.error(e)}"initializing"===this._state&&(this._state="ready")}return o(e,[{key:"unsubscribe",value:function(){"closed"!==this._state&&(b(this),m(this))}},{key:"closed",get:function(){return"closed"===this._state}}]),e}(),S=function(){function e(t){r(this,e),this._subscription=t}return o(e,[{key:"next",value:function(e){w(this._subscription,"next",e)}},{key:"error",value:function(e){w(this._subscription,"error",e)}},{key:"complete",value:function(){w(this._subscription,"complete")}},{key:"closed",get:function(){return"closed"===this._subscription._state}}]),e}(),E=function(){function e(t){if(r(this,e),!(this instanceof e))throw new TypeError("Observable cannot be called as a function");if("function"!=typeof t)throw new TypeError("Observable initializer must be a function");this._subscriber=t}return o(e,[{key:"subscribe",value:function(e){return"object"==typeof e&&null!==e||(e={next:e,error:arguments[1],complete:arguments[2]}),new _(e,this._subscriber)}},{key:"forEach",value:function(e){var t=this;return new Promise((function(n,r){if("function"==typeof e)var i=t.subscribe({next:function(t){try{e(t,o)}catch(e){r(e),i.unsubscribe()}},error:r,complete:n});else r(new TypeError(e+" is not a function"));function o(){i.unsubscribe(),n()}}))}},{key:"map",value:function(e){var t=this;if("function"!=typeof e)throw new TypeError(e+" is not a function");return new(h(this))((function(n){return t.subscribe({next:function(t){try{t=e(t)}catch(e){return n.error(e)}n.next(t)},error:function(e){n.error(e)},complete:function(){n.complete()}})}))}},{key:"filter",value:function(e){var t=this;if("function"!=typeof e)throw new TypeError(e+" is not a function");return new(h(this))((function(n){return t.subscribe({next:function(t){try{if(!e(t))return}catch(e){return n.error(e)}n.next(t)},error:function(e){n.error(e)},complete:function(){n.complete()}})}))}},{key:"reduce",value:function(e){var t=this;if("function"!=typeof e)throw new TypeError(e+" is not a function");var n=h(this),r=arguments.length>1,i=!1,o=arguments[1],s=o;return new n((function(n){return t.subscribe({next:function(t){var o=!i;if(i=!0,!o||r)try{s=e(s,t)}catch(e){return n.error(e)}else s=t},error:function(e){n.error(e)},complete:function(){if(!i&&!r)return n.error(new TypeError("Cannot reduce an empty sequence"));n.next(s),n.complete()}})}))}},{key:"concat",value:function(){for(var e=this,t=arguments.length,n=new Array(t),r=0;r<t;r++)n[r]=arguments[r];var i=h(this);return new i((function(t){var r,o=0;return function e(s){r=s.subscribe({next:function(e){t.next(e)},error:function(e){t.error(e)},complete:function(){o===n.length?(r=void 0,t.complete()):e(i.from(n[o++]))}})}(e),function(){r&&(r.unsubscribe(),r=void 0)}}))}},{key:"flatMap",value:function(e){var t=this;if("function"!=typeof e)throw new TypeError(e+" is not a function");var n=h(this);return new n((function(r){var i=[],o=t.subscribe({next:function(t){if(e)try{t=e(t)}catch(e){return r.error(e)}var o=n.from(t).subscribe({next:function(e){r.next(e)},error:function(e){r.error(e)},complete:function(){var e=i.indexOf(o);e>=0&&i.splice(e,1),s()}});i.push(o)},error:function(e){r.error(e)},complete:function(){s()}});function s(){o.closed&&0===i.length&&r.complete()}return function(){i.forEach((function(e){return e.unsubscribe()})),o.unsubscribe()}}))}},{key:f,value:function(){return this}}],[{key:"from",value:function(t){var n="function"==typeof this?this:e;if(null==t)throw new TypeError(t+" is not an object");var r=d(t,f);if(r){var i=r.call(t);if(Object(i)!==i)throw new TypeError(i+" is not an object");return p(i)&&i.constructor===n?i:new n((function(e){return i.subscribe(e)}))}if(a("iterator")&&(r=d(t,c)))return new n((function(e){g((function(){if(!e.closed){var n=!0,i=!1,o=void 0;try{for(var s,a=r.call(t)[Symbol.iterator]();!(n=(s=a.next()).done);n=!0){var u=s.value;if(e.next(u),e.closed)return}}catch(e){i=!0,o=e}finally{try{n||null==a.return||a.return()}finally{if(i)throw o}}e.complete()}}))}));if(Array.isArray(t))return new n((function(e){g((function(){if(!e.closed){for(var n=0;n<t.length;++n)if(e.next(t[n]),e.closed)return;e.complete()}}))}));throw new TypeError(t+" is not observable")}},{key:"of",value:function(){for(var t=arguments.length,n=new Array(t),r=0;r<t;r++)n[r]=arguments[r];var i="function"==typeof this?this:e;return new i((function(e){g((function(){if(!e.closed){for(var t=0;t<n.length;++t)if(e.next(n[t]),e.closed)return;e.complete()}}))}))}},{key:l,get:function(){return this}}]),e}();t.Observable=E,s()&&Object.defineProperty(E,Symbol("extensions"),{value:{symbol:f,hostReportError:v},configurable:!0})},function(e,t,n){var r,i,o=n(243),s=n(244),a=0,u=0;e.exports=function(e,t,n){var c=t&&n||0,f=t||[],l=(e=e||{}).node||r,d=void 0!==e.clockseq?e.clockseq:i;if(null==l||null==d){var h=o();null==l&&(l=r=[1|h[0],h[1],h[2],h[3],h[4],h[5]]),null==d&&(d=i=16383&(h[6]<<8|h[7]))}var p=void 0!==e.msecs?e.msecs:(new Date).getTime(),v=void 0!==e.nsecs?e.nsecs:u+1,g=p-a+(v-u)/1e4;if(g<0&&void 0===e.clockseq&&(d=d+1&16383),(g<0||p>a)&&void 0===e.nsecs&&(v=0),v>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");a=p,u=v,i=d;var m=(1e4*(268435455&(p+=122192928e5))+v)%4294967296;f[c++]=m>>>24&255,f[c++]=m>>>16&255,f[c++]=m>>>8&255,f[c++]=255&m;var b=p/4294967296*1e4&268435455;f[c++]=b>>>8&255,f[c++]=255&b,f[c++]=b>>>24&15|16,f[c++]=b>>>16&255,f[c++]=d>>>8|128,f[c++]=255&d;for(var y=0;y<6;++y)f[c+y]=l[y];return t||s(f)}},function(e,t,n){var r=n(243),i=n(244);e.exports=function(e,t,n){var o=t&&n||0;"string"==typeof e&&(t="binary"===e?new Array(16):null,e=null);var s=(e=e||{}).random||(e.rng||r)();if(s[6]=15&s[6]|64,s[8]=63&s[8]|128,t)for(var a=0;a<16;++a)t[o+a]=s[a];return t||i(s)}},function(e,t){},function(e,t,n){e.exports=n(487).Observable},function(e,t,n){"use strict";(function(e){!function(e,t){function n(e){return"function"==typeof Symbol&&Boolean(Symbol[e])}function r(e){return n(e)?Symbol[e]:"@@"+e}function i(e){setTimeout((function(){throw e}))}function o(e,t){var n=e[t];if(null!=n){if("function"!=typeof n)throw new TypeError(n+" is not a function");return n}}function s(e){var t=e.constructor;return void 0!==t&&null===(t=t[r("species")])&&(t=void 0),void 0!==t?t:d}function a(e,t){Object.keys(t).forEach((function(n){var r=Object.getOwnPropertyDescriptor(t,n);r.enumerable=!1,Object.defineProperty(e,n,r)}))}function u(e){var t=e._cleanup;if(t){e._cleanup=void 0;try{t()}catch(e){i(e)}}}function c(e){return void 0===e._observer}function f(e,t){if(Object(e)!==e)throw new TypeError("Observer must be an object");this._cleanup=void 0,this._observer=e;try{var n=o(e,"start");n&&n.call(e,this)}catch(e){i(e)}if(!c(this)){e=new l(this);try{var r=t.call(void 0,e);if(null!=r){if("function"==typeof r.unsubscribe)s=r,r=function(){s.unsubscribe()};else if("function"!=typeof r)throw new TypeError(r+" is not a function");this._cleanup=r}}catch(t){return void e.error(t)}var s;c(this)&&u(this)}}function l(e){this._subscription=e}function d(e){if(!(this instanceof d))throw new TypeError("Observable cannot be called as a function");if("function"!=typeof e)throw new TypeError("Observable initializer must be a function");this._subscriber=e}"function"!=typeof Symbol||Symbol.observable||(Symbol.observable=Symbol("observable")),a(f.prototype={},{get closed(){return c(this)},unsubscribe:function(){var e;c(e=this)||(e._observer=void 0,u(e))}}),a(l.prototype={},{get closed(){return c(this._subscription)},next:function(e){var t=this._subscription;if(!c(t)){var n=t._observer;try{var r=o(n,"next");r&&r.call(n,e)}catch(e){i(e)}}},error:function(e){var t=this._subscription;if(c(t))i(e);else{var n=t._observer;t._observer=void 0;try{var r=o(n,"error");if(!r)throw e;r.call(n,e)}catch(e){i(e)}u(t)}},complete:function(){var e=this._subscription;if(!c(e)){var t=e._observer;e._observer=void 0;try{var n=o(t,"complete");n&&n.call(t)}catch(e){i(e)}u(e)}}}),a(d.prototype,{subscribe:function(e){for(var t=[],n=1;n<arguments.length;++n)t.push(arguments[n]);return"function"==typeof e?e={next:e,error:t[0],complete:t[1]}:"object"==typeof e&&null!==e||(e={}),new f(e,this._subscriber)},forEach:function(e){var t=this;return new Promise((function(n,r){if("function"!=typeof e)return Promise.reject(new TypeError(e+" is not a function"));t.subscribe({_subscription:null,start:function(e){if(Object(e)!==e)throw new TypeError(e+" is not an object");this._subscription=e},next:function(t){var n=this._subscription;if(!n.closed)try{e(t)}catch(e){r(e),n.unsubscribe()}},error:r,complete:n})}))},map:function(e){var t=this;if("function"!=typeof e)throw new TypeError(e+" is not a function");return new(s(this))((function(n){return t.subscribe({next:function(t){if(!n.closed){try{t=e(t)}catch(e){return n.error(e)}n.next(t)}},error:function(e){n.error(e)},complete:function(){n.complete()}})}))},filter:function(e){var t=this;if("function"!=typeof e)throw new TypeError(e+" is not a function");return new(s(this))((function(n){return t.subscribe({next:function(t){if(!n.closed){try{if(!e(t))return}catch(e){return n.error(e)}n.next(t)}},error:function(e){n.error(e)},complete:function(){n.complete()}})}))},reduce:function(e){var t=this;if("function"!=typeof e)throw new TypeError(e+" is not a function");var n=s(this),r=arguments.length>1,i=!1,o=arguments[1],a=o;return new n((function(n){return t.subscribe({next:function(t){if(!n.closed){var o=!i;if(i=!0,!o||r)try{a=e(a,t)}catch(e){return n.error(e)}else a=t}},error:function(e){n.error(e)},complete:function(){if(!i&&!r)return n.error(new TypeError("Cannot reduce an empty sequence"));n.next(a),n.complete()}})}))}}),Object.defineProperty(d.prototype,r("observable"),{value:function(){return this},writable:!0,configurable:!0}),a(d,{from:function(e){var t="function"==typeof this?this:d;if(null==e)throw new TypeError(e+" is not an object");var i=o(e,r("observable"));if(i){var s=i.call(e);if(Object(s)!==s)throw new TypeError(s+" is not an object");return s.constructor===t?s:new t((function(e){return s.subscribe(e)}))}if(n("iterator")&&(i=o(e,r("iterator"))))return new t((function(t){for(var n,r=i.call(e)[Symbol.iterator]();!(n=r.next()).done;){var o=n.value;if(t.next(o),t.closed)return}t.complete()}));if(Array.isArray(e))return new t((function(t){for(var n=0;n<e.length;++n)if(t.next(e[n]),t.closed)return;t.complete()}));throw new TypeError(e+" is not observable")},of:function(){for(var e=[],t=0;t<arguments.length;++t)e.push(arguments[t]);var n="function"==typeof this?this:d;return new n((function(t){for(var n=0;n<e.length;++n)if(t.next(e[n]),t.closed)return;t.complete()}))}}),Object.defineProperty(d,r("species"),{get:function(){return this},configurable:!0}),Object.defineProperty(d,"extensions",{value:{observableSymbol:r("observable"),setHostReportError:function(e){i=e}}}),e.Observable=d}(t)}).call(this,n(57)(e))},function(e,t,n){"use strict";n.d(t,"a",(function(){return c}));var r=n(44),i=n(19),o=function(){return(o=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},s=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},a=function(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(s(arguments[t]));return e},u=new r.a("Predictions"),c=new(function(){function e(e){this._options=e,this._convertPluggables=[],this._identifyPluggables=[],this._interpretPluggables=[]}return e.prototype.getModuleName=function(){return"Predictions"},e.prototype.addPluggable=function(e){if(this.getPluggable(e.getProviderName()))throw new Error("Pluggable with name "+e.getProviderName()+" has already been added.");var t=!1;this.implementsConvertPluggable(e)&&(this._convertPluggables.push(e),t=!0),this.implementsIdentifyPluggable(e)&&(this._identifyPluggables.push(e),t=!0),this.implementsInterpretPluggable(e)&&(this._interpretPluggables.push(e),t=!0),t&&this.configurePluggable(e)},e.prototype.getPluggable=function(e){var t=this.getAllProviders().find((function(t){return t.getProviderName()===e}));return void 0===t?(u.debug("No plugin found with providerName=>",e),null):t},e.prototype.removePluggable=function(e){this._convertPluggables=this._convertPluggables.filter((function(t){return t.getProviderName()!==e})),this._identifyPluggables=this._identifyPluggables.filter((function(t){return t.getProviderName()!==e})),this._interpretPluggables=this._interpretPluggables.filter((function(t){return t.getProviderName()!==e}))},e.prototype.configure=function(e){var t=this,n=e?e.predictions||e:{};n=o(o({},n),e),this._options=Object.assign({},this._options,n),u.debug("configure Predictions",this._options),this.getAllProviders().forEach((function(e){return t.configurePluggable(e)}))},e.prototype.interpret=function(e,t){return this.getPluggableToExecute(this._interpretPluggables,t).interpret(e)},e.prototype.convert=function(e,t){return this.getPluggableToExecute(this._convertPluggables,t).convert(e)},e.prototype.identify=function(e,t){return this.getPluggableToExecute(this._identifyPluggables,t).identify(e)},e.prototype.getPluggableToExecute=function(e,t){if(t&&t.providerName)return a(e).find((function(e){return e.getProviderName()===t.providerName}));if(1===e.length)return e[0];throw new Error("More than one or no providers are configured, Either specify a provider name or configure exactly one provider")},e.prototype.getAllProviders=function(){return a(this._convertPluggables,this._identifyPluggables,this._interpretPluggables)},e.prototype.configurePluggable=function(e){var t=Object.assign({},this._options.predictions,this._options[e.getCategory().toLowerCase()]);e.configure(t)},e.prototype.implementsConvertPluggable=function(e){return e&&"function"==typeof e.convert},e.prototype.implementsIdentifyPluggable=function(e){return e&&"function"==typeof e.identify},e.prototype.implementsInterpretPluggable=function(e){return e&&"function"==typeof e.interpret},e}())({});i.a.register(c)},function(e,t,n){"use strict";n.d(t,"a",(function(){return ft}));var r=n(44),i=n(19),o=function(){return(o=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},s=new r.a("AbstractInteractionsProvider"),a=function(){function e(e){void 0===e&&(e={}),this._config=e}return e.prototype.configure=function(e){return void 0===e&&(e={}),this._config=o(o({},this._config),e),s.debug("configure "+this.getProviderName(),this._config),this.options},e.prototype.getCategory=function(){return"Interactions"},Object.defineProperty(e.prototype,"options",{get:function(){return o({},this._config)},enumerable:!0,configurable:!0}),e}(),u=function(e,t){return(u=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};function c(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}u(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var f=function(){return(f=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};function l(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))}function d(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}}Object.create;function h(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s}Object.create;var p,v,g,m,b,y,w,_,S,E,M,A,I,k,O,x,C,T,P,N,R,L,j,D,U,B,F,z,q,K,H,V,G,W,$,Y=n(155),J=n(38),Z=n(18),X=n(24),Q=n(11),ee=n(39),te=n(17),ne=n(40),re=n(41),ie=n(15),oe=new Set(["ap-east-1","ap-northeast-1","ap-northeast-2","ap-south-1","ap-southeast-1","ap-southeast-2","ca-central-1","eu-central-1","eu-north-1","eu-west-1","eu-west-2","eu-west-3","me-south-1","sa-east-1","us-east-1","us-east-2","us-west-1","us-west-2"]),se=new Set(["cn-north-1","cn-northwest-1"]),ae=new Set(["us-iso-east-1"]),ue=new Set(["us-isob-east-1"]),ce=new Set(["us-gov-east-1","us-gov-west-1"]),fe=f(f({},{apiVersion:"2016-11-28",disableHostPrefix:!1,logger:{},regionInfoProvider:function(e,t){var n=void 0;switch(e){case"eu-west-1":n={hostname:"runtime.lex.eu-west-1.amazonaws.com",partition:"aws",signingService:"lex"};break;case"us-east-1":n={hostname:"runtime.lex.us-east-1.amazonaws.com",partition:"aws",signingService:"lex"};break;case"us-west-2":n={hostname:"runtime.lex.us-west-2.amazonaws.com",partition:"aws",signingService:"lex"};break;default:oe.has(e)&&(n={hostname:"runtime.lex.{region}.amazonaws.com".replace("{region}",e),partition:"aws",signingService:"lex"}),se.has(e)&&(n={hostname:"runtime.lex.{region}.amazonaws.com.cn".replace("{region}",e),partition:"aws-cn"}),ae.has(e)&&(n={hostname:"runtime.lex.{region}.c2s.ic.gov".replace("{region}",e),partition:"aws-iso"}),ue.has(e)&&(n={hostname:"runtime.lex.{region}.sc2s.sgov.gov".replace("{region}",e),partition:"aws-iso-b"}),ce.has(e)&&(n={hostname:"runtime.lex.{region}.amazonaws.com".replace("{region}",e),partition:"aws-us-gov"}),void 0===n&&(n={hostname:"runtime.lex.{region}.amazonaws.com".replace("{region}",e),partition:"aws",signingService:"lex"})}return Promise.resolve(n)},signingName:"lex"}),{runtime:"browser",base64Decoder:te.a,base64Encoder:te.b,bodyLengthChecker:ne.a,credentialDefaultProvider:Object(X.a)("Credential is missing"),defaultUserAgent:Object(re.a)(Y.name,Y.version),maxAttempts:Q.a,region:Object(X.a)("Region is missing"),requestHandler:new Z.a,sha256:J.Sha256,streamCollector:Z.b,urlParser:ee.a,utf8Decoder:ie.a,utf8Encoder:ie.b}),le=n(22),de=n(37),he=n(21),pe=n(43),ve=n(25),ge=n(23),me=n(0),be=function(e){function t(t){var n=this,r=f(f({},fe),t),i=Object(le.b)(r),o=Object(le.a)(i),s=Object(ve.b)(o),a=Object(Q.c)(s),u=Object(ge.b)(a),c=Object(he.b)(u);return(n=e.call(this,c)||this).config=c,n.middlewareStack.use(Object(ve.a)(n.config)),n.middlewareStack.use(Object(Q.b)(n.config)),n.middlewareStack.use(Object(ge.a)(n.config)),n.middlewareStack.use(Object(de.a)(n.config)),n.middlewareStack.use(Object(he.a)(n.config)),n.middlewareStack.use(Object(pe.a)(n.config)),n}return c(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this)},t}(me.a);(p||(p={})).filterSensitiveLog=function(e){return f({},e)},(v||(v={})).filterSensitiveLog=function(e){return f({},e)},(g||(g={})).filterSensitiveLog=function(e){return f({},e)},(m||(m={})).filterSensitiveLog=function(e){return f({},e)},(b||(b={})).filterSensitiveLog=function(e){return f({},e)},(y||(y={})).filterSensitiveLog=function(e){return f({},e)},(w||(w={})).filterSensitiveLog=function(e){return f({},e)},(_||(_={})).filterSensitiveLog=function(e){return f({},e)},function(e){e.FAILED="Failed",e.FULFILLED="Fulfilled",e.READY_FOR_FULFILLMENT="ReadyForFulfillment"}(S||(S={})),function(e){e.COMPOSITE="Composite",e.CUSTOM_PAYLOAD="CustomPayload",e.PLAIN_TEXT="PlainText",e.SSML="SSML"}(E||(E={})),function(e){e.CLOSE="Close",e.CONFIRM_INTENT="ConfirmIntent",e.DELEGATE="Delegate",e.ELICIT_INTENT="ElicitIntent",e.ELICIT_SLOT="ElicitSlot"}(M||(M={})),(A||(A={})).filterSensitiveLog=function(e){return f(f(f({},e),e.slots&&{slots:me.d}),e.message&&{message:me.d})},function(e){e.CONFIRMED="Confirmed",e.DENIED="Denied",e.NONE="None"}(I||(I={})),(k||(k={})).filterSensitiveLog=function(e){return f(f({},e),e.slots&&{slots:me.d})},(O||(O={})).filterSensitiveLog=function(e){return f(f(f(f({},e),e.dialogAction&&{dialogAction:A.filterSensitiveLog(e.dialogAction)}),e.recentIntentSummaryView&&{recentIntentSummaryView:e.recentIntentSummaryView.map((function(e){return k.filterSensitiveLog(e)}))}),e.sessionAttributes&&{sessionAttributes:me.d})},(x||(x={})).filterSensitiveLog=function(e){return f({},e)},(C||(C={})).filterSensitiveLog=function(e){return f({},e)},(T||(T={})).filterSensitiveLog=function(e){return f({},e)},(P||(P={})).filterSensitiveLog=function(e){return f({},e)},(N||(N={})).filterSensitiveLog=function(e){return f(f(f({},e),e.requestAttributes&&{requestAttributes:me.d}),e.sessionAttributes&&{sessionAttributes:me.d})},function(e){e.CONFIRM_INTENT="ConfirmIntent",e.ELICIT_INTENT="ElicitIntent",e.ELICIT_SLOT="ElicitSlot",e.FAILED="Failed",e.FULFILLED="Fulfilled",e.READY_FOR_FULFILLMENT="ReadyForFulfillment"}(R||(R={})),(L||(L={})).filterSensitiveLog=function(e){return f(f({},e),e.message&&{message:me.d})},(j||(j={})).filterSensitiveLog=function(e){return f({},e)},(D||(D={})).filterSensitiveLog=function(e){return f({},e)},(U||(U={})).filterSensitiveLog=function(e){return f(f(f(f({},e),e.requestAttributes&&{requestAttributes:me.d}),e.inputText&&{inputText:me.d}),e.sessionAttributes&&{sessionAttributes:me.d})},(B||(B={})).filterSensitiveLog=function(e){return f({},e)},(F||(F={})).filterSensitiveLog=function(e){return f(f({},e),e.slots&&{slots:me.d})},function(e){e.GENERIC="application/vnd.amazonaws.card.generic"}(z||(z={})),(q||(q={})).filterSensitiveLog=function(e){return f({},e)},(K||(K={})).filterSensitiveLog=function(e){return f({},e)},(H||(H={})).filterSensitiveLog=function(e){return f({},e)},(V||(V={})).filterSensitiveLog=function(e){return f({},e)},(G||(G={})).filterSensitiveLog=function(e){return f(f(f(f(f({},e),e.alternativeIntents&&{alternativeIntents:e.alternativeIntents.map((function(e){return F.filterSensitiveLog(e)}))}),e.message&&{message:me.d}),e.sessionAttributes&&{sessionAttributes:me.d}),e.slots&&{slots:me.d})},(W||(W={})).filterSensitiveLog=function(e){return f(f(f(f({},e),e.dialogAction&&{dialogAction:A.filterSensitiveLog(e.dialogAction)}),e.recentIntentSummaryView&&{recentIntentSummaryView:e.recentIntentSummaryView.map((function(e){return k.filterSensitiveLog(e)}))}),e.sessionAttributes&&{sessionAttributes:me.d})},($||($={})).filterSensitiveLog=function(e){return f(f({},e),e.message&&{message:me.d})};var ye,we=n(2),_e=function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l,h,p,v,g,m,b,y,w,_;return d(this,(function(d){switch(d.label){case 0:return r=[f({},e)],_={},[4,Ge(e.body,t)];case 1:switch(n=f.apply(void 0,r.concat([(_.body=d.sent(),_)])),o="UnknownError",o=We(e,n.body),o){case"BadGatewayException":case"com.amazonaws.lexruntimeservice#BadGatewayException":return[3,2];case"BadRequestException":case"com.amazonaws.lexruntimeservice#BadRequestException":return[3,4];case"ConflictException":case"com.amazonaws.lexruntimeservice#ConflictException":return[3,6];case"DependencyFailedException":case"com.amazonaws.lexruntimeservice#DependencyFailedException":return[3,8];case"InternalFailureException":case"com.amazonaws.lexruntimeservice#InternalFailureException":return[3,10];case"LimitExceededException":case"com.amazonaws.lexruntimeservice#LimitExceededException":return[3,12];case"LoopDetectedException":case"com.amazonaws.lexruntimeservice#LoopDetectedException":return[3,14];case"NotAcceptableException":case"com.amazonaws.lexruntimeservice#NotAcceptableException":return[3,16];case"NotFoundException":case"com.amazonaws.lexruntimeservice#NotFoundException":return[3,18];case"RequestTimeoutException":case"com.amazonaws.lexruntimeservice#RequestTimeoutException":return[3,20];case"UnsupportedMediaTypeException":case"com.amazonaws.lexruntimeservice#UnsupportedMediaTypeException":return[3,22]}return[3,24];case 2:return s=[{}],[4,Ee(n,t)];case 3:return i=f.apply(void 0,[f.apply(void 0,s.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,25];case 4:return a=[{}],[4,Me(n,t)];case 5:return i=f.apply(void 0,[f.apply(void 0,a.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,25];case 6:return u=[{}],[4,Ae(n,t)];case 7:return i=f.apply(void 0,[f.apply(void 0,u.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,25];case 8:return c=[{}],[4,Ie(n,t)];case 9:return i=f.apply(void 0,[f.apply(void 0,c.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,25];case 10:return l=[{}],[4,ke(n,t)];case 11:return i=f.apply(void 0,[f.apply(void 0,l.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,25];case 12:return h=[{}],[4,Oe(n,t)];case 13:return i=f.apply(void 0,[f.apply(void 0,h.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,25];case 14:return p=[{}],[4,xe(n,t)];case 15:return i=f.apply(void 0,[f.apply(void 0,p.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,25];case 16:return v=[{}],[4,Ce(n,t)];case 17:return i=f.apply(void 0,[f.apply(void 0,v.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,25];case 18:return g=[{}],[4,Te(n,t)];case 19:return i=f.apply(void 0,[f.apply(void 0,g.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,25];case 20:return m=[{}],[4,Pe(n,t)];case 21:return i=f.apply(void 0,[f.apply(void 0,m.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,25];case 22:return b=[{}],[4,Ne(n,t)];case 23:return i=f.apply(void 0,[f.apply(void 0,b.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,25];case 24:y=n.body,o=y.code||y.Code||o,i=f(f({},y),{name:""+o,message:y.message||y.Message||o,$fault:"client",$metadata:Ke(e)}),d.label=25;case 25:return w=i.message||i.Message||o,i.message=w,delete i.Message,[2,Promise.reject(Object.assign(new Error(w),i))]}}))}))},Se=function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l,h,p,v,g,m,b;return d(this,(function(d){switch(d.label){case 0:return r=[f({},e)],b={},[4,Ge(e.body,t)];case 1:switch(n=f.apply(void 0,r.concat([(b.body=d.sent(),b)])),o="UnknownError",o=We(e,n.body),o){case"BadGatewayException":case"com.amazonaws.lexruntimeservice#BadGatewayException":return[3,2];case"BadRequestException":case"com.amazonaws.lexruntimeservice#BadRequestException":return[3,4];case"ConflictException":case"com.amazonaws.lexruntimeservice#ConflictException":return[3,6];case"DependencyFailedException":case"com.amazonaws.lexruntimeservice#DependencyFailedException":return[3,8];case"InternalFailureException":case"com.amazonaws.lexruntimeservice#InternalFailureException":return[3,10];case"LimitExceededException":case"com.amazonaws.lexruntimeservice#LimitExceededException":return[3,12];case"LoopDetectedException":case"com.amazonaws.lexruntimeservice#LoopDetectedException":return[3,14];case"NotFoundException":case"com.amazonaws.lexruntimeservice#NotFoundException":return[3,16]}return[3,18];case 2:return s=[{}],[4,Ee(n,t)];case 3:return i=f.apply(void 0,[f.apply(void 0,s.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,19];case 4:return a=[{}],[4,Me(n,t)];case 5:return i=f.apply(void 0,[f.apply(void 0,a.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,19];case 6:return u=[{}],[4,Ae(n,t)];case 7:return i=f.apply(void 0,[f.apply(void 0,u.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,19];case 8:return c=[{}],[4,Ie(n,t)];case 9:return i=f.apply(void 0,[f.apply(void 0,c.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,19];case 10:return l=[{}],[4,ke(n,t)];case 11:return i=f.apply(void 0,[f.apply(void 0,l.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,19];case 12:return h=[{}],[4,Oe(n,t)];case 13:return i=f.apply(void 0,[f.apply(void 0,h.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,19];case 14:return p=[{}],[4,xe(n,t)];case 15:return i=f.apply(void 0,[f.apply(void 0,p.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,19];case 16:return v=[{}],[4,Te(n,t)];case 17:return i=f.apply(void 0,[f.apply(void 0,v.concat([d.sent()])),{name:o,$metadata:Ke(e)}]),[3,19];case 18:g=n.body,o=g.code||g.Code||o,i=f(f({},g),{name:""+o,message:g.message||g.Message||o,$fault:"client",$metadata:Ke(e)}),d.label=19;case 19:return m=i.message||i.Message||o,i.message=m,delete i.Message,[2,Promise.reject(Object.assign(new Error(m),i))]}}))}))},Ee=function(e,t){return l(void 0,void 0,void 0,(function(){var t,n;return d(this,(function(r){return t={name:"BadGatewayException",$fault:"server",$metadata:Ke(e),Message:void 0},void 0!==(n=e.body).Message&&null!==n.Message&&(t.Message=n.Message),[2,t]}))}))},Me=function(e,t){return l(void 0,void 0,void 0,(function(){var t,n;return d(this,(function(r){return t={name:"BadRequestException",$fault:"client",$metadata:Ke(e),message:void 0},void 0!==(n=e.body).message&&null!==n.message&&(t.message=n.message),[2,t]}))}))},Ae=function(e,t){return l(void 0,void 0,void 0,(function(){var t,n;return d(this,(function(r){return t={name:"ConflictException",$fault:"client",$metadata:Ke(e),message:void 0},void 0!==(n=e.body).message&&null!==n.message&&(t.message=n.message),[2,t]}))}))},Ie=function(e,t){return l(void 0,void 0,void 0,(function(){var t,n;return d(this,(function(r){return t={name:"DependencyFailedException",$fault:"client",$metadata:Ke(e),Message:void 0},void 0!==(n=e.body).Message&&null!==n.Message&&(t.Message=n.Message),[2,t]}))}))},ke=function(e,t){return l(void 0,void 0,void 0,(function(){var t,n;return d(this,(function(r){return t={name:"InternalFailureException",$fault:"server",$metadata:Ke(e),message:void 0},void 0!==(n=e.body).message&&null!==n.message&&(t.message=n.message),[2,t]}))}))},Oe=function(e,t){return l(void 0,void 0,void 0,(function(){var t,n;return d(this,(function(r){return t={name:"LimitExceededException",$fault:"client",$metadata:Ke(e),message:void 0,retryAfterSeconds:void 0},void 0!==e.headers["retry-after"]&&(t.retryAfterSeconds=e.headers["retry-after"]),void 0!==(n=e.body).message&&null!==n.message&&(t.message=n.message),[2,t]}))}))},xe=function(e,t){return l(void 0,void 0,void 0,(function(){var t,n;return d(this,(function(r){return t={name:"LoopDetectedException",$fault:"server",$metadata:Ke(e),Message:void 0},void 0!==(n=e.body).Message&&null!==n.Message&&(t.Message=n.Message),[2,t]}))}))},Ce=function(e,t){return l(void 0,void 0,void 0,(function(){var t,n;return d(this,(function(r){return t={name:"NotAcceptableException",$fault:"client",$metadata:Ke(e),message:void 0},void 0!==(n=e.body).message&&null!==n.message&&(t.message=n.message),[2,t]}))}))},Te=function(e,t){return l(void 0,void 0,void 0,(function(){var t,n;return d(this,(function(r){return t={name:"NotFoundException",$fault:"client",$metadata:Ke(e),message:void 0},void 0!==(n=e.body).message&&null!==n.message&&(t.message=n.message),[2,t]}))}))},Pe=function(e,t){return l(void 0,void 0,void 0,(function(){var t,n;return d(this,(function(r){return t={name:"RequestTimeoutException",$fault:"client",$metadata:Ke(e),message:void 0},void 0!==(n=e.body).message&&null!==n.message&&(t.message=n.message),[2,t]}))}))},Ne=function(e,t){return l(void 0,void 0,void 0,(function(){var t,n;return d(this,(function(r){return t={name:"UnsupportedMediaTypeException",$fault:"client",$metadata:Ke(e),message:void 0},void 0!==(n=e.body).message&&null!==n.message&&(t.message=n.message),[2,t]}))}))},Re=function(e,t){return Object.entries(e).reduce((function(e,t){var n,r=h(t,2),i=r[0],o=r[1];return f(f({},e),((n={})[i]=o,n))}),{})},Le=function(e,t){return(e||[]).map((function(e){return function(e,t){return{attachmentLinkUrl:void 0!==e.attachmentLinkUrl&&null!==e.attachmentLinkUrl?e.attachmentLinkUrl:void 0,buttons:void 0!==e.buttons&&null!==e.buttons?Ue(e.buttons,t):void 0,imageUrl:void 0!==e.imageUrl&&null!==e.imageUrl?e.imageUrl:void 0,subTitle:void 0!==e.subTitle&&null!==e.subTitle?e.subTitle:void 0,title:void 0!==e.title&&null!==e.title?e.title:void 0}}(e,t)}))},je=function(e,t){return{score:void 0!==e.score&&null!==e.score?e.score:void 0}},De=function(e,t){return(e||[]).map((function(e){return Be(e,t)}))},Ue=function(e,t){return(e||[]).map((function(e){return function(e,t){return{text:void 0!==e.text&&null!==e.text?e.text:void 0,value:void 0!==e.value&&null!==e.value?e.value:void 0}}(e)}))},Be=function(e,t){return{intentName:void 0!==e.intentName&&null!==e.intentName?e.intentName:void 0,nluIntentConfidence:void 0!==e.nluIntentConfidence&&null!==e.nluIntentConfidence?je(e.nluIntentConfidence,t):void 0,slots:void 0!==e.slots&&null!==e.slots?qe(e.slots,t):void 0}},Fe=function(e,t){return{contentType:void 0!==e.contentType&&null!==e.contentType?e.contentType:void 0,genericAttachments:void 0!==e.genericAttachments&&null!==e.genericAttachments?Le(e.genericAttachments,t):void 0,version:void 0!==e.version&&null!==e.version?e.version:void 0}},ze=function(e,t){return{sentimentLabel:void 0!==e.sentimentLabel&&null!==e.sentimentLabel?e.sentimentLabel:void 0,sentimentScore:void 0!==e.sentimentScore&&null!==e.sentimentScore?e.sentimentScore:void 0}},qe=function(e,t){return Object.entries(e).reduce((function(e,t){var n,r=h(t,2),i=r[0],o=r[1];return f(f({},e),((n={})[i]=o,n))}),{})},Ke=function(e){return{httpStatusCode:e.statusCode,httpHeaders:e.headers,requestId:e.headers["x-amzn-requestid"]}},He=function(e,t){return function(e,t){return void 0===e&&(e=new Uint8Array),e instanceof Uint8Array?Promise.resolve(e):t.streamCollector(e)||Promise.resolve(new Uint8Array)}(e,t).then((function(e){return t.utf8Encoder(e)}))},Ve=function(e){return!(void 0===e||""===e||Object.getOwnPropertyNames(e).includes("length")&&0==e.length||Object.getOwnPropertyNames(e).includes("size")&&0==e.size)},Ge=function(e,t){return He(e,t).then((function(e){return e.length?JSON.parse(e):{}}))},We=function(e,t){var n,r,i=function(e){var t=e;return t.indexOf(":")>=0&&(t=t.split(":")[0]),t.indexOf("#")>=0&&(t=t.split("#")[1]),t},o=(n=e.headers,r="x-amzn-errortype",Object.keys(n).find((function(e){return e.toLowerCase()===r.toLowerCase()})));return void 0!==o?i(e.headers[o]):void 0!==t.code?i(t.code):void 0!==t.__type?i(t.__type):""},$e=n(10),Ye=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return c(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object($e.a)(t,this.serialize,this.deserialize));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"LexRuntimeServiceClient",commandName:"PostTextCommand",inputFilterSensitiveLog:U.filterSensitiveLog,outputFilterSensitiveLog:G.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"LexRuntimeServiceClient",commandName:"PostTextCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l;return d(this,(function(d){switch(d.label){case 0:if(n={"Content-Type":"application/json"},r="/bot/{botName}/alias/{botAlias}/user/{userId}/text",void 0===e.userId)throw new Error("No value provided for input HTTP label: userId.");if((i=e.userId).length<=0)throw new Error("Empty value provided for input HTTP label: userId.");if(r=r.replace("{userId}",Object(me.f)(i)),void 0===e.botAlias)throw new Error("No value provided for input HTTP label: botAlias.");if((i=e.botAlias).length<=0)throw new Error("Empty value provided for input HTTP label: botAlias.");if(r=r.replace("{botAlias}",Object(me.f)(i)),void 0===e.botName)throw new Error("No value provided for input HTTP label: botName.");if((i=e.botName).length<=0)throw new Error("Empty value provided for input HTTP label: botName.");return r=r.replace("{botName}",Object(me.f)(i)),o=JSON.stringify(f(f(f({},void 0!==e.inputText&&{inputText:e.inputText}),void 0!==e.requestAttributes&&{requestAttributes:Re(e.requestAttributes,t)}),void 0!==e.sessionAttributes&&{sessionAttributes:Re(e.sessionAttributes,t)})),[4,t.endpoint()];case 1:return s=d.sent(),a=s.hostname,u=s.protocol,c=void 0===u?"https":u,l=s.port,[2,new we.a({protocol:c,hostname:a,port:l,method:"POST",headers:n,path:r,body:o})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r;return d(this,(function(i){switch(i.label){case 0:return 200!==e.statusCode&&e.statusCode>=300?[2,Se(e,t)]:(n={$metadata:Ke(e),alternativeIntents:void 0,botVersion:void 0,dialogState:void 0,intentName:void 0,message:void 0,messageFormat:void 0,nluIntentConfidence:void 0,responseCard:void 0,sentimentResponse:void 0,sessionAttributes:void 0,sessionId:void 0,slotToElicit:void 0,slots:void 0},[4,Ge(e.body,t)]);case 1:return void 0!==(r=i.sent()).alternativeIntents&&null!==r.alternativeIntents&&(n.alternativeIntents=De(r.alternativeIntents,t)),void 0!==r.botVersion&&null!==r.botVersion&&(n.botVersion=r.botVersion),void 0!==r.dialogState&&null!==r.dialogState&&(n.dialogState=r.dialogState),void 0!==r.intentName&&null!==r.intentName&&(n.intentName=r.intentName),void 0!==r.message&&null!==r.message&&(n.message=r.message),void 0!==r.messageFormat&&null!==r.messageFormat&&(n.messageFormat=r.messageFormat),void 0!==r.nluIntentConfidence&&null!==r.nluIntentConfidence&&(n.nluIntentConfidence=je(r.nluIntentConfidence,t)),void 0!==r.responseCard&&null!==r.responseCard&&(n.responseCard=Fe(r.responseCard,t)),void 0!==r.sentimentResponse&&null!==r.sentimentResponse&&(n.sentimentResponse=ze(r.sentimentResponse,t)),void 0!==r.sessionAttributes&&null!==r.sessionAttributes&&(n.sessionAttributes=qe(r.sessionAttributes,t)),void 0!==r.sessionId&&null!==r.sessionId&&(n.sessionId=r.sessionId),void 0!==r.slotToElicit&&null!==r.slotToElicit&&(n.slotToElicit=r.slotToElicit),void 0!==r.slots&&null!==r.slots&&(n.slots=qe(r.slots,t)),[2,Promise.resolve(n)]}}))}))}(e,t)},t}(me.b),Je=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return c(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object($e.a)(t,this.serialize,this.deserialize));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"LexRuntimeServiceClient",commandName:"PostContentCommand",inputFilterSensitiveLog:N.filterSensitiveLog,outputFilterSensitiveLog:L.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"LexRuntimeServiceClient",commandName:"PostContentCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,l;return d(this,(function(d){switch(d.label){case 0:if(n=f(f(f(f({"Content-Type":"application/octet-stream","x-amz-content-sha256":"UNSIGNED-PAYLOAD"},Ve(e.requestAttributes)&&{"x-amz-lex-request-attributes":me.c.fromObject(e.requestAttributes)}),Ve(e.sessionAttributes)&&{"x-amz-lex-session-attributes":me.c.fromObject(e.sessionAttributes)}),Ve(e.contentType)&&{"Content-Type":e.contentType}),Ve(e.accept)&&{Accept:e.accept}),r="/bot/{botName}/alias/{botAlias}/user/{userId}/content",void 0===e.botAlias)throw new Error("No value provided for input HTTP label: botAlias.");if((i=e.botAlias).length<=0)throw new Error("Empty value provided for input HTTP label: botAlias.");if(r=r.replace("{botAlias}",Object(me.f)(i)),void 0===e.botName)throw new Error("No value provided for input HTTP label: botName.");if((i=e.botName).length<=0)throw new Error("Empty value provided for input HTTP label: botName.");if(r=r.replace("{botName}",Object(me.f)(i)),void 0===e.userId)throw new Error("No value provided for input HTTP label: userId.");if((i=e.userId).length<=0)throw new Error("Empty value provided for input HTTP label: userId.");return r=r.replace("{userId}",Object(me.f)(i)),void 0!==e.inputStream&&(o=e.inputStream),[4,t.endpoint()];case 1:return s=d.sent(),a=s.hostname,u=s.protocol,c=void 0===u?"https":u,l=s.port,[2,new we.a({protocol:c,hostname:a,port:l,method:"POST",headers:n,path:r,body:o})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return l(void 0,void 0,void 0,(function(){var n,r;return d(this,(function(i){return 200!==e.statusCode&&e.statusCode>=300?[2,_e(e,t)]:(n={$metadata:Ke(e),alternativeIntents:void 0,audioStream:void 0,botVersion:void 0,contentType:void 0,dialogState:void 0,inputTranscript:void 0,intentName:void 0,message:void 0,messageFormat:void 0,nluIntentConfidence:void 0,sentimentResponse:void 0,sessionAttributes:void 0,sessionId:void 0,slotToElicit:void 0,slots:void 0},void 0!==e.headers["x-amz-lex-alternative-intents"]&&(n.alternativeIntents=new me.c(e.headers["x-amz-lex-alternative-intents"])),void 0!==e.headers["x-amz-lex-message-format"]&&(n.messageFormat=e.headers["x-amz-lex-message-format"]),void 0!==e.headers["content-type"]&&(n.contentType=e.headers["content-type"]),void 0!==e.headers["x-amz-lex-message"]&&(n.message=e.headers["x-amz-lex-message"]),void 0!==e.headers["x-amz-lex-bot-version"]&&(n.botVersion=e.headers["x-amz-lex-bot-version"]),void 0!==e.headers["x-amz-lex-sentiment"]&&(n.sentimentResponse=e.headers["x-amz-lex-sentiment"]),void 0!==e.headers["x-amz-lex-slots"]&&(n.slots=new me.c(e.headers["x-amz-lex-slots"])),void 0!==e.headers["x-amz-lex-input-transcript"]&&(n.inputTranscript=e.headers["x-amz-lex-input-transcript"]),void 0!==e.headers["x-amz-lex-slot-to-elicit"]&&(n.slotToElicit=e.headers["x-amz-lex-slot-to-elicit"]),void 0!==e.headers["x-amz-lex-session-attributes"]&&(n.sessionAttributes=new me.c(e.headers["x-amz-lex-session-attributes"])),void 0!==e.headers["x-amz-lex-session-id"]&&(n.sessionId=e.headers["x-amz-lex-session-id"]),void 0!==e.headers["x-amz-lex-dialog-state"]&&(n.dialogState=e.headers["x-amz-lex-dialog-state"]),void 0!==e.headers["x-amz-lex-intent-name"]&&(n.intentName=e.headers["x-amz-lex-intent-name"]),void 0!==e.headers["x-amz-lex-nlu-intent-confidence"]&&(n.nluIntentConfidence=new me.c(e.headers["x-amz-lex-nlu-intent-confidence"])),r=e.body,n.audioStream=r,[2,Promise.resolve(n)])}))}))}(e,t)},t}(me.b),Ze=n(89),Xe=n(50),Qe=function(e){if(e instanceof Blob||e instanceof ReadableStream)return new Response(e).arrayBuffer().then((function(e){return new Uint8Array(e)}));throw new Error("Readable is not supported.")},et=(ye=function(e,t){return(ye=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)},function(e,t){function n(){this.constructor=e}ye(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),tt=function(){return(tt=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},nt=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},rt=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},it=new r.a("AWSLexProvider"),ot=function(e){function t(t){void 0===t&&(t={});var n=e.call(this,t)||this;return n._botsCompleteCallback={},n}return et(t,e),t.prototype.getProviderName=function(){return"AWSLexProvider"},t.prototype.reportBotStatus=function(e,t){var n=this;it.debug("postContent state",e.dialogState),"ReadyForFulfillment"!==e.dialogState&&"Fulfilled"!==e.dialogState||("function"==typeof this._botsCompleteCallback[t]&&setTimeout((function(){return n._botsCompleteCallback[t](null,{slots:e.slots})}),0),this._config&&"function"==typeof this._config[t].onComplete&&setTimeout((function(){return n._config[t].onComplete(null,{slots:e.slots})}),0)),"Failed"===e.dialogState&&("function"==typeof this._botsCompleteCallback[t]&&setTimeout((function(){return n._botsCompleteCallback[t]("Bot conversation failed")}),0),this._config&&"function"==typeof this._config[t].onComplete&&setTimeout((function(){return n._config[t].onComplete("Bot conversation failed")}),0))},t.prototype.sendMessage=function(e,t){return nt(this,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,l;return rt(this,(function(d){switch(d.label){case 0:return this._config[e]?[4,Ze.a.get()]:[2,Promise.reject("Bot "+e+" does not exist")];case 1:if(!(n=d.sent()))return[2,Promise.reject("No credentials")];if(this.lexRuntimeServiceClient=new be({region:this._config[e].region,credentials:n,customUserAgent:Object(Xe.b)()}),"string"!=typeof t)return[3,6];r={botAlias:this._config[e].alias,botName:e,inputText:t,userId:n.identityId},it.debug("postText to lex",t),d.label=2;case 2:return d.trys.push([2,4,,5]),i=new Ye(r),[4,this.lexRuntimeServiceClient.send(i)];case 3:return c=d.sent(),this.reportBotStatus(c,e),[2,c];case 4:return o=d.sent(),[2,Promise.reject(o)];case 5:return[3,11];case 6:s=t.content,a=t.options.messageType,r="voice"===a?{botAlias:this._config[e].alias,botName:e,contentType:"audio/x-l16; sample-rate=16000",inputStream:s,userId:n.identityId,accept:"audio/mpeg"}:{botAlias:this._config[e].alias,botName:e,contentType:"text/plain; charset=utf-8",inputStream:s,userId:n.identityId,accept:"audio/mpeg"},it.debug("postContent to lex",t),d.label=7;case 7:return d.trys.push([7,10,,11]),u=new Je(r),[4,this.lexRuntimeServiceClient.send(u)];case 8:return c=d.sent(),[4,Qe(c.audioStream)];case 9:return f=d.sent(),this.reportBotStatus(c,e),[2,tt(tt({},c),{audioStream:f})];case 10:return l=d.sent(),[2,Promise.reject(l)];case 11:return[2]}}))}))},t.prototype.onComplete=function(e,t){if(!this._config[e])throw new ErrorEvent("Bot "+e+" does not exist");this._botsCompleteCallback[e]=t},t}(a),st=function(){return(st=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},at=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},ut=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},ct=new r.a("Interactions"),ft=new(function(){function e(e){this._options=e,ct.debug("Interactions Options",this._options),this._pluggables={}}return e.prototype.getModuleName=function(){return"Interactions"},e.prototype.configure=function(e){var t=this,n=e?e.Interactions||e:{};ct.debug("configure Interactions",{opt:n}),this._options=st(st({bots:{}},n),n.Interactions);var r=this._options.aws_bots_config,i=this._options.bots;return!Object.keys(i).length&&r&&Array.isArray(r)&&r.forEach((function(e){t._options.bots[e.name]=e})),!this._pluggables.AWSLexProvider&&i&&Object.keys(i).map((function(e){return i[e]})).find((function(e){return!e.providerName||"AWSLexProvider"===e.providerName}))&&(this._pluggables.AWSLexProvider=new ot),Object.keys(this._pluggables).map((function(e){t._pluggables[e].configure(t._options.bots)})),this._options},e.prototype.addPluggable=function(e){if(e&&"Interactions"===e.getCategory()){if(this._pluggables[e.getProviderName()])throw new Error("Bot "+e.getProviderName()+" already plugged");return e.configure(this._options.bots),void(this._pluggables[e.getProviderName()]=e)}},e.prototype.send=function(e,t){return at(this,void 0,void 0,(function(){var n;return ut(this,(function(r){switch(r.label){case 0:if(!this._options.bots||!this._options.bots[e])throw new Error("Bot "+e+" does not exist");if(n=this._options.bots[e].providerName||"AWSLexProvider",!this._pluggables[n])throw new Error("Bot "+n+" does not have valid pluggin did you try addPluggable first?");return[4,this._pluggables[n].sendMessage(e,t)];case 1:return[2,r.sent()]}}))}))},e.prototype.onComplete=function(e,t){if(!this._options.bots||!this._options.bots[e])throw new Error("Bot "+e+" does not exist");var n=this._options.bots[e].providerName||"AWSLexProvider";if(!this._pluggables[n])throw new Error("Bot "+n+" does not have valid pluggin did you try addPluggable first?");this._pluggables[n].onComplete(e,t)},e}())(null);i.a.register(ft)},function(e,t,n){"use strict";n.d(t,"a",(function(){return be}));var r=n(44),i=n(33),o=n(50),s=n(89),a=function(e,t){return(a=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};function u(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}a(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var c=function(){return(c=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};function f(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))}function l(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}}Object.create;var d,h,p,v,g,m,b,y;Object.create;(d||(d={})).filterSensitiveLog=function(e){return c({},e)},(h||(h={})).filterSensitiveLog=function(e){return c({},e)},(p||(p={})).filterSensitiveLog=function(e){return c({},e)},(v||(v={})).filterSensitiveLog=function(e){return c({},e)},(g||(g={})).filterSensitiveLog=function(e){return c({},e)},(m||(m={})).filterSensitiveLog=function(e){return c({},e)},(b||(b={})).filterSensitiveLog=function(e){return c({},e)},(y||(y={})).filterSensitiveLog=function(e){return c({},e)};var w,_,S,E=n(2),M=n(0),A=function(e,t){return f(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,f;return l(this,(function(l){switch(l.label){case 0:return r=[c({},e)],f={},[4,T(e.body,t)];case 1:switch(n=c.apply(void 0,r.concat([(f.body=l.sent(),f)])),o="UnknownError",o=P(e,n.body),o){case"InvalidInputException":case"com.amazonaws.personalizeevents#InvalidInputException":return[3,2]}return[3,4];case 2:return s=[{}],[4,I(n,t)];case 3:return i=c.apply(void 0,[c.apply(void 0,s.concat([l.sent()])),{name:o,$metadata:x(e)}]),[3,5];case 4:a=n.body,o=a.code||a.Code||o,i=c(c({},a),{name:""+o,message:a.message||a.Message||o,$fault:"client",$metadata:x(e)}),l.label=5;case 5:return u=i.message||i.Message||o,i.message=u,delete i.Message,[2,Promise.reject(Object.assign(new Error(u),i))]}}))}))},I=function(e,t){return f(void 0,void 0,void 0,(function(){var t,n;return l(this,(function(r){return t={name:"InvalidInputException",$fault:"client",$metadata:x(e),message:void 0},void 0!==(n=e.body).message&&null!==n.message&&(t.message=n.message),[2,t]}))}))},k=function(e,t){return e.map((function(e){return function(e,t){return c(c(c(c(c(c(c(c({},void 0!==e.eventId&&{eventId:e.eventId}),void 0!==e.eventType&&{eventType:e.eventType}),void 0!==e.eventValue&&{eventValue:e.eventValue}),void 0!==e.impression&&{impression:O(e.impression,t)}),void 0!==e.itemId&&{itemId:e.itemId}),void 0!==e.properties&&{properties:M.c.fromObject(e.properties)}),void 0!==e.recommendationId&&{recommendationId:e.recommendationId}),void 0!==e.sentAt&&{sentAt:Math.round(e.sentAt.getTime()/1e3)})}(e,t)}))},O=function(e,t){return e.map((function(e){return e}))},x=function(e){return{httpStatusCode:e.statusCode,httpHeaders:e.headers,requestId:e.headers["x-amzn-requestid"]}},C=function(e,t){return void 0===e&&(e=new Uint8Array),e instanceof Uint8Array?Promise.resolve(e):t.streamCollector(e)||Promise.resolve(new Uint8Array)},T=function(e,t){return function(e,t){return C(e,t).then((function(e){return t.utf8Encoder(e)}))}(e,t).then((function(e){return e.length?JSON.parse(e):{}}))},P=function(e,t){var n,r,i=function(e){var t=e;return t.indexOf(":")>=0&&(t=t.split(":")[0]),t.indexOf("#")>=0&&(t=t.split("#")[1]),t},o=(n=e.headers,r="x-amzn-errortype",Object.keys(n).find((function(e){return e.toLowerCase()===r.toLowerCase()})));return void 0!==o?i(e.headers[o]):void 0!==t.code?i(t.code):void 0!==t.__type?i(t.__type):""},N=n(10),R=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return u(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(N.a)(t,this.serialize,this.deserialize));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"PersonalizeEventsClient",commandName:"PutEventsCommand",inputFilterSensitiveLog:p.filterSensitiveLog,outputFilterSensitiveLog:function(e){return e}};"function"==typeof i.info&&i.info({clientName:"PersonalizeEventsClient",commandName:"PutEventsCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return f(void 0,void 0,void 0,(function(){var n,r,i,o,s,a,u,f;return l(this,(function(l){switch(l.label){case 0:return n={"Content-Type":"application/json"},r="/events",i=JSON.stringify(c(c(c(c({},void 0!==e.eventList&&{eventList:k(e.eventList,t)}),void 0!==e.sessionId&&{sessionId:e.sessionId}),void 0!==e.trackingId&&{trackingId:e.trackingId}),void 0!==e.userId&&{userId:e.userId})),[4,t.endpoint()];case 1:return o=l.sent(),s=o.hostname,a=o.protocol,u=void 0===a?"https":a,f=o.port,[2,new E.a({protocol:u,hostname:s,port:f,method:"POST",headers:n,path:r,body:i})]}}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return f(void 0,void 0,void 0,(function(){var n;return l(this,(function(r){switch(r.label){case 0:return 200!==e.statusCode&&e.statusCode>=300?[2,A(e,t)]:(n={$metadata:x(e)},[4,C(e.body,t)]);case 1:return r.sent(),[2,Promise.resolve(n)]}}))}))}(e,t)},t}(M.b),L=n(152),j=n(38),D=n(18),U=n(24),B=n(11),F=n(39),z=n(17),q=n(40),K=n(41),H=n(15),V="personalize-events.{region}.amazonaws.com",G=new Set(["ap-east-1","ap-northeast-1","ap-northeast-2","ap-south-1","ap-southeast-1","ap-southeast-2","ca-central-1","eu-central-1","eu-north-1","eu-west-1","eu-west-2","eu-west-3","me-south-1","sa-east-1","us-east-1","us-east-2","us-west-1","us-west-2"]),W=new Set(["cn-north-1","cn-northwest-1"]),$=new Set(["us-iso-east-1"]),Y=new Set(["us-isob-east-1"]),J=new Set(["us-gov-east-1","us-gov-west-1"]),Z=c(c({},{apiVersion:"2018-03-22",disableHostPrefix:!1,logger:{},regionInfoProvider:function(e,t){var n=void 0;return G.has(e)&&(n={hostname:V.replace("{region}",e),partition:"aws"}),W.has(e)&&(n={hostname:"personalize-events.{region}.amazonaws.com.cn".replace("{region}",e),partition:"aws-cn"}),$.has(e)&&(n={hostname:"personalize-events.{region}.c2s.ic.gov".replace("{region}",e),partition:"aws-iso"}),Y.has(e)&&(n={hostname:"personalize-events.{region}.sc2s.sgov.gov".replace("{region}",e),partition:"aws-iso-b"}),J.has(e)&&(n={hostname:"personalize-events.{region}.amazonaws.com".replace("{region}",e),partition:"aws-us-gov"}),void 0===n&&(n={hostname:V.replace("{region}",e),partition:"aws"}),Promise.resolve(n)},signingName:"personalize"}),{runtime:"browser",base64Decoder:z.a,base64Encoder:z.b,bodyLengthChecker:q.a,credentialDefaultProvider:Object(U.a)("Credential is missing"),defaultUserAgent:Object(K.a)(L.name,L.version),maxAttempts:B.a,region:Object(U.a)("Region is missing"),requestHandler:new D.a,sha256:j.Sha256,streamCollector:D.b,urlParser:F.a,utf8Decoder:H.a,utf8Encoder:H.b}),X=n(22),Q=n(37),ee=n(21),te=n(43),ne=n(25),re=n(23),ie=function(e){function t(t){var n=this,r=c(c({},Z),t),i=Object(X.b)(r),o=Object(X.a)(i),s=Object(ne.b)(o),a=Object(B.c)(s),u=Object(re.b)(a),f=Object(ee.b)(u);return(n=e.call(this,f)||this).config=f,n.middlewareStack.use(Object(ne.a)(n.config)),n.middlewareStack.use(Object(B.b)(n.config)),n.middlewareStack.use(Object(re.a)(n.config)),n.middlewareStack.use(Object(Q.a)(n.config)),n.middlewareStack.use(Object(ee.a)(n.config)),n.middlewareStack.use(Object(te.a)(n.config)),n}return u(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this)},t}(M.a),oe=n(36),se=n.n(oe),ae=n(108),ue=n.n(ae),ce=n(27),fe=n(26),le=(new r.a("AmazonPersonalizeProvider"),function(){function e(e){void 0===e&&(e=""),this._isBrowser=i.a.browserOrNode().isBrowser,this._timerKey=Object(ce.v1)().substr(0,15),this._refreshTimer()}return e.prototype._refreshTimer=function(){this._timer&&clearInterval(this._timer);var e=this;this._timer=setInterval((function(){e._timerKey=Object(ce.v1)().substr(0,15)}),3e4)},e.prototype.storeValue=function(e,t){var n=new Date,r=new Date;r.setTime(n.getTime()+6048e5),fe.a.setItem(this._getCachePrefix(e),t,{expires:r.getTime()})},e.prototype.retrieveValue=function(e){return fe.a.getItem(this._getCachePrefix(e))},e.prototype._getCachePrefix=function(e){return this._isBrowser?e+"."+window.location.host:"peronslize"},e.prototype.getTimerKey=function(){return this._timerKey},e.prototype.updateSessionInfo=function(e,t){var n=t.userId,r=t.sessionId;if(this._isRequireNewSession(e,n,r)){var i=Object(ce.v1)();this.storeValue("_awsct_uid",e),this.storeValue("_awsct_sid",i),t.sessionId=i}else this._isRequireUpdateSessionInfo(e,n,r)&&this.storeValue("_awsct_uid",e);t.userId=e},e.prototype._isRequireUpdateSessionInfo=function(e,t,n){return!se()(n)&&se()(t)&&!se()(e)},e.prototype.retrieveSessionInfo=function(e){var t={};return t.trackingId=e,t.sessionId=this.retrieveValue("_awsct_sid"),t.userId=this.retrieveValue("_awsct_uid"),se()(t.sessionId)&&(t.sessionId=Object(ce.v1)(),this.storeValue("_awsct_sid",t.sessionId)),this.storeValue("_awsct",e),t},e.prototype._isRequireNewSession=function(e,t,n){var r=se()(n),i=se()(e)&&!se()(t),o=!se()(e)&&!se()(t)&&!ue()(e,t);return r||i||o},e}());!function(e){e.PLAY="play",e.PAUSE="pause",e.ENDED="Ended"}(w||(w={})),function(e){e.IFRAME="IFRAME",e.VIDEO="VIDEO",e.AUDIO="AUDIO"}(_||(_={})),function(e){e.PLAY="Play",e.ENDED="Ended",e.PAUSE="Pause",e.TIME_WATCHED="TimeWatched"}(S||(S={}));var de=function(){function e(e,t){var n;this.eventActionMapping=((n={})[S.ENDED]=this.endedEventAction.bind(this),n[S.PLAY]=this.playEventAction.bind(this),n[S.PAUSE]=this.pauseEventAction.bind(this),n);var r=e.eventData;this._params=e,this._mediaElement=document.getElementById(r.properties.domElementId),this._started=!1,this._provider=t,{IFRAME:this._iframeMediaTracker,VIDEO:this._html5MediaTracker,AUDIO:this._html5MediaTracker}[this._mediaElement.tagName].bind(this)(),this._initYoutubeFrame()}return e.prototype._initYoutubeFrame=function(){this._youTubeIframeLoader={src:"https://www.youtube.com/iframe_api",loading:!1,loaded:!1,listeners:[],load:function(e){var t=this;if(this.listeners.push(e),this.loaded)setTimeout((function(){t.done()}));else if(!this.loading){this.loading=!0,window.onYouTubeIframeAPIReady=function(){t.loaded=!0,t.done()};var n=document.createElement("script");n.type="text/javascript",n.src=this.src,document.body.appendChild(n)}},done:function(){for(delete window.onYouTubeIframeAPIReady;this.listeners.length;)this.listeners.pop()(window.YT)}}},e.prototype._iframeMediaTracker=function(){var e=this;setInterval((function(){e._started&&e.recordEvent(_.IFRAME,S.TIME_WATCHED)}),3e3),this._youTubeIframeLoader.load((function(t){e._iframePlayer=new t.Player(e._mediaElement.id,{events:{onStateChange:e._onPlayerStateChange.bind(e)}})}))},e.prototype._onPlayerStateChange=function(e){var t={0:S.ENDED,1:S.PLAY,2:S.PAUSE}[e.data];t&&this.eventActionMapping[t](_.IFRAME)},e.prototype._html5MediaTracker=function(){var e=this;setInterval((function(){e._started&&e.recordEvent(_.VIDEO,S.TIME_WATCHED)}),3e3),this._mediaElement.addEventListener(w.PLAY,(function(){e.eventActionMapping[S.PLAY](_.VIDEO)}),!1),this._mediaElement.addEventListener(w.PAUSE,(function(){e.eventActionMapping[S.PAUSE](_.VIDEO)}),!1),this._mediaElement.addEventListener(w.ENDED,(function(){e.eventActionMapping[S.ENDED](_.VIDEO)}),!1)},e.prototype.playEventAction=function(e){this._started=!0,this.recordEvent(e,S.PLAY)},e.prototype.pauseEventAction=function(e){this._started=!1,this.recordEvent(e,S.PAUSE)},e.prototype.endedEventAction=function(e){this._started=!1,this.recordEvent(e,S.ENDED)},e.prototype.recordEvent=function(e,t){var n=Object.assign({},this._params),r=n.eventData;r.eventType=t,e===_.VIDEO?(r.properties.timestamp=this._mediaElement.currentTime,r.properties.duration=this._mediaElement.duration):(r.properties.timestamp=this._financial(this._iframePlayer.getCurrentTime()),r.properties.duration=this._financial(this._iframePlayer.getDuration()));var i=parseFloat(r.properties.timestamp)/parseFloat(r.properties.duration);r.properties.eventValue=Number(i.toFixed(4)),delete r.properties.domElementId,this._provider.putToBuffer(n)},e.prototype._financial=function(e){return Number.parseFloat(e).toFixed(4)},e}(),he=n(252),pe=n.n(he),ve=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},ge=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},me=new r.a("AmazonPersonalizeProvider"),be=function(){function e(e){this._buffer=[],this._config=e||{},this._config.flushSize=this._config.flushSize>0&&this._config.flushSize<=10?this._config.flushSize:5,this._config.flushInterval=this._config.flushInterval||5e3,this._sessionManager=new le,se()(this._config.trackingId)||(this._sessionInfo=this._sessionManager.retrieveSessionInfo(this._config.trackingId)),this._isBrowser=i.a.browserOrNode().isBrowser,this._setupTimer()}return e.prototype._setupTimer=function(){this._timer&&clearInterval(this._timer);var e=this._config.flushInterval,t=this;this._timer=setInterval((function(){t._sendFromBuffer()}),e)},e.prototype.record=function(e){return ve(this,void 0,void 0,(function(){var t,n,r,i,o;return ge(this,(function(s){switch(s.label){case 0:return[4,this._getCredentials()];case 1:return(t=s.sent())?(Object.assign(e,{config:this._config,credentials:t,sentAt:new Date}),n=e.event,r=n.eventType,i=n.properties,"Identify"===r?(this._sessionManager.updateSessionInfo(i&&i.userId?i.userId:"",this._sessionInfo),[2]):(se()(e.event.userId)||this._sessionManager.updateSessionInfo(e.event.userId,this._sessionInfo),o=this.generateRequestParams(e,this._sessionInfo),"MediaAutoTrack"!==r?[3,7]:this._isBrowser?se()(pe()(o,"eventData.properties.domElementId",null))?[3,3]:[4,this.isElementFullyLoaded(this.loadElement,o.eventData.properties.domElementId,500,5)]:[3,5])):[2,Promise.resolve(!1)];case 2:return s.sent()?new de(o,this):me.debug("Cannot find the media element."),[3,4];case 3:me.debug("Missing domElementId field in 'properties' for MediaAutoTrack event type."),s.label=4;case 4:return[3,6];case 5:me.debug("MediaAutoTrack only for browser"),s.label=6;case 6:return[2];case 7:return[2,this.putToBuffer(o)]}}))}))},e.prototype.loadElement=function(e){return new Promise((function(t,n){return document.getElementById(e)&&document.getElementById(e).clientHeight?t(!0):n(!0)}))},e.prototype.isElementFullyLoaded=function(e,t,n,r){var i=this;return new Promise((function(o,s){return e(t).then(o).catch((function(a){return r-1>0?(u=n,new Promise((function(e){return setTimeout(e,u)}))).then(i.isElementFullyLoaded.bind(null,e,t,n,r-1)).then(o).catch(s):s(a);var u}))}))},e.prototype.getCategory=function(){return"Analytics"},e.prototype.getProviderName=function(){return"AmazonPersonalize"},e.prototype.configure=function(e){me.debug("configure Analytics",e);var t=e||{};return this._config=Object.assign({},this._config,t),se()(this._config.trackingId)||(this._sessionInfo=this._sessionManager.retrieveSessionInfo(this._config.trackingId)),this._setupTimer(),this._config},e.prototype.generateRequestParams=function(e,t){var n={},r=e.event,i=r.eventType,o=r.properties;return n.eventData={eventType:i,properties:o},n.sessionInfo=t,n.sentAt=e.sentAt,n.credentials=e.credentials,n.config=e.config,n},e.prototype._sendEvents=function(e){var t=e.length;if(0!==t){var n=e[0],r=n.config,i=n.credentials,o=n.sessionInfo;if(!this._init(r,i))return!1;if(t>0){for(var s=[],a=0;a<t;a+=1){var u=e.shift(),c=this._generateSingleRecordPayload(u,o);s.push(c)}var f={};f.trackingId=o.trackingId,f.sessionId=o.sessionId,f.userId=o.userId,f.eventList=[],s.forEach((function(e){f.eventList.push(e)}));var l=new R(f);this._personalize.send(l,(function(e){e?me.debug("Failed to call putEvents in Personalize",e):me.debug("Put events")}))}}else me.debug("events array is empty, directly return")},e.prototype.putToBuffer=function(e){return this._buffer.length<this._config.flushSize?this._buffer.push(e):(this._buffer.push(e),this._sendFromBuffer()),Promise.resolve(!0)},e.prototype._sendFromBuffer=function(){var e=this,t=this._buffer.length;if(!(t<=0)){for(var n=[],r=null,i=[],o=0;o<t;o+=1){var s=this._buffer.shift(),a=s.credentials,u=s.sessionInfo;0===o?(i.push(s),r=a):ue()(u,this._sessionInfo)&&a.sessionToken===r.sessionToken&&a.identityId===r.identityId?(me.debug("no change for cred, put event in the same group"),i.push(s)):(n.push(i),(i=[]).push(s),r=a,this._sessionInfo=u)}n.push(i),n.map((function(t){e._sendEvents(t)}))}},e.prototype._generateSingleRecordPayload=function(e,t){var n=e.eventData,r=e.sentAt,i={};return i.sentAt=r,i.properties=n.properties&&JSON.stringify(n.properties),i.eventId=this._sessionManager.getTimerKey()+t.sessionId,i.eventType=n.eventType,i},e.prototype._init=function(e,t){if(me.debug("init clients"),this._personalize&&this._config.credentials&&this._config.credentials.sessionToken===t.sessionToken&&this._config.credentials.identityId===t.identityId)return me.debug("no change for analytics config, directly return from init"),!0;this._config.credentials=t;var n=e.region;return me.debug("initialize personalize with credentials",t),this._personalize=new ie({region:n,credentials:t,customUserAgent:Object(o.b)()}),!0},e.prototype._getCredentials=function(){var e=this;return s.a.get().then((function(t){return t?(me.debug("set credentials for analytics",e._config.credentials),s.a.shear(t)):null})).catch((function(e){return me.debug("ensure credentials error",e),null}))},e}()},function(e,t,n){"use strict";n.d(t,"a",(function(){return X}));var r=n(44),i=n(88),o=n(221),s=n(19),a=n(144),u=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},c=function(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(u(arguments[t]));return e},f=[],l=function(){function e(e,t){this.context=e,this.methodName=t,this._originalMethod=e[t].bind(e)}return e.add=function(e,t,n){d(e,t).set(n)},e.remove=function(e,t){d(e,t).remove()},e.prototype.set=function(e){var t=this;this.context[this.methodName]=function(){for(var n=[],r=0;r<arguments.length;r++)n[r]=arguments[r];return e(t._originalMethod.apply(t,c(n)))}},e.prototype.remove=function(){this.context[this.methodName]=this._originalMethod},e}();function d(e,t){var n=f.filter((function(n){return n.context===e&&n.methodName===t}))[0];return n||(n=new l(e,t),f.push(n)),n}var h=n(33),p=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},v=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},g=new r.a("PageViewTracker"),m="aws-amplify-analytics-prevUrl",b={enable:!1,provider:"AWSPinpoint",getUrl:function(){return h.a.browserOrNode().isBrowser?window.location.origin+window.location.pathname:""}},y=function(){function e(e,t){g.debug("initialize pageview tracker with opts",t),this._config=Object.assign({},b,t),this._tracker=e,this._hasEnabled=!1,this._trackFunc=this._trackFunc.bind(this),"SPA"===this._config.type?this._pageViewTrackSPA():this._pageViewTrackDefault()}return e.prototype.configure=function(e){return Object.assign(this._config,e),"SPA"===this._config.type&&this._pageViewTrackSPA(),this._config},e.prototype._isSameUrl=function(){return sessionStorage.getItem(m)===this._config.getUrl()&&(g.debug("the url is same"),!0)},e.prototype._pageViewTrackDefault=function(){return p(this,void 0,void 0,(function(){var e,t,n,r;return v(this,(function(i){switch(i.label){case 0:return h.a.browserOrNode().isBrowser&&window.addEventListener&&window.sessionStorage?(e=this._config.getUrl(),"function"!=typeof this._config.attributes?[3,2]:[4,this._config.attributes()]):(g.debug("not in the supported web enviroment"),[2]);case 1:return n=i.sent(),[3,3];case 2:n=this._config.attributes,i.label=3;case 3:return t=n,r=Object.assign({url:e},t),this._config.enable&&!this._isSameUrl()&&(this._tracker({name:this._config.eventName||"pageView",attributes:r},this._config.provider).catch((function(e){g.debug("Failed to record the page view event",e)})),sessionStorage.setItem(m,e)),[2]}}))}))},e.prototype._trackFunc=function(){return p(this,void 0,void 0,(function(){var e,t,n,r;return v(this,(function(i){switch(i.label){case 0:return h.a.browserOrNode().isBrowser&&window.addEventListener&&history.pushState&&window.sessionStorage?(e=this._config.getUrl(),"function"!=typeof this._config.attributes?[3,2]:[4,this._config.attributes()]):(g.debug("not in the supported web enviroment"),[2]);case 1:return n=i.sent(),[3,3];case 2:n=this._config.attributes,i.label=3;case 3:return t=n,r=Object.assign({url:e},t),this._isSameUrl()||(this._tracker({name:this._config.eventName||"pageView",attributes:r},this._config.provider).catch((function(e){g.debug("Failed to record the page view event",e)})),sessionStorage.setItem(m,e)),[2]}}))}))},e.prototype._pageViewTrackSPA=function(){h.a.browserOrNode().isBrowser&&window.addEventListener&&history.pushState?this._config.enable&&!this._hasEnabled?(l.add(history,"pushState",this._trackFunc),l.add(history,"replaceState",this._trackFunc),window.addEventListener("popstate",this._trackFunc),this._trackFunc(),this._hasEnabled=!0):(l.remove(history,"pushState"),l.remove(history,"replaceState"),window.removeEventListener("popstate",this._trackFunc),this._hasEnabled=!1):g.debug("not in the supported web enviroment")},e}(),w=h.a.browserOrNode().isBrowser&&window.Element?window.Element.prototype:null,_=w?w.matches||w.matchesSelector||w.webkitMatchesSelector||w.mozMatchesSelector||w.msMatchesSelector||w.oMatchesSelector:null;function S(e,t){if(e&&1===e.nodeType&&t){if("string"==typeof t||1===t.nodeType)return e===t||E(e,t);if("length"in t)for(var n=0,r=void 0;r=t[n];n++)if(e===r||E(e,r))return!0}return!1}function E(e,t){if("string"!=typeof t)return!1;if(_)return _.call(e,t);for(var n=e.parentNode.querySelectorAll(t),r=0,i=void 0;i=n[r];r++)if(i===e)return!0;return!1}function M(e,t,n,r,i){void 0===i&&(i={});var o=function(e){var t;if(i.composed&&"function"==typeof e.composedPath)for(var o=e.composedPath(),s=0,a=void 0;a=o[s];s++)1===a.nodeType&&S(a,n)&&(t=a);else t=function(e,t,n){if(void 0===n&&(n=!1),e&&1===e.nodeType&&t)for(var r,i=(n?[e]:[]).concat(function(e){for(var t=[],n=e;n&&n.parentNode&&1===n.parentNode.nodeType;)n=n.parentNode,t.push(n);return t}(e)),o=0;r=i[o];o++)if(S(r,t))return r}(e.target,n,!0);t&&r.call(t,e,t)};return e.addEventListener(t,o,i.useCapture),{destroy:function(){e.removeEventListener(t,o,i.useCapture)}}}var A=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},I=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},k=new r.a("EventTracker"),O={enable:!1,events:["click"],selectorPrefix:"data-amplify-analytics-",provider:"AWSPinpoint"},x=function(){function e(e,t){h.a.browserOrNode().isBrowser&&window.addEventListener?(this._config=Object.assign({},O,t),this._tracker=e,this._delegates={},this._trackFunc=this._trackFunc.bind(this),k.debug("initialize pageview tracker with opts",this._config),this.configure(this._config)):k.debug("not in the supported web environment")}return e.prototype.configure=function(e){var t=this;if(Object.assign(this._config,e),this._config.enable){if(this._config.enable&&0===Object.keys(this._delegates).length){var n="["+this._config.selectorPrefix+"on]";this._config.events.forEach((function(e){t._delegates[e]=M(document,e,n,t._trackFunc,{composed:!0,useCapture:!0})}))}}else Object.keys(this._delegates).forEach((function(e){"function"==typeof t._delegates[e].destroy&&t._delegates[e].destroy()})),this._delegates={};return this._config},e.prototype._trackFunc=function(e,t){return A(this,void 0,void 0,(function(){var n,r,i,o,s,a,u;return I(this,(function(c){switch(c.label){case 0:return n={},r=t.getAttribute(this._config.selectorPrefix+"on").split(/\s*,\s*/),i=t.getAttribute(this._config.selectorPrefix+"name"),(o=t.getAttribute(this._config.selectorPrefix+"attrs"))&&o.split(/\s*,\s*/).forEach((function(e){var t=e.trim().split(/\s*:\s*/);n[t[0]]=t[1]})),"function"!=typeof this._config.attributes?[3,2]:[4,this._config.attributes()];case 1:return a=c.sent(),[3,3];case 2:a=this._config.attributes,c.label=3;case 3:return s=a,u=Object.assign({type:e.type,target:e.target.localName+" with id "+e.target.id},s,n),k.debug("events needed to be recorded",r),k.debug("attributes needed to be attached",n),r.indexOf(e.type)<0?(k.debug("event "+e.type+" is not selected to be recorded"),[2]):(this._tracker({name:i||"event",attributes:u},this._config.provider).catch((function(t){k.debug("Failed to record the "+e.type+" event', "+t)})),[2])}}))}))},e}(),C=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},T=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},P=new r.a("SessionTracker"),N={enable:!1,provider:"AWSPinpoint"},R=!1,L=function(){function e(e,t){this._config=Object.assign({},N,t),this._tracker=e,this._hasEnabled=!1,this._trackFunc=this._trackFunc.bind(this),this._trackBeforeUnload=this._trackBeforeUnload.bind(this),this.configure(this._config)}return e.prototype._envCheck=function(){if(!h.a.browserOrNode().isBrowser)return!1;if(!document||!document.addEventListener)return P.debug("not in the supported web environment"),!1;if(void 0!==document.hidden)this._hidden="hidden",this._visibilityChange="visibilitychange";else if(void 0!==document.msHidden)this._hidden="msHidden",this._visibilityChange="msvisibilitychange";else{if(void 0===document.webkitHidden)return P.debug("not in the supported web environment"),!1;this._hidden="webkitHidden",this._visibilityChange="webkitvisibilitychange"}return!0},e.prototype._trackFunc=function(){return C(this,void 0,void 0,(function(){var e,t,n;return T(this,(function(r){switch(r.label){case 0:return"function"!=typeof this._config.attributes?[3,2]:[4,this._config.attributes()];case 1:return t=r.sent(),[3,3];case 2:t=this._config.attributes,r.label=3;case 3:return e=t,n=Object.assign({},e),document.visibilityState===this._hidden?this._tracker({name:"_session.stop",attributes:n},this._config.provider).catch((function(e){P.debug("record session stop event failed.",e)})):this._tracker({name:"_session.start",attributes:n},this._config.provider).catch((function(e){P.debug("record session start event failed.",e)})),[2]}}))}))},e.prototype._trackBeforeUnload=function(e){var t=this;("function"==typeof this._config.attributes?Promise.resolve(this._config.attributes()):Promise.resolve(this._config.attributes)).then((function(e){var n=Object.assign({},e);t._tracker({name:"_session.stop",attributes:n,immediate:!0},t._config.provider).catch((function(e){P.debug("record session stop event failed.",e)}))}))},e.prototype._sendInitialEvent=function(){return C(this,void 0,void 0,(function(){var e,t,n;return T(this,(function(r){switch(r.label){case 0:return R?(P.debug("the start session has been sent when the page is loaded"),[2]):(R=!0,"function"!=typeof this._config.attributes?[3,2]:[4,this._config.attributes()]);case 1:return t=r.sent(),[3,3];case 2:t=this._config.attributes,r.label=3;case 3:return e=t,n=Object.assign({},e),this._tracker({name:"_session.start",attributes:n},this._config.provider).catch((function(e){P.debug("record session start event failed.",e)})),[2]}}))}))},e.prototype.configure=function(e){return this._envCheck()?(Object.assign(this._config,e),this._config.enable&&!this._hasEnabled?(this._sendInitialEvent(),document.addEventListener(this._visibilityChange,this._trackFunc,!1),window.addEventListener("beforeunload",this._trackBeforeUnload,!1),this._hasEnabled=!0):!this._config.enable&&this._hasEnabled&&(document.removeEventListener(this._visibilityChange,this._trackFunc,!1),window.removeEventListener("beforeunload",this._trackBeforeUnload,!1),this._hasEnabled=!1),this._config):this._config},e}(),j=function(){return(j=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},D=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},U=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},B=new r.a("AnalyticsClass"),F="undefined"!=typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("amplify_default"):"@@amplify_default",z={pageView:y,event:x,session:L},q=null,K=function(){function e(){this._config={},this._pluggables=[],this._disabled=!1,this._trackers={},q=this,this.record=this.record.bind(this),i.a.listen("auth",W),i.a.listen("storage",W),i.a.listen("analytics",W)}return e.prototype.getModuleName=function(){return"Analytics"},e.prototype.configure=function(e){var t=this;if(!e)return this._config;B.debug("configure Analytics",e);var n,r,s,u=o.a.parseMobilehubConfig(e);return this._config=Object.assign({},this._config,u.Analytics,e),this._config.disabled&&(this._disabled=!0),void 0===this._config.autoSessionRecord&&(this._config.autoSessionRecord=!0),this._pluggables.forEach((function(e){var n="AWSPinpoint"!==e.getProviderName()||t._config.AWSPinpoint?t._config[e.getProviderName()]:t._config;e.configure(j({disabled:t._config.disabled,autoSessionRecord:t._config.autoSessionRecord},n))})),0===this._pluggables.length&&this.addPluggable(new a.a),n="configured",r=null,s="The Analytics category has been configured successfully",i.a.dispatch("analytics",{event:n,data:r,message:s},"Analytics",F),B.debug("current configuration",this._config),this._config},e.prototype.addPluggable=function(e){if(e&&"Analytics"===e.getCategory()){this._pluggables.push(e);var t="AWSPinpoint"!==e.getProviderName()||this._config.AWSPinpoint?this._config[e.getProviderName()]:this._config,n=j({disabled:this._config.disabled},t);return e.configure(n),n}},e.prototype.getPluggable=function(e){for(var t=0;t<this._pluggables.length;t+=1){var n=this._pluggables[t];if(n.getProviderName()===e)return n}return B.debug("No plugin found with providerName",e),null},e.prototype.removePluggable=function(e){for(var t=0;t<this._pluggables.length&&this._pluggables[t].getProviderName()!==e;)t+=1;return t===this._pluggables.length?void B.debug("No plugin found with providerName",e):void this._pluggables.splice(t,t+1)},e.prototype.disable=function(){this._disabled=!0},e.prototype.enable=function(){this._disabled=!1},e.prototype.startSession=function(e){return D(this,void 0,void 0,(function(){var t;return U(this,(function(n){return t={event:{name:"_session.start"},provider:e},[2,this._sendEvent(t)]}))}))},e.prototype.stopSession=function(e){return D(this,void 0,void 0,(function(){var t;return U(this,(function(n){return t={event:{name:"_session.stop"},provider:e},[2,this._sendEvent(t)]}))}))},e.prototype.record=function(e,t,n){return D(this,void 0,void 0,(function(){var r;return U(this,(function(i){return r=null,r="string"==typeof e?{event:{name:e,attributes:t,metrics:n},provider:"AWSPinpoint"}:{event:e,provider:t},[2,this._sendEvent(r)]}))}))},e.prototype.updateEndpoint=function(e,t){return D(this,void 0,void 0,(function(){var n;return U(this,(function(r){return n=j(j({},e),{name:"_update_endpoint"}),[2,this.record(n,t)]}))}))},e.prototype._sendEvent=function(e){var t=this;if(this._disabled)return B.debug("Analytics has been disabled"),Promise.resolve();var n=e.provider?e.provider:"AWSPinpoint";return new Promise((function(r,i){t._pluggables.forEach((function(t){t.getProviderName()===n&&t.record(e,{resolve:r,reject:i})}))}))},e.prototype.autoTrack=function(e,t){if(z[e]){"session"===e&&(this._config.autoSessionRecord=t.enable);var n=this._trackers[e];n?n.configure(t):this._trackers[e]=new z[e](this.record,t)}else B.debug("invalid tracker type")},e}(),H=!1,V=!1,G=!1,W=function(e){var t=e.channel,n=e.payload;switch(B.debug("on hub capsule "+t,n),t){case"auth":Y(n);break;case"storage":$(n);break;case"analytics":J(n)}},$=function(e){var t=e.data,n=t.attrs,r=t.metrics;n&&G&&q.record({name:"Storage",attributes:n,metrics:r}).catch((function(e){B.debug("Failed to send the storage event automatically",e)}))},Y=function(e){var t=e.event;if(t){var n=function(e){return D(void 0,void 0,void 0,(function(){var t;return U(this,(function(n){switch(n.label){case 0:if(!V||!G)return[3,4];n.label=1;case 1:return n.trys.push([1,3,,4]),[4,q.record({name:"_userauth."+e})];case 2:return[2,n.sent()];case 3:return t=n.sent(),B.debug("Failed to send the "+e+" event automatically",t),[3,4];case 4:return[2]}}))}))};switch(t){case"signIn":return n("sign_in");case"signUp":return n("sign_up");case"signOut":return n("sign_out");case"signIn_failure":return n("auth_fail");case"configured":(V=!0)&&G&&Z()}}},J=function(e){var t=e.event;if(t)switch(t){case"pinpointProvider_configured":G=!0,V&&G&&Z()}},Z=function(){var e=q.configure();!H&&e.autoSessionRecord&&(q.updateEndpoint({immediate:!0}).catch((function(e){B.debug("Failed to update the endpoint",e)})),H=!0),q.autoTrack("session",{enable:e.autoSessionRecord})},X=new K;s.a.register(X)},function(e,t,n){"use strict";n.d(t,"a",(function(){return Zt}));var r=n(44),i=n(145),o=function(e,t){return(o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}o(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var a=function(){return(a=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};function u(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))}function c(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}}Object.create;Object.create;var f,l,d,h,p,v,g,m,b,y,w,_,S,E,M,A,I,k,O,x,C,T,P,N,R,L,j,D,U,B,F,z,q,K,H,V,G,W,$,Y,J,Z,X,Q,ee,te,ne,re,ie,oe,se,ae,ue,ce,fe,le,de,he,pe,ve,ge,me,be,ye,we,_e,Se,Ee,Me,Ae,Ie,ke,Oe,xe,Ce,Te,Pe,Ne,Re,Le,je,De,Ue,Be,Fe,ze,qe,Ke,He,Ve,Ge,We,$e,Ye,Je,Ze,Xe,Qe,et,tt,nt,rt=n(0);(f||(f={})).filterSensitiveLog=function(e){return a({},e)},(l||(l={})).filterSensitiveLog=function(e){return a({},e)},function(e){e.GZIP="GZIP",e.HADOOP_SNAPPY="HADOOP_SNAPPY",e.SNAPPY="Snappy",e.UNCOMPRESSED="UNCOMPRESSED",e.ZIP="ZIP"}(d||(d={})),(h||(h={})).filterSensitiveLog=function(e){return a({},e)},function(e){e.GZIP="GZIP",e.NONE="NONE"}(p||(p={})),(v||(v={})).filterSensitiveLog=function(e){return a({},e)},function(e){e.AWS_OWNED_CMK="AWS_OWNED_CMK",e.CUSTOMER_MANAGED_CMK="CUSTOMER_MANAGED_CMK"}(g||(g={})),(m||(m={})).filterSensitiveLog=function(e){return a({},e)},(b||(b={})).filterSensitiveLog=function(e){return a({},e)},function(e){e.BUFFER_INTERVAL_IN_SECONDS="BufferIntervalInSeconds",e.BUFFER_SIZE_IN_MB="BufferSizeInMBs",e.LAMBDA_ARN="LambdaArn",e.LAMBDA_NUMBER_OF_RETRIES="NumberOfRetries",e.ROLE_ARN="RoleArn"}(y||(y={})),(w||(w={})).filterSensitiveLog=function(e){return a({},e)},(_||(_={})).filterSensitiveLog=function(e){return a({},e)},(S||(S={})).filterSensitiveLog=function(e){return a({},e)},(E||(E={})).filterSensitiveLog=function(e){return a({},e)},(M||(M={})).filterSensitiveLog=function(e){return a({},e)},(A||(A={})).filterSensitiveLog=function(e){return a({},e)},(I||(I={})).filterSensitiveLog=function(e){return a({},e)},(k||(k={})).filterSensitiveLog=function(e){return a({},e)},(O||(O={})).filterSensitiveLog=function(e){return a({},e)},(x||(x={})).filterSensitiveLog=function(e){return a({},e)},(C||(C={})).filterSensitiveLog=function(e){return a({},e)},(T||(T={})).filterSensitiveLog=function(e){return a({},e)},(P||(P={})).filterSensitiveLog=function(e){return a({},e)},function(e){e.NONE="NONE",e.SNAPPY="SNAPPY",e.ZLIB="ZLIB"}(N||(N={})),function(e){e.V0_11="V0_11",e.V0_12="V0_12"}(R||(R={})),(L||(L={})).filterSensitiveLog=function(e){return a({},e)},function(e){e.GZIP="GZIP",e.SNAPPY="SNAPPY",e.UNCOMPRESSED="UNCOMPRESSED"}(j||(j={})),function(e){e.V1="V1",e.V2="V2"}(D||(D={})),(U||(U={})).filterSensitiveLog=function(e){return a({},e)},(B||(B={})).filterSensitiveLog=function(e){return a({},e)},(F||(F={})).filterSensitiveLog=function(e){return a({},e)},(z||(z={})).filterSensitiveLog=function(e){return a({},e)},(q||(q={})).filterSensitiveLog=function(e){return a({},e)},(K||(K={})).filterSensitiveLog=function(e){return a({},e)},(H||(H={})).filterSensitiveLog=function(e){return a({},e)},(V||(V={})).filterSensitiveLog=function(e){return a(a(a({},e),e.Url&&{Url:rt.d}),e.AccessKey&&{AccessKey:rt.d})},(G||(G={})).filterSensitiveLog=function(e){return a(a(a({},e),e.AttributeName&&{AttributeName:rt.d}),e.AttributeValue&&{AttributeValue:rt.d})},(W||(W={})).filterSensitiveLog=function(e){return a(a({},e),e.CommonAttributes&&{CommonAttributes:e.CommonAttributes.map((function(e){return G.filterSensitiveLog(e)}))})},($||($={})).filterSensitiveLog=function(e){return a({},e)},(Y||(Y={})).filterSensitiveLog=function(e){return a(a(a({},e),e.RequestConfiguration&&{RequestConfiguration:W.filterSensitiveLog(e.RequestConfiguration)}),e.EndpointConfiguration&&{EndpointConfiguration:V.filterSensitiveLog(e.EndpointConfiguration)})},(J||(J={})).filterSensitiveLog=function(e){return a({},e)},(Z||(Z={})).filterSensitiveLog=function(e){return a({},e)},(X||(X={})).filterSensitiveLog=function(e){return a(a(a({},e),e.Password&&{Password:rt.d}),e.Username&&{Username:rt.d})},(Q||(Q={})).filterSensitiveLog=function(e){return a({},e)},(ee||(ee={})).filterSensitiveLog=function(e){return a({},e)},(te||(te={})).filterSensitiveLog=function(e){return a({},e)},(ne||(ne={})).filterSensitiveLog=function(e){return a(a(a({},e),e.HttpEndpointDestinationConfiguration&&{HttpEndpointDestinationConfiguration:Y.filterSensitiveLog(e.HttpEndpointDestinationConfiguration)}),e.RedshiftDestinationConfiguration&&{RedshiftDestinationConfiguration:X.filterSensitiveLog(e.RedshiftDestinationConfiguration)})},(re||(re={})).filterSensitiveLog=function(e){return a({},e)},(ie||(ie={})).filterSensitiveLog=function(e){return a({},e)},(oe||(oe={})).filterSensitiveLog=function(e){return a({},e)},(se||(se={})).filterSensitiveLog=function(e){return a({},e)},(ae||(ae={})).filterSensitiveLog=function(e){return a({},e)},(ue||(ue={})).filterSensitiveLog=function(e){return a({},e)},(ce||(ce={})).filterSensitiveLog=function(e){return a({},e)},(fe||(fe={})).filterSensitiveLog=function(e){return a({},e)},function(e){e.CREATE_ENI_FAILED="CREATE_ENI_FAILED",e.CREATE_KMS_GRANT_FAILED="CREATE_KMS_GRANT_FAILED",e.DELETE_ENI_FAILED="DELETE_ENI_FAILED",e.DISABLED_KMS_KEY="DISABLED_KMS_KEY",e.ENI_ACCESS_DENIED="ENI_ACCESS_DENIED",e.INVALID_KMS_KEY="INVALID_KMS_KEY",e.KMS_ACCESS_DENIED="KMS_ACCESS_DENIED",e.KMS_KEY_NOT_FOUND="KMS_KEY_NOT_FOUND",e.KMS_OPT_IN_REQUIRED="KMS_OPT_IN_REQUIRED",e.RETIRE_KMS_GRANT_FAILED="RETIRE_KMS_GRANT_FAILED",e.SECURITY_GROUP_ACCESS_DENIED="SECURITY_GROUP_ACCESS_DENIED",e.SECURITY_GROUP_NOT_FOUND="SECURITY_GROUP_NOT_FOUND",e.SUBNET_ACCESS_DENIED="SUBNET_ACCESS_DENIED",e.SUBNET_NOT_FOUND="SUBNET_NOT_FOUND",e.UNKNOWN_ERROR="UNKNOWN_ERROR"}(le||(le={})),(de||(de={})).filterSensitiveLog=function(e){return a({},e)},function(e){e.DISABLED="DISABLED",e.DISABLING="DISABLING",e.DISABLING_FAILED="DISABLING_FAILED",e.ENABLED="ENABLED",e.ENABLING="ENABLING",e.ENABLING_FAILED="ENABLING_FAILED"}(he||(he={})),(pe||(pe={})).filterSensitiveLog=function(e){return a({},e)},function(e){e.ACTIVE="ACTIVE",e.CREATING="CREATING",e.CREATING_FAILED="CREATING_FAILED",e.DELETING="DELETING",e.DELETING_FAILED="DELETING_FAILED"}(ve||(ve={})),(ge||(ge={})).filterSensitiveLog=function(e){return a({},e)},(me||(me={})).filterSensitiveLog=function(e){return a({},e)},(be||(be={})).filterSensitiveLog=function(e){return a({},e)},(ye||(ye={})).filterSensitiveLog=function(e){return a({},e)},(we||(we={})).filterSensitiveLog=function(e){return a(a({},e),e.Url&&{Url:rt.d})},(_e||(_e={})).filterSensitiveLog=function(e){return a(a(a({},e),e.EndpointConfiguration&&{EndpointConfiguration:we.filterSensitiveLog(e.EndpointConfiguration)}),e.RequestConfiguration&&{RequestConfiguration:W.filterSensitiveLog(e.RequestConfiguration)})},(Se||(Se={})).filterSensitiveLog=function(e){return a(a({},e),e.Username&&{Username:rt.d})},(Ee||(Ee={})).filterSensitiveLog=function(e){return a({},e)},(Me||(Me={})).filterSensitiveLog=function(e){return a(a(a({},e),e.HttpEndpointDestinationDescription&&{HttpEndpointDestinationDescription:_e.filterSensitiveLog(e.HttpEndpointDestinationDescription)}),e.RedshiftDestinationDescription&&{RedshiftDestinationDescription:Se.filterSensitiveLog(e.RedshiftDestinationDescription)})},(Ae||(Ae={})).filterSensitiveLog=function(e){return a({},e)},(Ie||(Ie={})).filterSensitiveLog=function(e){return a({},e)},(ke||(ke={})).filterSensitiveLog=function(e){return a(a({},e),e.Destinations&&{Destinations:e.Destinations.map((function(e){return Me.filterSensitiveLog(e)}))})},(Oe||(Oe={})).filterSensitiveLog=function(e){return a({},e)},(xe||(xe={})).filterSensitiveLog=function(e){return a(a({},e),e.DeliveryStreamDescription&&{DeliveryStreamDescription:ke.filterSensitiveLog(e.DeliveryStreamDescription)})},(Ce||(Ce={})).filterSensitiveLog=function(e){return a({},e)},(Te||(Te={})).filterSensitiveLog=function(e){return a({},e)},(Pe||(Pe={})).filterSensitiveLog=function(e){return a({},e)},(Ne||(Ne={})).filterSensitiveLog=function(e){return a({},e)},(Re||(Re={})).filterSensitiveLog=function(e){return a({},e)},(Le||(Le={})).filterSensitiveLog=function(e){return a({},e)},(je||(je={})).filterSensitiveLog=function(e){return a({},e)},(De||(De={})).filterSensitiveLog=function(e){return a({},e)},(Ue||(Ue={})).filterSensitiveLog=function(e){return a({},e)},(Be||(Be={})).filterSensitiveLog=function(e){return a({},e)},(Fe||(Fe={})).filterSensitiveLog=function(e){return a({},e)},(ze||(ze={})).filterSensitiveLog=function(e){return a({},e)},(qe||(qe={})).filterSensitiveLog=function(e){return a({},e)},(Ke||(Ke={})).filterSensitiveLog=function(e){return a({},e)},(He||(He={})).filterSensitiveLog=function(e){return a({},e)},(Ve||(Ve={})).filterSensitiveLog=function(e){return a({},e)},(Ge||(Ge={})).filterSensitiveLog=function(e){return a({},e)},(We||(We={})).filterSensitiveLog=function(e){return a({},e)},($e||($e={})).filterSensitiveLog=function(e){return a({},e)},(Ye||(Ye={})).filterSensitiveLog=function(e){return a({},e)},(Je||(Je={})).filterSensitiveLog=function(e){return a({},e)},(Ze||(Ze={})).filterSensitiveLog=function(e){return a({},e)},(Xe||(Xe={})).filterSensitiveLog=function(e){return a(a(a({},e),e.EndpointConfiguration&&{EndpointConfiguration:V.filterSensitiveLog(e.EndpointConfiguration)}),e.RequestConfiguration&&{RequestConfiguration:W.filterSensitiveLog(e.RequestConfiguration)})},(Qe||(Qe={})).filterSensitiveLog=function(e){return a(a(a({},e),e.Username&&{Username:rt.d}),e.Password&&{Password:rt.d})},(et||(et={})).filterSensitiveLog=function(e){return a({},e)},(tt||(tt={})).filterSensitiveLog=function(e){return a(a(a({},e),e.HttpEndpointDestinationUpdate&&{HttpEndpointDestinationUpdate:Xe.filterSensitiveLog(e.HttpEndpointDestinationUpdate)}),e.RedshiftDestinationUpdate&&{RedshiftDestinationUpdate:Qe.filterSensitiveLog(e.RedshiftDestinationUpdate)})},(nt||(nt={})).filterSensitiveLog=function(e){return a({},e)};var it,ot=n(2),st=function(e,t){return u(void 0,void 0,void 0,(function(){var n,r,i,o,s,u,f,l,d,h,p,v;return c(this,(function(c){switch(c.label){case 0:return r=[a({},e)],v={},[4,Et(e.body,t)];case 1:switch(n=a.apply(void 0,r.concat([(v.body=c.sent(),v)])),o="UnknownError",s=n.body.__type.split("#"),o=void 0===s[1]?s[0]:s[1],o){case"InvalidArgumentException":case"com.amazonaws.firehose#InvalidArgumentException":return[3,2];case"InvalidKMSResourceException":case"com.amazonaws.firehose#InvalidKMSResourceException":return[3,4];case"ResourceNotFoundException":case"com.amazonaws.firehose#ResourceNotFoundException":return[3,6];case"ServiceUnavailableException":case"com.amazonaws.firehose#ServiceUnavailableException":return[3,8]}return[3,10];case 2:return u=[{}],[4,at(n,t)];case 3:return i=a.apply(void 0,[a.apply(void 0,u.concat([c.sent()])),{name:o,$metadata:wt(e)}]),[3,11];case 4:return f=[{}],[4,ut(n,t)];case 5:return i=a.apply(void 0,[a.apply(void 0,f.concat([c.sent()])),{name:o,$metadata:wt(e)}]),[3,11];case 6:return l=[{}],[4,ct(n,t)];case 7:return i=a.apply(void 0,[a.apply(void 0,l.concat([c.sent()])),{name:o,$metadata:wt(e)}]),[3,11];case 8:return d=[{}],[4,ft(n,t)];case 9:return i=a.apply(void 0,[a.apply(void 0,d.concat([c.sent()])),{name:o,$metadata:wt(e)}]),[3,11];case 10:h=n.body,o=h.code||h.Code||o,i=a(a({},h),{name:""+o,message:h.message||h.Message||o,$fault:"client",$metadata:wt(e)}),c.label=11;case 11:return p=i.message||i.Message||o,i.message=p,delete i.Message,[2,Promise.reject(Object.assign(new Error(p),i))]}}))}))},at=function(e,t){return u(void 0,void 0,void 0,(function(){var n,r;return c(this,(function(i){return n=e.body,r=pt(n,t),[2,a({name:"InvalidArgumentException",$fault:"client",$metadata:wt(e)},r)]}))}))},ut=function(e,t){return u(void 0,void 0,void 0,(function(){var n,r;return c(this,(function(i){return n=e.body,r=vt(n,t),[2,a({name:"InvalidKMSResourceException",$fault:"client",$metadata:wt(e)},r)]}))}))},ct=function(e,t){return u(void 0,void 0,void 0,(function(){var n,r;return c(this,(function(i){return n=e.body,r=bt(n,t),[2,a({name:"ResourceNotFoundException",$fault:"client",$metadata:wt(e)},r)]}))}))},ft=function(e,t){return u(void 0,void 0,void 0,(function(){var n,r;return c(this,(function(i){return n=e.body,r=yt(n,t),[2,a({name:"ServiceUnavailableException",$fault:"server",$metadata:wt(e)},r)]}))}))},lt=function(e,t){return a(a({},void 0!==e.DeliveryStreamName&&{DeliveryStreamName:e.DeliveryStreamName}),void 0!==e.Records&&{Records:dt(e.Records,t)})},dt=function(e,t){return e.map((function(e){return ht(e,t)}))},ht=function(e,t){return a({},void 0!==e.Data&&{Data:t.base64Encoder(e.Data)})},pt=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},vt=function(e,t){return{code:void 0!==e.code&&null!==e.code?e.code:void 0,message:void 0!==e.message&&null!==e.message?e.message:void 0}},gt=function(e,t){return{Encrypted:void 0!==e.Encrypted&&null!==e.Encrypted?e.Encrypted:void 0,FailedPutCount:void 0!==e.FailedPutCount&&null!==e.FailedPutCount?e.FailedPutCount:void 0,RequestResponses:void 0!==e.RequestResponses&&null!==e.RequestResponses?mt(e.RequestResponses,t):void 0}},mt=function(e,t){return(e||[]).map((function(e){return function(e,t){return{ErrorCode:void 0!==e.ErrorCode&&null!==e.ErrorCode?e.ErrorCode:void 0,ErrorMessage:void 0!==e.ErrorMessage&&null!==e.ErrorMessage?e.ErrorMessage:void 0,RecordId:void 0!==e.RecordId&&null!==e.RecordId?e.RecordId:void 0}}(e)}))},bt=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},yt=function(e,t){return{message:void 0!==e.message&&null!==e.message?e.message:void 0}},wt=function(e){return{httpStatusCode:e.statusCode,httpHeaders:e.headers,requestId:e.headers["x-amzn-requestid"]}},_t=function(e,t){return function(e,t){return void 0===e&&(e=new Uint8Array),e instanceof Uint8Array?Promise.resolve(e):t.streamCollector(e)||Promise.resolve(new Uint8Array)}(e,t).then((function(e){return t.utf8Encoder(e)}))},St=function(e,t,n,r,i){return u(void 0,void 0,void 0,(function(){var o,s,a,u,f,l;return c(this,(function(c){switch(c.label){case 0:return[4,e.endpoint()];case 1:return o=c.sent(),s=o.hostname,a=o.protocol,u=void 0===a?"https":a,f=o.port,l={protocol:u,hostname:s,port:f,method:"POST",path:n,headers:t},void 0!==r&&(l.hostname=r),void 0!==i&&(l.body=i),[2,new ot.a(l)]}}))}))},Et=function(e,t){return _t(e,t).then((function(e){return e.length?JSON.parse(e):{}}))},Mt=n(10),At=function(e){function t(t){var n=e.call(this)||this;return n.input=t,n}return s(t,e),t.prototype.resolveMiddleware=function(e,t,n){this.middlewareStack.use(Object(Mt.a)(t,this.serialize,this.deserialize));var r=e.concat(this.middlewareStack),i=t.logger,o={logger:i,clientName:"FirehoseClient",commandName:"PutRecordBatchCommand",inputFilterSensitiveLog:ze.filterSensitiveLog,outputFilterSensitiveLog:Ke.filterSensitiveLog};"function"==typeof i.info&&i.info({clientName:"FirehoseClient",commandName:"PutRecordBatchCommand"});var s=t.requestHandler;return r.resolve((function(e){return s.handle(e.request,n||{})}),o)},t.prototype.serialize=function(e,t){return function(e,t){return u(void 0,void 0,void 0,(function(){var n,r;return c(this,(function(i){return n={"Content-Type":"application/x-amz-json-1.1","X-Amz-Target":"Firehose_20150804.PutRecordBatch"},r=JSON.stringify(lt(e,t)),[2,St(t,n,"/",void 0,r)]}))}))}(e,t)},t.prototype.deserialize=function(e,t){return function(e,t){return u(void 0,void 0,void 0,(function(){var n,r,i;return c(this,(function(o){switch(o.label){case 0:return e.statusCode>=300?[2,st(e,t)]:[4,Et(e.body,t)];case 1:return n=o.sent(),{},r=gt(n,t),i=a({$metadata:wt(e)},r),[2,Promise.resolve(i)]}}))}))}(e,t)},t}(rt.b),It=n(151),kt=n(38),Ot=n(18),xt=n(24),Ct=n(11),Tt=n(39),Pt=n(17),Nt=n(40),Rt=n(41),Lt=n(15),jt=new Set(["ap-east-1","ap-northeast-1","ap-northeast-2","ap-south-1","ap-southeast-1","ap-southeast-2","ca-central-1","eu-central-1","eu-north-1","eu-west-1","eu-west-2","eu-west-3","me-south-1","sa-east-1","us-east-1","us-east-2","us-west-1","us-west-2"]),Dt=new Set(["cn-north-1","cn-northwest-1"]),Ut=new Set(["us-iso-east-1"]),Bt=new Set(["us-isob-east-1"]),Ft=new Set(["us-gov-east-1","us-gov-west-1"]),zt=a(a({},{apiVersion:"2015-08-04",disableHostPrefix:!1,logger:{},regionInfoProvider:function(e,t){var n=void 0;switch(e){case"ap-east-1":n={hostname:"firehose.ap-east-1.amazonaws.com",partition:"aws"};break;case"ap-northeast-1":n={hostname:"firehose.ap-northeast-1.amazonaws.com",partition:"aws"};break;case"ap-northeast-2":n={hostname:"firehose.ap-northeast-2.amazonaws.com",partition:"aws"};break;case"ap-south-1":n={hostname:"firehose.ap-south-1.amazonaws.com",partition:"aws"};break;case"ap-southeast-1":n={hostname:"firehose.ap-southeast-1.amazonaws.com",partition:"aws"};break;case"ap-southeast-2":n={hostname:"firehose.ap-southeast-2.amazonaws.com",partition:"aws"};break;case"ca-central-1":n={hostname:"firehose.ca-central-1.amazonaws.com",partition:"aws"};break;case"cn-north-1":n={hostname:"firehose.cn-north-1.amazonaws.com.cn",partition:"aws-cn"};break;case"cn-northwest-1":n={hostname:"firehose.cn-northwest-1.amazonaws.com.cn",partition:"aws-cn"};break;case"eu-central-1":n={hostname:"firehose.eu-central-1.amazonaws.com",partition:"aws"};break;case"eu-north-1":n={hostname:"firehose.eu-north-1.amazonaws.com",partition:"aws"};break;case"eu-west-1":n={hostname:"firehose.eu-west-1.amazonaws.com",partition:"aws"};break;case"eu-west-2":n={hostname:"firehose.eu-west-2.amazonaws.com",partition:"aws"};break;case"eu-west-3":n={hostname:"firehose.eu-west-3.amazonaws.com",partition:"aws"};break;case"me-south-1":n={hostname:"firehose.me-south-1.amazonaws.com",partition:"aws"};break;case"sa-east-1":n={hostname:"firehose.sa-east-1.amazonaws.com",partition:"aws"};break;case"us-east-1":n={hostname:"firehose.us-east-1.amazonaws.com",partition:"aws"};break;case"us-east-2":n={hostname:"firehose.us-east-2.amazonaws.com",partition:"aws"};break;case"us-gov-east-1":n={hostname:"firehose.us-gov-east-1.amazonaws.com",partition:"aws-us-gov"};break;case"us-gov-west-1":n={hostname:"firehose.us-gov-west-1.amazonaws.com",partition:"aws-us-gov"};break;case"us-west-1":n={hostname:"firehose.us-west-1.amazonaws.com",partition:"aws"};break;case"us-west-2":n={hostname:"firehose.us-west-2.amazonaws.com",partition:"aws"};break;default:jt.has(e)&&(n={hostname:"firehose.{region}.amazonaws.com".replace("{region}",e),partition:"aws"}),Dt.has(e)&&(n={hostname:"firehose.{region}.amazonaws.com.cn".replace("{region}",e),partition:"aws-cn"}),Ut.has(e)&&(n={hostname:"firehose.{region}.c2s.ic.gov".replace("{region}",e),partition:"aws-iso"}),Bt.has(e)&&(n={hostname:"firehose.{region}.sc2s.sgov.gov".replace("{region}",e),partition:"aws-iso-b"}),Ft.has(e)&&(n={hostname:"firehose.{region}.amazonaws.com".replace("{region}",e),partition:"aws-us-gov"}),void 0===n&&(n={hostname:"firehose.{region}.amazonaws.com".replace("{region}",e),partition:"aws"})}return Promise.resolve(n)},signingName:"firehose"}),{runtime:"browser",base64Decoder:Pt.a,base64Encoder:Pt.b,bodyLengthChecker:Nt.a,credentialDefaultProvider:Object(xt.a)("Credential is missing"),defaultUserAgent:Object(Rt.a)(It.name,It.version),maxAttempts:Ct.a,region:Object(xt.a)("Region is missing"),requestHandler:new Ot.a,sha256:kt.Sha256,streamCollector:Ot.b,urlParser:Tt.a,utf8Decoder:Lt.a,utf8Encoder:Lt.b}),qt=n(22),Kt=n(37),Ht=n(21),Vt=n(43),Gt=n(25),Wt=n(23),$t=function(e){function t(t){var n=this,r=a(a({},zt),t),i=Object(qt.b)(r),o=Object(qt.a)(i),s=Object(Gt.b)(o),u=Object(Ct.c)(s),c=Object(Wt.b)(u),f=Object(Ht.b)(c);return(n=e.call(this,f)||this).config=f,n.middlewareStack.use(Object(Gt.a)(n.config)),n.middlewareStack.use(Object(Ct.b)(n.config)),n.middlewareStack.use(Object(Wt.a)(n.config)),n.middlewareStack.use(Object(Kt.a)(n.config)),n.middlewareStack.use(Object(Ht.a)(n.config)),n.middlewareStack.use(Object(Vt.a)(n.config)),n}return s(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this)},t}(rt.a),Yt=(it=function(e,t){return(it=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)},function(e,t){function n(){this.constructor=e}it(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),Jt=new r.a("AWSKineisFirehoseProvider"),Zt=function(e){function t(t){return e.call(this,t)||this}return Yt(t,e),t.prototype.getProviderName=function(){return"AWSKinesisFirehose"},t.prototype._sendEvents=function(e){var t=this;if(0!==e.length){var n=e[0],r=n.config,i=n.credentials;if(!this._init(r,i))return!1;var o={};e.map((function(e){var t=e.event,n=t.streamName,r=t.data;void 0===o[n]&&(o[n]=[]);var i=r&&"string"!=typeof r?JSON.stringify(r):r,s={Data:Object(Lt.a)(i)};o[n].push(s)})),Object.keys(o).map((function(e){Jt.debug("putting records to kinesis",e,"with records",o[e]),t._kinesisFirehose.send(new At({Records:o[e],DeliveryStreamName:e})).then((function(t){return Jt.debug("Upload records to stream",e)})).catch((function(e){return Jt.debug("Failed to upload records to Kinesis",e)}))}))}},t.prototype._init=function(e,t){if(Jt.debug("init clients"),this._kinesisFirehose&&this._config.credentials&&this._config.credentials.sessionToken===t.sessionToken&&this._config.credentials.identityId===t.identityId)return Jt.debug("no change for analytics config, directly return from init"),!0;this._config.credentials=t;var n=e.region;return this._initFirehose(n,t)},t.prototype._initFirehose=function(e,t){return Jt.debug("initialize kinesis firehose with credentials",t),this._kinesisFirehose=new $t({apiVersion:"2015-08-04",region:e,credentials:t}),!0},t}(i.a)},function(e,t,n){"use strict";n.d(t,"a",(function(){return T}));var r,i=n(44),o=n(19),s=n(5),a=n(89),u=n(104),c=function(){return(c=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},f=new i.a("AbstractXRProvider"),l=function(){function e(e){void 0===e&&(e={}),this._config=e}return e.prototype.configure=function(e){return void 0===e&&(e={}),this._config=c(c({},e),this._config),f.debug("configure "+this.getProviderName(),this._config),this.options},e.prototype.getCategory=function(){return"XR"},Object.defineProperty(e.prototype,"options",{get:function(){return c({},this._config)},enumerable:!0,configurable:!0}),e}(),d=(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)},function(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),h=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return d(t,e),t}(Error),p=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return d(t,e),t}(h),v=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return d(t,e),t}(h),g=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return d(t,e),t}(h),m=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return d(t,e),t}(h),b=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return d(t,e),t}(h),y=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return d(t,e),t}(h),w=function(){var e=function(t,n){return(e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(t,n)};return function(t,n){function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}}(),_=function(){return(_=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},S=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},E=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},M=function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},A=new i.a("SumerianProvider"),I=function(e){function t(t){return void 0===t&&(t={}),e.call(this,t)||this}return w(t,e),t.prototype.getProviderName=function(){return"SumerianProvider"},t.prototype.loadScript=function(e){return S(this,void 0,void 0,(function(){return E(this,(function(t){return[2,new Promise((function(t,n){var r=document.createElement("script");r.src=e,r.addEventListener("load",(function(e){t()})),r.addEventListener("error",(function(t){n(new Error("Failed to load script: "+e))})),document.head.appendChild(r)}))]}))}))},t.prototype.loadScene=function(e,t,n){return S(this,void 0,void 0,(function(){var r,i,o,c,f,l,d,h,p,v,g,y,w,S,I,k,O,x,C,T,P,N,R,L,j,D,U;return E(this,(function(E){switch(E.label){case 0:if(!e)throw l="No scene name passed into loadScene",A.error(l),new b(l);if(!t)throw l="No dom element id passed into loadScene",A.error(l),new m(l);if(!(r=document.getElementById(t)))throw l="DOM element id, "+t+" not found",A.error(l),new m(l);if(!(i=this.getScene(e)).sceneConfig)throw l="No scene config configured for scene: "+e,A.error(l),new b(l);if(o=i.sceneConfig.url,c=i.sceneConfig.sceneId,i.sceneConfig.hasOwnProperty("region"))f=i.sceneConfig.region;else{if(!this.options.hasOwnProperty("region"))throw l="No region configured for scene: "+e,A.error(l),new b(l);f=this.options.region}d={region:f,customUserAgent:s.a.userAgent+"-SumerianScene"},h={headers:{"X-Amz-User-Agent":s.a.userAgent}},p=o,E.label=1;case 1:return E.trys.push([1,3,,4]),[4,a.a.get()];case 2:return v=E.sent(),d.credentials=v,g={secret_key:v.secretAccessKey,access_key:v.accessKeyId,session_token:v.sessionToken},y={region:f,service:"sumerian"},w=u.a.sign({method:"GET",url:o},g,y),h.headers=_(_({},h.headers),w.headers),p=w.url,[3,4];case 3:return E.sent(),A.debug("No credentials available, the request will be unsigned"),[3,4];case 4:return[4,fetch(p,h)];case 5:return[4,(S=E.sent()).json()];case 6:if(I=E.sent(),403===S.status)throw I.message?(A.error("Failure to authenticate user: "+I.message),new b("Failure to authenticate user: "+I.message)):(A.error("Failure to authenticate user"),new b("Failure to authenticate user"));return k=I.bundleData[c],[4,fetch(k.url,{headers:k.headers})];case 7:return[4,E.sent().json()];case 8:O=E.sent(),E.label=9;case 9:return E.trys.push([9,11,,12]),[4,this.loadScript(O[c].bootstrapperUrl)];case 10:return E.sent(),[3,12];case 11:throw x=E.sent(),A.error(x),new b(x);case 12:return C=n.progressCallback?n.progressCallback:void 0,T=i.publishParamOverrides?i.publishParamOverrides:void 0,P={element:r,sceneId:c,sceneBundle:O,apiResponse:I,progressCallback:C,publishParamOverrides:T,awsSDKConfigOverride:d},[4,window.SumerianBootstrapper.loadScene(P)];case 13:N=E.sent(),i.sceneController=N,i.isLoaded=!0;try{for(R=M(N.sceneLoadWarnings),L=R.next();!L.done;L=R.next())j=L.value,A.warn("loadScene warning: "+j)}catch(e){D={error:e}}finally{try{L&&!L.done&&(U=R.return)&&U.call(R)}finally{if(D)throw D.error}}return[2]}}))}))},t.prototype.isSceneLoaded=function(e){return this.getScene(e).isLoaded||!1},t.prototype.getScene=function(e){if(!this.options.scenes){var t="No scenes were defined in the configuration";throw A.error(t),new p(t)}if(!e){t="No scene name was passed";throw A.error(t),new v(t)}if(!this.options.scenes[e]){t="Scene '"+e+"' is not configured";throw A.error(t),new v(t)}return this.options.scenes[e]},t.prototype.getSceneController=function(e){if(!this.options.scenes){var t="No scenes were defined in the configuration";throw A.error(t),new p(t)}var n=this.options.scenes[e];if(!n){t="Scene '"+e+"' is not configured";throw A.error(t),new v(t)}var r=n.sceneController;if(!r){t="Scene controller for '"+e+"' has not been loaded";throw A.error(t),new g(t)}return r},t.prototype.isVRCapable=function(e){return this.getSceneController(e).vrCapable},t.prototype.isVRPresentationActive=function(e){return this.getSceneController(e).vrPresentationActive},t.prototype.start=function(e){this.getSceneController(e).start()},t.prototype.enterVR=function(e){this.getSceneController(e).enterVR()},t.prototype.exitVR=function(e){this.getSceneController(e).exitVR()},t.prototype.isMuted=function(e){return this.getSceneController(e).muted},t.prototype.setMuted=function(e,t){this.getSceneController(e).muted=t},t.prototype.onSceneEvent=function(e,t,n){this.getSceneController(e).on(t,n)},t.prototype.enableAudio=function(e){this.getSceneController(e).enableAudio()},t}(l),k=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},O=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},x=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},C=new i.a("XR"),T=new(function(){function e(e){this._options=e,C.debug("XR Options",this._options),this._defaultProvider="SumerianProvider",this._pluggables={},this.addPluggable(new I)}return e.prototype.configure=function(e){var t=this,n=e?e.XR||e:{};return C.debug("configure XR",{opt:n}),this._options=Object.assign({},this._options,n),Object.entries(this._pluggables).map((function(e){var r=x(e,2),i=r[0],o=r[1];i!==t._defaultProvider||n[t._defaultProvider]?o.configure(t._options[i]):o.configure(t._options)})),this._options},e.prototype.addPluggable=function(e){return k(this,void 0,void 0,(function(){return O(this,(function(t){return e&&"XR"===e.getCategory()?(this._pluggables[e.getProviderName()]=e,[2,e.configure(this._options)]):[2]}))}))},e.prototype.loadScene=function(e,t,n,r){return void 0===n&&(n={}),void 0===r&&(r=this._defaultProvider),k(this,void 0,void 0,(function(){return O(this,(function(i){switch(i.label){case 0:if(!this._pluggables[r])throw new y("Provider '"+r+"' not configured");return[4,this._pluggables[r].loadScene(e,t,n)];case 1:return[2,i.sent()]}}))}))},e.prototype.isSceneLoaded=function(e,t){if(void 0===t&&(t=this._defaultProvider),!this._pluggables[t])throw new y("Provider '"+t+"' not configured");return this._pluggables[t].isSceneLoaded(e)},e.prototype.getSceneController=function(e,t){if(void 0===t&&(t=this._defaultProvider),!this._pluggables[t])throw new y("Provider '"+t+"' not configured");return this._pluggables[t].getSceneController(e)},e.prototype.isVRCapable=function(e,t){if(void 0===t&&(t=this._defaultProvider),!this._pluggables[t])throw new y("Provider '"+t+"' not configured");return this._pluggables[t].isVRCapable(e)},e.prototype.isVRPresentationActive=function(e,t){if(void 0===t&&(t=this._defaultProvider),!this._pluggables[t])throw new y("Provider '"+t+"' not configured");return this._pluggables[t].isVRPresentationActive(e)},e.prototype.start=function(e,t){if(void 0===t&&(t=this._defaultProvider),!this._pluggables[t])throw new y("Provider '"+t+"' not configured");return this._pluggables[t].start(e)},e.prototype.enterVR=function(e,t){if(void 0===t&&(t=this._defaultProvider),!this._pluggables[t])throw new y("Provider '"+t+"' not configured");return this._pluggables[t].enterVR(e)},e.prototype.exitVR=function(e,t){if(void 0===t&&(t=this._defaultProvider),!this._pluggables[t])throw new y("Provider '"+t+"' not configured");return this._pluggables[t].exitVR(e)},e.prototype.isMuted=function(e,t){if(void 0===t&&(t=this._defaultProvider),!this._pluggables[t])throw new y("Provider '"+t+"' not configured");return this._pluggables[t].isMuted(e)},e.prototype.setMuted=function(e,t,n){if(void 0===n&&(n=this._defaultProvider),!this._pluggables[n])throw new y("Provider '"+n+"' not configured");return this._pluggables[n].setMuted(e,t)},e.prototype.onSceneEvent=function(e,t,n,r){if(void 0===r&&(r=this._defaultProvider),!this._pluggables[r])throw new y("Provider '"+r+"' not configured");return this._pluggables[r].onSceneEvent(e,t,n)},e.prototype.enableAudio=function(e,t){if(void 0===t&&(t=this._defaultProvider),!this._pluggables[t])throw new y("Provider '"+t+"' not configured");return this._pluggables[t].enableAudio(e)},e}())(null);o.a.register(T)},function(e,t,n){"use strict";n.r(t),n.d(t,"fromUtf8",(function(){return r})),n.d(t,"toUtf8",(function(){return i}));var r=function(e){return"function"==typeof TextEncoder?function(e){return(new TextEncoder).encode(e)}(e):function(e){for(var t=[],n=0,r=e.length;n<r;n++){var i=e.charCodeAt(n);if(i<128)t.push(i);else if(i<2048)t.push(i>>6|192,63&i|128);else if(n+1<e.length&&55296==(64512&i)&&56320==(64512&e.charCodeAt(n+1))){var o=65536+((1023&i)<<10)+(1023&e.charCodeAt(++n));t.push(o>>18|240,o>>12&63|128,o>>6&63|128,63&o|128)}else t.push(i>>12|224,i>>6&63|128,63&i|128)}return Uint8Array.from(t)}(e)},i=function(e){return"function"==typeof TextDecoder?function(e){return new TextDecoder("utf-8").decode(e)}(e):function(e){for(var t="",n=0,r=e.length;n<r;n++){var i=e[n];if(i<128)t+=String.fromCharCode(i);else if(192<=i&&i<224){var o=e[++n];t+=String.fromCharCode((31&i)<<6|63&o)}else if(240<=i&&i<365){var s="%"+[i,e[++n],e[++n],e[++n]].map((function(e){return e.toString(16)})).join("%");t+=decodeURIComponent(s)}else t+=String.fromCharCode((15&i)<<12|(63&e[++n])<<6|63&e[++n])}return t}(e)}},function(e,t,n){"use strict";n.r(t);var r=n(44);let i,o;const s=new WeakMap,a=new WeakMap,u=new WeakMap,c=new WeakMap,f=new WeakMap;let l={get(e,t,n){if(e instanceof IDBTransaction){if("done"===t)return a.get(e);if("objectStoreNames"===t)return e.objectStoreNames||u.get(e);if("store"===t)return n.objectStoreNames[1]?void 0:n.objectStore(n.objectStoreNames[0])}return p(e[t])},set:(e,t,n)=>(e[t]=n,!0),has:(e,t)=>e instanceof IDBTransaction&&("done"===t||"store"===t)||t in e};function d(e){return e!==IDBDatabase.prototype.transaction||"objectStoreNames"in IDBTransaction.prototype?(o||(o=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])).includes(e)?function(...t){return e.apply(v(this),t),p(s.get(this))}:function(...t){return p(e.apply(v(this),t))}:function(t,...n){const r=e.call(v(this),t,...n);return u.set(r,t.sort?t.sort():[t]),p(r)}}function h(e){return"function"==typeof e?d(e):(e instanceof IDBTransaction&&function(e){if(a.has(e))return;const t=new Promise((t,n)=>{const r=()=>{e.removeEventListener("complete",i),e.removeEventListener("error",o),e.removeEventListener("abort",o)},i=()=>{t(),r()},o=()=>{n(e.error||new DOMException("AbortError","AbortError")),r()};e.addEventListener("complete",i),e.addEventListener("error",o),e.addEventListener("abort",o)});a.set(e,t)}(e),t=e,(i||(i=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])).some(e=>t instanceof e)?new Proxy(e,l):e);var t}function p(e){if(e instanceof IDBRequest)return function(e){const t=new Promise((t,n)=>{const r=()=>{e.removeEventListener("success",i),e.removeEventListener("error",o)},i=()=>{t(p(e.result)),r()},o=()=>{n(e.error),r()};e.addEventListener("success",i),e.addEventListener("error",o)});return t.then(t=>{t instanceof IDBCursor&&s.set(t,e)}).catch(()=>{}),f.set(t,e),t}(e);if(c.has(e))return c.get(e);const t=h(e);return t!==e&&(c.set(e,t),f.set(t,e)),t}const v=e=>f.get(e);function g(e,t,{blocked:n,upgrade:r,blocking:i,terminated:o}={}){const s=indexedDB.open(e,t),a=p(s);return r&&s.addEventListener("upgradeneeded",e=>{r(p(s.result),e.oldVersion,e.newVersion,p(s.transaction))}),n&&s.addEventListener("blocked",()=>n()),a.then(e=>{o&&e.addEventListener("close",()=>o()),i&&e.addEventListener("versionchange",()=>i())}).catch(()=>{}),a}function m(e,{blocked:t}={}){const n=indexedDB.deleteDatabase(e);return t&&n.addEventListener("blocked",()=>t()),p(n).then(()=>{})}const b=["get","getKey","getAll","getAllKeys","count"],y=["put","add","delete","clear"],w=new Map;function _(e,t){if(!(e instanceof IDBDatabase)||t in e||"string"!=typeof t)return;if(w.get(t))return w.get(t);const n=t.replace(/FromIndex$/,""),r=t!==n,i=y.includes(n);if(!(n in(r?IDBIndex:IDBObjectStore).prototype)||!i&&!b.includes(n))return;const o=async function(e,...t){const o=this.transaction(e,i?"readwrite":"readonly");let s=o.store;r&&(s=s.index(t.shift()));const a=await s[n](...t);return i&&await o.done,a};return w.set(t,o),o}l=(e=>({...e,get:(t,n,r)=>_(t,n)||e.get(t,n,r),has:(t,n)=>!!_(t,n)||e.has(t,n)}))(l);var S=n(9),E=n(245),M=n(4),A=n(3);function I(e){return(I="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var k=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},O=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},x=function(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,n=e[Symbol.asyncIterator];return n?n.call(e):(e="function"==typeof C?C(e):e[Symbol.iterator](),t={},r("next"),r("throw"),r("return"),t[Symbol.asyncIterator]=function(){return this},t);function r(n){t[n]=e[n]&&function(t){return new Promise((function(r,i){(function(e,t,n,r){Promise.resolve(r).then((function(t){e({value:t,done:n})}),t)})(r,i,(t=e[n](t)).done,t.value)}))}}},C=function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},T=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},P=function(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(T(arguments[t]));return e},N=new r.a("DataStore"),R=function(){function e(){this.dbName="amplify-datastore"}return e.prototype.checkPrivate=function(){return k(this,void 0,void 0,(function(){return O(this,(function(e){switch(e.label){case 0:return[4,Object(A.u)().then((function(e){return e}))];case 1:return e.sent()?(N.error("IndexedDB not supported in this browser's private mode"),[2,Promise.reject("IndexedDB not supported in this browser's private mode")]):[2,Promise.resolve()]}}))}))},e.prototype.getStorenameForModel=function(e){var t=this.namespaceResolver(e),n=e.name;return this.getStorename(t,n)},e.prototype.getStorename=function(e,t){return e+"_"+t},e.prototype.setUp=function(e,t,n,r,i){return k(this,void 0,void 0,(function(){var o,s,a=this;return O(this,(function(u){switch(u.label){case 0:return[4,this.checkPrivate()];case 1:return u.sent(),this.initPromise?[3,2]:(this.initPromise=new Promise((function(e,t){a.resolve=e,a.reject=t})),[3,4]);case 2:return[4,this.initPromise];case 3:u.sent(),u.label=4;case 4:i&&(this.dbName="amplify-datastore-"+i),this.schema=e,this.namespaceResolver=t,this.modelInstanceCreator=n,this.getModelConstructorByModelName=r,u.label=5;case 5:return u.trys.push([5,8,,9]),this.db?[3,7]:(2,o=this,[4,g(this.dbName,2,{upgrade:function(t,n,r,i){return k(a,void 0,void 0,(function(){var o,s,a,u,c,f,l,d,h,p,v,g,m=this;return O(this,(function(b){switch(b.label){case 0:if(0===n)return Object.keys(e.namespaces).forEach((function(n){var r=e.namespaces[n];Object.keys(r.models).forEach((function(e){var r=m.getStorename(n,e),i=t.createObjectStore(r,{autoIncrement:!0});m.schema.namespaces[n].relationships[e].indexes.forEach((function(e){return i.createIndex(e,e)})),i.createIndex("byId","id",{unique:!0})}))})),[2];if(1!==n||2!==r)return[3,16];b.label=1;case 1:b.trys.push([1,14,,15]),b.label=2;case 2:b.trys.push([2,11,12,13]),o=C(i.objectStoreNames),s=o.next(),b.label=3;case 3:return s.done?[3,10]:(a=s.value,u=i.objectStore(a),c="tmp_"+a,u.name=c,(f=t.createObjectStore(a,{keyPath:void 0,autoIncrement:!0})).createIndex("byId","id",{unique:!0}),[4,u.openCursor()]);case 4:l=b.sent(),d=0,b.label=5;case 5:return l&&l.value?[4,f.put(l.value)]:[3,8];case 6:return b.sent(),[4,l.continue()];case 7:return l=b.sent(),d++,[3,5];case 8:t.deleteObjectStore(c),N.debug(d+" "+a+" records migrated"),b.label=9;case 9:return s=o.next(),[3,3];case 10:return[3,13];case 11:return h=b.sent(),v={error:h},[3,13];case 12:try{s&&!s.done&&(g=o.return)&&g.call(o)}finally{if(v)throw v.error}return[7];case 13:return[3,15];case 14:throw p=b.sent(),N.error("Error migrating IndexedDB data",p),i.abort(),p;case 15:case 16:return[2]}}))}))}})]);case 6:o.db=u.sent(),this.resolve(),u.label=7;case 7:return[3,9];case 8:return s=u.sent(),this.reject(s),[3,9];case 9:return[2]}}))}))},e.prototype._get=function(e,t){return k(this,void 0,void 0,(function(){var n,r;return O(this,(function(i){switch(i.label){case 0:return"string"==typeof e?(r=e,n=this.db.transaction(r,"readonly").store.index("byId")):n=e.index("byId"),[4,n.get(t)];case 1:return[2,i.sent()]}}))}))},e.prototype.save=function(e,t){var n,r;return k(this,void 0,void 0,(function(){var i,o,s,a,u,c,f,l,d,h,p,v,g,m,b,y,w,_,E,I,k,C,T,R,L,j=this;return O(this,(function(O){switch(O.label){case 0:return[4,this.checkPrivate()];case 1:return O.sent(),i=Object.getPrototypeOf(e).constructor,o=this.getStorenameForModel(i),s=Object(A.x)(i.name,e,this.schema.namespaces[this.namespaceResolver(i)],this.modelInstanceCreator,this.getModelConstructorByModelName),a=this.namespaceResolver(i),u=new Set,c=Object.values(s).map((function(e){var t=e.modelName,n=e.item,r=e.instance,i=j.getStorename(a,t);return u.add(i),{storeName:i,item:n,instance:r}})),f=this.db.transaction(P([o],Array.from(u.values())),"readwrite"),l=f.objectStore(o),[4,this._get(l,e.id)];case 2:if(d=O.sent(),t&&d&&(h=S.a.getPredicates(t),p=h.predicates,v=h.type,!Object(A.y)(d,v,p)))throw g="Conditional update failed",N.error(g,{model:d,condition:p}),new Error(g);m=[],O.label=3;case 3:O.trys.push([3,14,15,20]),b=x(c),O.label=4;case 4:return[4,b.next()];case 5:return(y=O.sent()).done?[3,13]:(w=y.value,_=w.storeName,E=w.item,I=w.instance,k=f.objectStore(_),C=E.id,[4,this._get(k,C)]);case 6:return T=void 0===O.sent()?M.c.INSERT:M.c.UPDATE,C!==e.id?[3,9]:[4,k.index("byId").getKey(E.id)];case 7:return R=O.sent(),[4,k.put(E,R)];case 8:return O.sent(),m.push([I,T]),[3,12];case 9:return T!==M.c.INSERT?[3,12]:[4,k.index("byId").getKey(E.id)];case 10:return R=O.sent(),[4,k.put(E,R)];case 11:O.sent(),m.push([I,T]),O.label=12;case 12:return[3,4];case 13:return[3,20];case 14:return L=O.sent(),n={error:L},[3,20];case 15:return O.trys.push([15,,18,19]),y&&!y.done&&(r=b.return)?[4,r.call(b)]:[3,17];case 16:O.sent(),O.label=17;case 17:return[3,19];case 18:if(n)throw n.error;return[7];case 19:return[7];case 20:return[4,f.done];case 21:return O.sent(),[2,m]}}))}))},e.prototype.load=function(e,t,n){var r,i,o,s,a,u,c,f,l,d;return k(this,void 0,void 0,(function(){var h,p,v,g,m,b,y,w,_,S,E,M,I,k,C,T,N,R,L,j=this;return O(this,(function(O){switch(O.label){case 0:if(h=this.schema.namespaces[e],p=h.relationships[t].relationTypes,v=p.map((function(t){var n=t.modelName;return j.getStorename(e,n)})),g=this.getModelConstructorByModelName(e,t),0===v.length)return[2,n.map((function(e){return j.modelInstanceCreator(g,e)}))];m=this.db.transaction(P(v),"readonly"),O.label=1;case 1:O.trys.push([1,34,35,40]),b=x(p),O.label=2;case 2:return[4,b.next()];case 3:if((y=O.sent()).done)return[3,33];switch(w=y.value,_=w.fieldName,S=w.modelName,E=w.targetName,M=this.getStorename(e,S),I=m.objectStore(M),k=this.getModelConstructorByModelName(e,S),w.relationType){case"HAS_ONE":return[3,4];case"BELONGS_TO":return[3,17];case"HAS_MANY":return[3,30]}return[3,31];case 4:O.trys.push([4,10,11,16]),r=x(n),O.label=5;case 5:return[4,r.next()];case 6:return(i=O.sent()).done?[3,9]:(T=i.value)[_]?[4,this._get(I,T[_])]:[3,8];case 7:N=O.sent(),T[_]=N&&this.modelInstanceCreator(k,N),O.label=8;case 8:return[3,5];case 9:return[3,16];case 10:return C=O.sent(),c={error:C},[3,16];case 11:return O.trys.push([11,,14,15]),i&&!i.done&&(f=r.return)?[4,f.call(r)]:[3,13];case 12:O.sent(),O.label=13;case 13:return[3,15];case 14:if(c)throw c.error;return[7];case 15:return[7];case 16:return[3,32];case 17:O.trys.push([17,23,24,29]),o=x(n),O.label=18;case 18:return[4,o.next()];case 19:return(s=O.sent()).done?[3,22]:(T=s.value)[E]?[4,this._get(I,T[E])]:[3,21];case 20:N=O.sent(),T[_]=N&&this.modelInstanceCreator(k,N),delete T[E],O.label=21;case 21:return[3,18];case 22:return[3,29];case 23:return R=O.sent(),l={error:R},[3,29];case 24:return O.trys.push([24,,27,28]),s&&!s.done&&(d=o.return)?[4,d.call(o)]:[3,26];case 25:O.sent(),O.label=26;case 26:return[3,28];case 27:if(l)throw l.error;return[7];case 28:return[7];case 29:case 30:return[3,32];case 31:return Object(A.f)(w.relationType),[3,32];case 32:return[3,2];case 33:return[3,40];case 34:return L=O.sent(),a={error:L},[3,40];case 35:return O.trys.push([35,,38,39]),y&&!y.done&&(u=b.return)?[4,u.call(b)]:[3,37];case 36:O.sent(),O.label=37;case 37:return[3,39];case 38:if(a)throw a.error;return[7];case 39:return[7];case 40:return[2,n.map((function(e){return j.modelInstanceCreator(g,e)}))]}}))}))},e.prototype.query=function(e,t,n){return k(this,void 0,void 0,(function(){var r,i,o,s,a,u,c,f,l,d,h,p,v,g;return O(this,(function(m){switch(m.label){case 0:return[4,this.checkPrivate()];case 1:return m.sent(),r=this.getStorenameForModel(e),i=this.namespaceResolver(e),o=n&&n.sort,t?(s=S.a.getPredicates(t))?(a=s.predicates,u=s.type,(c=1===a.length&&a.find((function(e){return Object(M.k)(e)&&"id"===e.field&&"eq"===e.operator})))?(f=c.operand,[4,this._get(r,f)]):[3,5]):[3,8]:[3,8];case 2:return(l=m.sent())?[4,this.load(i,e.name,[l])]:[3,4];case 3:return d=T.apply(void 0,[m.sent(),1]),[2,[d[0]]];case 4:return[2,[]];case 5:return[4,this.db.getAll(r)];case 6:return p=m.sent(),h=a?p.filter((function(e){return Object(A.y)(e,u,a)})):p,[4,this.load(i,e.name,this.inMemoryPagination(h,n))];case 7:return[2,m.sent()];case 8:return o?[4,this.db.getAll(r)]:[3,11];case 9:return p=m.sent(),[4,this.load(i,e.name,this.inMemoryPagination(p,n))];case 10:return[2,m.sent()];case 11:return v=this.load,g=[i,e.name],[4,this.enginePagination(r,n)];case 12:return[4,v.apply(this,g.concat([m.sent()]))];case 13:return[2,m.sent()]}}))}))},e.prototype.inMemoryPagination=function(e,t){if(t){if(t.sort){var n=E.a.getPredicates(t.sort);if(n.length){var r=Object(A.w)(n);e.sort(r)}}var i=t.page,o=void 0===i?0:i,s=t.limit,a=void 0===s?0:s,u=Math.max(0,o*a)||0,c=a>0?u+a:e.length;return e.slice(u,c)}return e},e.prototype.enginePagination=function(e,t){return k(this,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,l,d;return O(this,(function(h){switch(h.label){case 0:return t?(r=t.page,i=void 0===r?0:r,o=t.limit,s=void 0===o?0:o,a=Math.max(0,i*s)||0,[4,this.db.transaction(e).objectStore(e).openCursor()]):[3,7];case 1:return(u=h.sent())&&a>0?[4,u.advance(a)]:[3,3];case 2:h.sent(),h.label=3;case 3:c=[],f="number"==typeof s&&s>0,l=!0,d=s,h.label=4;case 4:return l&&u&&u.value?(c.push(u.value),[4,u.continue()]):[3,6];case 5:return u=h.sent(),f?(d--,l=d>0&&null!==u):l=null!==u,[3,4];case 6:return n=c,[3,9];case 7:return[4,this.db.getAll(e)];case 8:n=h.sent(),h.label=9;case 9:return[2,n]}}))}))},e.prototype.queryOne=function(e,t){return void 0===t&&(t=M.d.FIRST),k(this,void 0,void 0,(function(){var n,r,i;return O(this,(function(o){switch(o.label){case 0:return[4,this.checkPrivate()];case 1:return o.sent(),n=this.getStorenameForModel(e),[4,this.db.transaction([n],"readonly").objectStore(n).openCursor(void 0,t===M.d.FIRST?"next":"prev")];case 2:return r=o.sent(),[2,(i=r?r.value:void 0)&&this.modelInstanceCreator(e,i)]}}))}))},e.prototype.delete=function(e,t){return k(this,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,l,d,h,p,v,g;return O(this,(function(m){switch(m.label){case 0:return[4,this.checkPrivate()];case 1:return m.sent(),n=[],Object(A.s)(e)?(o=e,s=this.namespaceResolver(o),a=this.getStorenameForModel(o),[4,this.query(o,t)]):[3,9];case 2:return r=m.sent(),v=this.schema.namespaces[s].relationships[o.name].relationTypes,void 0===t?[3,5]:[4,this.deleteTraverse(v,r,o.name,s,n)];case 3:return m.sent(),[4,this.deleteItem(n)];case 4:return m.sent(),g=n.reduce((function(e,t){var n=t.items;return e.concat(n)}),[]),[2,[r,g]];case 5:return[4,this.deleteTraverse(v,r,o.name,s,n)];case 6:return m.sent(),[4,this.db.transaction([a],"readwrite").objectStore(a).clear()];case 7:return m.sent(),g=n.reduce((function(e,t){var n=t.items;return e.concat(n)}),[]),[2,[r,g]];case 8:return[3,17];case 9:return i=e,o=Object.getPrototypeOf(i).constructor,s=this.namespaceResolver(o),a=this.getStorenameForModel(o),t?(u=this.db.transaction([a],"readwrite"),c=u.objectStore(a),[4,this._get(c,i.id)]):[3,13];case 10:if(void 0===(f=m.sent()))return p="Model instance not found in storage",N.warn(p,{model:i}),[2,[[i],[]]];if(l=S.a.getPredicates(t),d=l.predicates,h=l.type,!Object(A.y)(f,h,d))throw p="Conditional update failed",N.error(p,{model:f,condition:d}),new Error(p);return[4,u.done];case 11:return m.sent(),v=this.schema.namespaces[s].relationships[o.name].relationTypes,[4,this.deleteTraverse(v,[i],o.name,s,n)];case 12:return m.sent(),[3,15];case 13:return v=this.schema.namespaces[s].relationships[o.name].relationTypes,[4,this.deleteTraverse(v,[i],o.name,s,n)];case 14:m.sent(),m.label=15;case 15:return[4,this.deleteItem(n)];case 16:return m.sent(),g=n.reduce((function(e,t){var n=t.items;return e.concat(n)}),[]),[2,[[i],g]];case 17:return[2]}}))}))},e.prototype.deleteItem=function(e){var t,n,r,i,o,s;return k(this,void 0,void 0,(function(){var a,u,c,f,l,d,h,p,v,g,m,b;return O(this,(function(y){switch(y.label){case 0:a=e.map((function(e){return e.storeName})),u=this.db.transaction(P(a),"readwrite"),y.label=1;case 1:y.trys.push([1,22,23,28]),t=x(e),y.label=2;case 2:return[4,t.next()];case 3:if((n=y.sent()).done)return[3,21];c=n.value,f=c.storeName,l=c.items,d=u.objectStore(f),y.label=4;case 4:y.trys.push([4,14,15,20]),h=x(l),y.label=5;case 5:return[4,h.next()];case 6:return(p=y.sent()).done?[3,13]:(v=p.value)?(g=void 0,"object"!==I(v)?[3,8]:[4,d.index("byId").getKey(v.id)]):[3,12];case 7:return g=y.sent(),[3,10];case 8:return[4,d.index("byId").getKey(v.toString())];case 9:g=y.sent(),y.label=10;case 10:return void 0===g?[3,12]:[4,d.delete(g)];case 11:y.sent(),y.label=12;case 12:return[3,5];case 13:return[3,20];case 14:return m=y.sent(),o={error:m},[3,20];case 15:return y.trys.push([15,,18,19]),p&&!p.done&&(s=h.return)?[4,s.call(h)]:[3,17];case 16:y.sent(),y.label=17;case 17:return[3,19];case 18:if(o)throw o.error;return[7];case 19:return[7];case 20:return[3,2];case 21:return[3,28];case 22:return b=y.sent(),r={error:b},[3,28];case 23:return y.trys.push([23,,26,27]),n&&!n.done&&(i=t.return)?[4,i.call(t)]:[3,25];case 24:y.sent(),y.label=25;case 25:return[3,27];case 26:if(r)throw r.error;return[7];case 27:return[7];case 28:return[2]}}))}))},e.prototype.deleteTraverse=function(e,t,n,r,i){var o,s,a,u,c,f,l,d,h,p,v,g;return k(this,void 0,void 0,(function(){var m,b,y,w,_,S,E,M,I,k,C,T=this;return O(this,(function(O){switch(O.label){case 0:O.trys.push([0,35,36,41]),o=x(e),O.label=1;case 1:return[4,o.next()];case 2:if((s=O.sent()).done)return[3,34];switch(m=s.value,b=m.relationType,m.fieldName,y=m.modelName,w=this.getStorename(r,y),_=Object(A.g)(this.schema.namespaces[r].relationships[y].relationTypes,n)||Object(A.h)(this.schema.namespaces[r].relationships[y].indexes,m.associatedWith),b){case"HAS_ONE":return[3,3];case"HAS_MANY":return[3,17];case"BELONGS_TO":return[3,31]}return[3,32];case 3:O.trys.push([3,10,11,16]),a=x(t),O.label=4;case 4:return[4,a.next()];case 5:return(u=O.sent()).done?[3,9]:(M=u.value,[4,this.db.transaction(w,"readwrite").objectStore(w).index(_).get(M.id)]);case 6:return S=O.sent(),[4,this.deleteTraverse(this.schema.namespaces[r].relationships[y].relationTypes,S?[S]:[],y,r,i)];case 7:O.sent(),O.label=8;case 8:return[3,4];case 9:return[3,16];case 10:return E=O.sent(),h={error:E},[3,16];case 11:return O.trys.push([11,,14,15]),u&&!u.done&&(p=a.return)?[4,p.call(a)]:[3,13];case 12:O.sent(),O.label=13;case 13:return[3,15];case 14:if(h)throw h.error;return[7];case 15:return[7];case 16:return[3,33];case 17:O.trys.push([17,24,25,30]),c=x(t),O.label=18;case 18:return[4,c.next()];case 19:return(f=O.sent()).done?[3,23]:(M=f.value,[4,this.db.transaction(w,"readwrite").objectStore(w).index(_).getAll(M.id)]);case 20:return I=O.sent(),[4,this.deleteTraverse(this.schema.namespaces[r].relationships[y].relationTypes,I,y,r,i)];case 21:O.sent(),O.label=22;case 22:return[3,18];case 23:return[3,30];case 24:return k=O.sent(),v={error:k},[3,30];case 25:return O.trys.push([25,,28,29]),f&&!f.done&&(g=c.return)?[4,g.call(c)]:[3,27];case 26:O.sent(),O.label=27;case 27:return[3,29];case 28:if(v)throw v.error;return[7];case 29:return[7];case 30:case 31:return[3,33];case 32:return Object(A.f)(b),[3,33];case 33:return[3,1];case 34:return[3,41];case 35:return C=O.sent(),l={error:C},[3,41];case 36:return O.trys.push([36,,39,40]),s&&!s.done&&(d=o.return)?[4,d.call(o)]:[3,38];case 37:O.sent(),O.label=38;case 38:return[3,40];case 39:if(l)throw l.error;return[7];case 40:return[7];case 41:return i.push({storeName:this.getStorename(r,n),items:t.map((function(e){return T.modelInstanceCreator(T.getModelConstructorByModelName(r,n),e)}))}),[2]}}))}))},e.prototype.clear=function(){return k(this,void 0,void 0,(function(){return O(this,(function(e){switch(e.label){case 0:return[4,this.checkPrivate()];case 1:return e.sent(),this.db.close(),[4,m(this.dbName)];case 2:return e.sent(),this.db=void 0,this.initPromise=void 0,[2]}}))}))},e.prototype.batchSave=function(e,t){return k(this,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,l,d,h;return O(this,(function(p){switch(p.label){case 0:return 0===t.length?[2,[]]:[4,this.checkPrivate()];case 1:p.sent(),n=[],r=this.getStorenameForModel(e),i=this.db.transaction(r,"readwrite"),o=i.store,s=function(t){var r,i,s,u,c;return O(this,(function(f){switch(f.label){case 0:return r=Object(A.x)(e.name,a.modelInstanceCreator(e,t),a.schema.namespaces[a.namespaceResolver(e)],a.modelInstanceCreator,a.getModelConstructorByModelName),i=t.id,s=t._deleted,[4,o.index("byId").getKey(i)];case 1:return u=f.sent(),s?[3,3]:(c=r.find((function(e){return e.instance.id===i})).instance,n.push([c,u?M.c.UPDATE:M.c.INSERT]),[4,o.put(c,u)]);case 2:return f.sent(),[3,5];case 3:return n.push([t,M.c.DELETE]),u?[4,o.delete(u)]:[3,5];case 4:f.sent(),f.label=5;case 5:return[2]}}))},a=this,p.label=2;case 2:p.trys.push([2,7,8,9]),u=C(t),c=u.next(),p.label=3;case 3:return c.done?[3,6]:(f=c.value,[5,s(f)]);case 4:p.sent(),p.label=5;case 5:return c=u.next(),[3,3];case 6:return[3,9];case 7:return l=p.sent(),d={error:l},[3,9];case 8:try{c&&!c.done&&(h=u.return)&&h.call(u)}finally{if(d)throw d.error}return[7];case 9:return[4,i.done];case 10:return p.sent(),[2,n]}}))}))},e}();t.default=new R},function(e,t,n){"use strict";n.r(t),n.d(t,"AsyncStorageAdapter",(function(){return I}));var r=n(44),i=n(4),o=n(3),s=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},a=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},u=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},c=function(){var e=this;this.db=new Map,this.getAllKeys=function(){return s(e,void 0,void 0,(function(){return a(this,(function(e){return[2,Array.from(this.db.keys())]}))}))},this.multiGet=function(t){return s(e,void 0,void 0,(function(){var e=this;return a(this,(function(n){return[2,t.reduce((function(t,n){return t.push([n,e.db.get(n)]),t}),[])]}))}))},this.multiRemove=function(t,n){return s(e,void 0,void 0,(function(){var e=this;return a(this,(function(r){return t.forEach((function(t){return e.db.delete(t)})),n(),[2]}))}))},this.multiSet=function(t,n){return s(e,void 0,void 0,(function(){var e=this;return a(this,(function(r){return t.forEach((function(t){var n=u(t,2),r=n[0],i=n[1];e.setItem(r,i)})),n(),[2]}))}))},this.setItem=function(t,n){return s(e,void 0,void 0,(function(){return a(this,(function(e){return[2,this.db.set(t,n)]}))}))},this.removeItem=function(t){return s(e,void 0,void 0,(function(){return a(this,(function(e){return[2,this.db.delete(t)]}))}))},this.getItem=function(t){return s(e,void 0,void 0,(function(){return a(this,(function(e){return[2,this.db.get(t)]}))}))}};var f=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},l=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},d=function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},h=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},p="@AmplifyDatastore",v=new Map,g=function(){function e(){this._collectionInMemoryIndex=new Map,this.storage=new c}return e.prototype.getCollectionIndex=function(e){return this._collectionInMemoryIndex.has(e)||this._collectionInMemoryIndex.set(e,new Map),this._collectionInMemoryIndex.get(e)},e.prototype.getMonotonicFactory=function(e){return v.has(e)||v.set(e,Object(o.v)()),v.get(e)},e.prototype.init=function(){return f(this,void 0,void 0,(function(){var e,t,n,r,i,o,s,a,u,c,f,v,g,m,b,y,w,_,S,E;return l(this,(function(l){switch(l.label){case 0:return this._collectionInMemoryIndex.clear(),[4,this.storage.getAllKeys()];case 1:e=l.sent(),t=[],l.label=2;case 2:l.trys.push([2,12,13,14]),n=d(e),r=n.next(),l.label=3;case 3:return r.done?[3,11]:(i=r.value,o=h(i.split("::"),5),s=o[0],a=o[1],u=o[2],c=o[3],f=o[4],s!==p?[3,10]:"Data"!==u?[3,9]:(v=void 0,void 0!==f?[3,7]:(g=c,m=this.getMonotonicFactory(a)(),b=this.getLegacyKeyForItem(a,g),y=this.getKeyForItem(a,g,m),[4,this.storage.getItem(b)])));case 4:return w=l.sent(),[4,this.storage.setItem(y,w)];case 5:return l.sent(),[4,this.storage.removeItem(b)];case 6:return l.sent(),v=m,[3,8];case 7:v=c,l.label=8;case 8:return this.getCollectionIndex(a).set(f,v),[3,10];case 9:"Collection"===u&&t.push(i),l.label=10;case 10:return r=n.next(),[3,3];case 11:return[3,14];case 12:return _=l.sent(),S={error:_},[3,14];case 13:try{r&&!r.done&&(E=n.return)&&E.call(n)}finally{if(S)throw S.error}return[7];case 14:return t.length>0?[4,this.storage.multiRemove(t)]:[3,16];case 15:l.sent(),l.label=16;case 16:return[2]}}))}))},e.prototype.save=function(e,t){return f(this,void 0,void 0,(function(){var n,r;return l(this,(function(i){switch(i.label){case 0:return n=this.getCollectionIndex(t).get(e.id)||this.getMonotonicFactory(t)(),r=this.getKeyForItem(t,e.id,n),this.getCollectionIndex(t).set(e.id,n),[4,this.storage.setItem(r,JSON.stringify(e))];case 1:return i.sent(),[2]}}))}))},e.prototype.batchSave=function(e,t){return f(this,void 0,void 0,(function(){var n,r,o,s,a,u,c,f,p,v,g,m,b,y,w,_,S,E,M,A,I,k=this;return l(this,(function(l){switch(l.label){case 0:if(0===t.length)return[2,[]];n=[],r=this.getCollectionIndex(e),o=new Set,s=new Set,a=[],u={};try{for(c=d(t),f=c.next();!f.done;f=c.next())p=f.value,v=p.id,g=p._deleted,m=r.get(v)||this.getMonotonicFactory(e)(),S=this.getKeyForItem(e,v,m),a.push(S),u[S]={ulid:m,model:p},g?o.add(S):s.add(S)}catch(e){E={error:e}}finally{try{f&&!f.done&&(M=c.return)&&M.call(c)}finally{if(E)throw E.error}}return[4,this.storage.multiGet(a)];case 1:return b=l.sent(),y=b.filter((function(e){return!!h(e,2)[1]})).reduce((function(e,t){var n=h(t,1)[0];return e.add(n)}),new Set),[4,new Promise((function(e,t){if(0!==o.size){var n=Array.from(o);n.forEach((function(e){return r.delete(u[e].model.id)})),k.storage.multiRemove(n,(function(n){n&&n.length>0?t(n):e()}))}else e()}))];case 2:return l.sent(),[4,new Promise((function(e,t){if(0!==s.size){var n=Array.from(s).map((function(e){return[e,JSON.stringify(u[e].model)]}));s.forEach((function(e){var t=u[e],n=t.model.id,i=t.ulid;r.set(n,i)})),k.storage.multiSet(n,(function(n){n&&n.length>0?t(n):e()}))}else e()}))];case 3:l.sent();try{for(w=d(a),_=w.next();!_.done;_=w.next())S=_.value,o.has(S)&&y.has(S)?n.push([u[S].model,i.c.DELETE]):s.has(S)&&n.push([u[S].model,y.has(S)?i.c.UPDATE:i.c.INSERT])}catch(e){A={error:e}}finally{try{_&&!_.done&&(I=w.return)&&I.call(w)}finally{if(A)throw A.error}}return[2,n]}}))}))},e.prototype.get=function(e,t){return f(this,void 0,void 0,(function(){var n,r,i;return l(this,(function(o){switch(o.label){case 0:return n=this.getCollectionIndex(t).get(e),r=this.getKeyForItem(t,e,n),[4,this.storage.getItem(r)];case 1:return i=o.sent(),[2,i&&JSON.parse(i)]}}))}))},e.prototype.getOne=function(e,t){return f(this,void 0,void 0,(function(){var n,r,o,s,a,u,c;return l(this,(function(f){switch(f.label){case 0:return n=this.getCollectionIndex(t),r=h(e===i.d.FIRST?function(){var e,t,r,i,o;try{for(var s=d(n),a=s.next();!a.done;a=s.next()){i=(r=h(a.value,2))[0],o=r[1];break}}catch(t){e={error:t}}finally{try{a&&!a.done&&(t=s.return)&&t.call(s)}finally{if(e)throw e.error}}return[i,o]}():function(){var e,t,r,i,o;try{for(var s=d(n),a=s.next();!a.done;a=s.next())i=(r=h(a.value,2))[0],o=r[1]}catch(t){e={error:t}}finally{try{a&&!a.done&&(t=s.return)&&t.call(s)}finally{if(e)throw e.error}}return[i,o]}(),2),o=r[0],s=r[1],a=this.getKeyForItem(t,o,s),(c=a)?[4,this.storage.getItem(a)]:[3,2];case 1:c=f.sent(),f.label=2;case 2:return[2,(u=c)&&JSON.parse(u)||void 0]}}))}))},e.prototype.getAll=function(e,t){return f(this,void 0,void 0,(function(){var n,r,i,o,s,a,u,c,f,p,v,g,m,b,y,w,_,S;return l(this,(function(l){switch(l.label){case 0:n=this.getCollectionIndex(e),i=(r=t||{}).page,o=void 0===i?0:i,s=r.limit,a=void 0===s?0:s,u=Math.max(0,o*a)||0,c=a>0?u+a:void 0,f=[],p=0;try{for(v=d(n),g=v.next();!g.done&&(m=h(g.value,2),b=m[0],y=m[1],++p<=u||(f.push(this.getKeyForItem(e,b,y)),p!==c));g=v.next());}catch(e){_={error:e}}finally{try{g&&!g.done&&(S=v.return)&&S.call(v)}finally{if(_)throw _.error}}return[4,this.storage.multiGet(f)];case 1:return w=l.sent(),[2,w.filter((function(e){return h(e,2)[1]})).map((function(e){var t=h(e,2)[1];return JSON.parse(t)}))]}}))}))},e.prototype.delete=function(e,t){return f(this,void 0,void 0,(function(){var n,r;return l(this,(function(i){switch(i.label){case 0:return n=this.getCollectionIndex(t).get(e),r=this.getKeyForItem(t,e,n),this.getCollectionIndex(t).delete(e),[4,this.storage.removeItem(r)];case 1:return i.sent(),[2]}}))}))},e.prototype.clear=function(){return f(this,void 0,void 0,(function(){var e,t;return l(this,(function(n){switch(n.label){case 0:return[4,this.storage.getAllKeys()];case 1:return e=n.sent(),t=e.filter((function(e){return e.startsWith(p)})),[4,this.storage.multiRemove(t)];case 2:return n.sent(),this._collectionInMemoryIndex.clear(),[2]}}))}))},e.prototype.getKeyForItem=function(e,t,n){return this.getKeyPrefixForStoreItems(e)+"::"+n+"::"+t},e.prototype.getLegacyKeyForItem=function(e,t){return this.getKeyPrefixForStoreItems(e)+"::"+t},e.prototype.getKeyPrefixForStoreItems=function(e){return p+"::"+e+"::Data"},e}(),m=n(9),b=n(245);function y(e){return(y="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var w=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},_=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},S=function(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,n=e[Symbol.asyncIterator];return n?n.call(e):(e="function"==typeof M?M(e):e[Symbol.iterator](),t={},r("next"),r("throw"),r("return"),t[Symbol.asyncIterator]=function(){return this},t);function r(n){t[n]=e[n]&&function(t){return new Promise((function(r,i){(function(e,t,n,r){Promise.resolve(r).then((function(t){e({value:t,done:n})}),t)})(r,i,(t=e[n](t)).done,t.value)}))}}},E=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},M=function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},A=new r.a("DataStore"),I=function(){function e(){}return e.prototype.getStorenameForModel=function(e){var t=this.namespaceResolver(e),n=e.name;return this.getStorename(t,n)},e.prototype.getStorename=function(e,t){return e+"_"+t},e.prototype.setUp=function(e,t,n,r){return w(this,void 0,void 0,(function(){var i,o=this;return _(this,(function(s){switch(s.label){case 0:return this.initPromise?[3,1]:(this.initPromise=new Promise((function(e,t){o.resolve=e,o.reject=t})),[3,3]);case 1:return[4,this.initPromise];case 2:return s.sent(),[2];case 3:this.schema=e,this.namespaceResolver=t,this.modelInstanceCreator=n,this.getModelConstructorByModelName=r,s.label=4;case 4:return s.trys.push([4,7,,8]),this.db?[3,6]:(this.db=new g,[4,this.db.init()]);case 5:s.sent(),this.resolve(),s.label=6;case 6:return[3,8];case 7:return i=s.sent(),this.reject(i),[3,8];case 8:return[2]}}))}))},e.prototype.save=function(e,t){var n,r;return w(this,void 0,void 0,(function(){var s,a,u,c,f,l,d,h,p,v,g,b,y,w,E,M,I,k,O,x,C,T=this;return _(this,(function(_){switch(_.label){case 0:return s=Object.getPrototypeOf(e).constructor,a=this.getStorenameForModel(s),u=Object(o.x)(s.name,e,this.schema.namespaces[this.namespaceResolver(s)],this.modelInstanceCreator,this.getModelConstructorByModelName),c=this.namespaceResolver(s),f=new Set,l=Object.values(u).map((function(e){var t=e.modelName,n=e.item,r=e.instance,i=T.getStorename(c,t);return f.add(i),{storeName:i,item:n,instance:r}})),[4,this.db.get(e.id,a)];case 1:if(d=_.sent(),t&&d&&(h=m.a.getPredicates(t),p=h.predicates,v=h.type,!Object(o.y)(d,v,p)))throw g="Conditional update failed",A.error(g,{model:d,condition:p}),new Error(g);b=[],_.label=2;case 2:_.trys.push([2,11,12,17]),y=S(l),_.label=3;case 3:return[4,y.next()];case 4:return(w=_.sent()).done?[3,10]:(E=w.value,M=E.storeName,I=E.item,k=E.instance,O=I.id,[4,this.db.get(O,M)]);case 5:return x=_.sent()?i.c.UPDATE:i.c.INSERT,O!==e.id?[3,7]:[4,this.db.save(I,M)];case 6:return _.sent(),b.push([k,x]),[3,9];case 7:return x!==i.c.INSERT?[3,9]:[4,this.db.save(I,M)];case 8:_.sent(),b.push([k,x]),_.label=9;case 9:return[3,3];case 10:return[3,17];case 11:return C=_.sent(),n={error:C},[3,17];case 12:return _.trys.push([12,,15,16]),w&&!w.done&&(r=y.return)?[4,r.call(y)]:[3,14];case 13:_.sent(),_.label=14;case 14:return[3,16];case 15:if(n)throw n.error;return[7];case 16:return[7];case 17:return[2,b]}}))}))},e.prototype.load=function(e,t,n){var r,i,s,a,u,c,f,l,d,h;return w(this,void 0,void 0,(function(){var p,v,g,m,b,y,w,E,M,A,I,k,O,x,C,T,P,N,R=this;return _(this,(function(_){switch(_.label){case 0:if(p=this.schema.namespaces[e],v=p.relationships[t].relationTypes,g=v.map((function(t){var n=t.modelName;return R.getStorename(e,n)})),m=this.getModelConstructorByModelName(e,t),0===g.length)return[2,n.map((function(e){return R.modelInstanceCreator(m,e)}))];_.label=1;case 1:_.trys.push([1,34,35,40]),b=S(v),_.label=2;case 2:return[4,b.next()];case 3:if((y=_.sent()).done)return[3,33];switch(w=y.value,E=w.fieldName,M=w.modelName,A=w.targetName,I=w.relationType,k=this.getStorename(e,M),O=this.getModelConstructorByModelName(e,M),I){case"HAS_ONE":return[3,4];case"BELONGS_TO":return[3,17];case"HAS_MANY":return[3,30]}return[3,31];case 4:_.trys.push([4,10,11,16]),r=S(n),_.label=5;case 5:return[4,r.next()];case 6:return(i=_.sent()).done?[3,9]:(C=i.value)[E]?[4,this.db.get(C[E],k)]:[3,8];case 7:T=_.sent(),C[E]=T&&this.modelInstanceCreator(O,T),_.label=8;case 8:return[3,5];case 9:return[3,16];case 10:return x=_.sent(),f={error:x},[3,16];case 11:return _.trys.push([11,,14,15]),i&&!i.done&&(l=r.return)?[4,l.call(r)]:[3,13];case 12:_.sent(),_.label=13;case 13:return[3,15];case 14:if(f)throw f.error;return[7];case 15:return[7];case 16:return[3,32];case 17:_.trys.push([17,23,24,29]),s=S(n),_.label=18;case 18:return[4,s.next()];case 19:return(a=_.sent()).done?[3,22]:(C=a.value)[A]?[4,this.db.get(C[A],k)]:[3,21];case 20:T=_.sent(),C[E]=T&&this.modelInstanceCreator(O,T),delete C[A],_.label=21;case 21:return[3,18];case 22:return[3,29];case 23:return P=_.sent(),d={error:P},[3,29];case 24:return _.trys.push([24,,27,28]),a&&!a.done&&(h=s.return)?[4,h.call(s)]:[3,26];case 25:_.sent(),_.label=26;case 26:return[3,28];case 27:if(d)throw d.error;return[7];case 28:return[7];case 29:case 30:return[3,32];case 31:return Object(o.f)(I),[3,32];case 32:return[3,2];case 33:return[3,40];case 34:return N=_.sent(),u={error:N},[3,40];case 35:return _.trys.push([35,,38,39]),y&&!y.done&&(c=b.return)?[4,c.call(b)]:[3,37];case 36:_.sent(),_.label=37;case 37:return[3,39];case 38:if(u)throw u.error;return[7];case 39:return[7];case 40:return[2,n.map((function(e){return R.modelInstanceCreator(m,e)}))]}}))}))},e.prototype.query=function(e,t,n){return w(this,void 0,void 0,(function(){var r,s,a,u,c,f,l,d,h,p,v,g,b,y;return _(this,(function(w){switch(w.label){case 0:return r=this.getStorenameForModel(e),s=this.namespaceResolver(e),a=n&&n.sort,t?(u=m.a.getPredicates(t))?(c=u.predicates,f=u.type,(l=1===c.length&&c.find((function(e){return Object(i.k)(e)&&"id"===e.field&&"eq"===e.operator})))?(d=l.operand,[4,this.db.get(d,r)]):[3,4]):[3,7]:[3,7];case 1:return(h=w.sent())?[4,this.load(s,e.name,[h])]:[3,3];case 2:return p=E.apply(void 0,[w.sent(),1]),[2,[p[0]]];case 3:return[2,[]];case 4:return[4,this.db.getAll(r)];case 5:return v=w.sent(),g=c?v.filter((function(e){return Object(o.y)(e,f,c)})):v,[4,this.load(s,e.name,this.inMemoryPagination(g,n))];case 6:return[2,w.sent()];case 7:return a?[4,this.db.getAll(r)]:[3,10];case 8:return b=w.sent(),[4,this.load(s,e.name,this.inMemoryPagination(b,n))];case 9:return[2,w.sent()];case 10:return[4,this.db.getAll(r,n)];case 11:return y=w.sent(),[4,this.load(s,e.name,y)];case 12:return[2,w.sent()]}}))}))},e.prototype.inMemoryPagination=function(e,t){if(t){if(t.sort){var n=b.a.getPredicates(t.sort);if(n.length){var r=Object(o.w)(n);e.sort(r)}}var i=t.page,s=void 0===i?0:i,a=t.limit,u=void 0===a?0:a,c=Math.max(0,s*u)||0,f=u>0?c+u:e.length;return e.slice(c,f)}return e},e.prototype.queryOne=function(e,t){return void 0===t&&(t=i.d.FIRST),w(this,void 0,void 0,(function(){var n,r;return _(this,(function(i){switch(i.label){case 0:return n=this.getStorenameForModel(e),[4,this.db.getOne(t,n)];case 1:return[2,(r=i.sent())&&this.modelInstanceCreator(e,r)]}}))}))},e.prototype.delete=function(e,t){return w(this,void 0,void 0,(function(){var n,r,i,s,a,u,c,f,l,d,h,p,v;return _(this,(function(g){switch(g.label){case 0:return n=[],Object(o.s)(e)?(s=e,a=this.namespaceResolver(s),[4,this.query(s,t)]):[3,8];case 1:return r=g.sent(),p=this.schema.namespaces[a].relationships[s.name].relationTypes,void 0===t?[3,4]:[4,this.deleteTraverse(p,r,s.name,a,n)];case 2:return g.sent(),[4,this.deleteItem(n)];case 3:return g.sent(),v=n.reduce((function(e,t){var n=t.items;return e.concat(n)}),[]),[2,[r,v]];case 4:return[4,this.deleteTraverse(p,r,s.name,a,n)];case 5:return g.sent(),[4,this.deleteItem(n)];case 6:return g.sent(),v=n.reduce((function(e,t){var n=t.items;return e.concat(n)}),[]),[2,[r,v]];case 7:return[3,15];case 8:return i=e,s=Object.getPrototypeOf(i).constructor,a=this.namespaceResolver(s),u=this.getStorenameForModel(s),t?[4,this.db.get(i.id,u)]:[3,11];case 9:if(void 0===(c=g.sent()))return h="Model instance not found in storage",A.warn(h,{model:i}),[2,[[i],[]]];if(f=m.a.getPredicates(t),l=f.predicates,d=f.type,!Object(o.y)(c,d,l))throw h="Conditional update failed",A.error(h,{model:c,condition:l}),new Error(h);return p=this.schema.namespaces[a].relationships[s.name].relationTypes,[4,this.deleteTraverse(p,[i],s.name,a,n)];case 10:return g.sent(),[3,13];case 11:return p=this.schema.namespaces[a].relationships[s.name].relationTypes,[4,this.deleteTraverse(p,[i],s.name,a,n)];case 12:g.sent(),g.label=13;case 13:return[4,this.deleteItem(n)];case 14:return g.sent(),v=n.reduce((function(e,t){var n=t.items;return e.concat(n)}),[]),[2,[[i],v]];case 15:return[2]}}))}))},e.prototype.deleteItem=function(e){var t,n,r,i,o,s;return w(this,void 0,void 0,(function(){var a,u,c,f,l,d,h,p,v;return _(this,(function(g){switch(g.label){case 0:g.trys.push([0,17,18,23]),t=S(e),g.label=1;case 1:return[4,t.next()];case 2:if((n=g.sent()).done)return[3,16];a=n.value,u=a.storeName,c=a.items,g.label=3;case 3:g.trys.push([3,9,10,15]),f=S(c),g.label=4;case 4:return[4,f.next()];case 5:return(l=g.sent()).done?[3,8]:(d=l.value)?"object"!==y(d)?[3,7]:(h=d.id,[4,this.db.delete(h,u)]):[3,7];case 6:g.sent(),g.label=7;case 7:return[3,4];case 8:return[3,15];case 9:return p=g.sent(),o={error:p},[3,15];case 10:return g.trys.push([10,,13,14]),l&&!l.done&&(s=f.return)?[4,s.call(f)]:[3,12];case 11:g.sent(),g.label=12;case 12:return[3,14];case 13:if(o)throw o.error;return[7];case 14:return[7];case 15:return[3,1];case 16:return[3,23];case 17:return v=g.sent(),r={error:v},[3,23];case 18:return g.trys.push([18,,21,22]),n&&!n.done&&(i=t.return)?[4,i.call(t)]:[3,20];case 19:g.sent(),g.label=20;case 20:return[3,22];case 21:if(r)throw r.error;return[7];case 22:return[7];case 23:return[2]}}))}))},e.prototype.deleteTraverse=function(e,t,n,r,i){var s,a,u,c,f,l,d,h,p,v,g,m;return w(this,void 0,void 0,(function(){var b,y,w,E,M,A,I,k,O,x,C,T,P=this;return _(this,(function(_){switch(_.label){case 0:_.trys.push([0,35,36,41]),s=S(e),_.label=1;case 1:return[4,s.next()];case 2:if((a=_.sent()).done)return[3,34];switch(b=a.value,y=b.relationType,w=b.modelName,E=this.getStorename(r,w),M=Object(o.g)(this.schema.namespaces[r].relationships[w].relationTypes,n)||Object(o.h)(this.schema.namespaces[r].relationships[w].indexes,b.associatedWith),y){case"HAS_ONE":return[3,3];case"HAS_MANY":return[3,17];case"BELONGS_TO":return[3,31]}return[3,32];case 3:_.trys.push([3,10,11,16]),u=S(t),_.label=4;case 4:return[4,u.next()];case 5:return(c=_.sent()).done?[3,9]:(k=c.value,[4,this.db.getAll(E)]);case 6:return O=_.sent(),A=O.filter((function(e){return e[M]===k.id})),[4,this.deleteTraverse(this.schema.namespaces[r].relationships[w].relationTypes,A,w,r,i)];case 7:_.sent(),_.label=8;case 8:return[3,4];case 9:return[3,16];case 10:return I=_.sent(),p={error:I},[3,16];case 11:return _.trys.push([11,,14,15]),c&&!c.done&&(v=u.return)?[4,v.call(u)]:[3,13];case 12:_.sent(),_.label=13;case 13:return[3,15];case 14:if(p)throw p.error;return[7];case 15:return[7];case 16:return[3,33];case 17:_.trys.push([17,24,25,30]),f=S(t),_.label=18;case 18:return[4,f.next()];case 19:return(l=_.sent()).done?[3,23]:(k=l.value,[4,this.db.getAll(E)]);case 20:return O=_.sent(),x=O.filter((function(e){return e[M]===k.id})),[4,this.deleteTraverse(this.schema.namespaces[r].relationships[w].relationTypes,x,w,r,i)];case 21:_.sent(),_.label=22;case 22:return[3,18];case 23:return[3,30];case 24:return C=_.sent(),g={error:C},[3,30];case 25:return _.trys.push([25,,28,29]),l&&!l.done&&(m=f.return)?[4,m.call(f)]:[3,27];case 26:_.sent(),_.label=27;case 27:return[3,29];case 28:if(g)throw g.error;return[7];case 29:return[7];case 30:case 31:return[3,33];case 32:return Object(o.f)(y),[3,33];case 33:return[3,1];case 34:return[3,41];case 35:return T=_.sent(),d={error:T},[3,41];case 36:return _.trys.push([36,,39,40]),a&&!a.done&&(h=s.return)?[4,h.call(s)]:[3,38];case 37:_.sent(),_.label=38;case 38:return[3,40];case 39:if(d)throw d.error;return[7];case 40:return[7];case 41:return i.push({storeName:this.getStorename(r,n),items:t.map((function(e){return P.modelInstanceCreator(P.getModelConstructorByModelName(r,n),e)}))}),[2]}}))}))},e.prototype.clear=function(){return w(this,void 0,void 0,(function(){return _(this,(function(e){switch(e.label){case 0:return[4,this.db.clear()];case 1:return e.sent(),this.db=void 0,this.initPromise=void 0,[2]}}))}))},e.prototype.batchSave=function(e,t){return w(this,void 0,void 0,(function(){var n,r,i,s,a,u,c,f,l,d,h;return _(this,(function(p){switch(p.label){case 0:n=e.name,r=this.namespaceResolver(e),i=this.getStorename(r,n),s=[],a=function(t){var n=t.id,r=Object(o.x)(e.name,u.modelInstanceCreator(e,t),u.schema.namespaces[u.namespaceResolver(e)],u.modelInstanceCreator,u.getModelConstructorByModelName).find((function(e){return e.instance.id===n})).instance;s.push(r)},u=this;try{for(c=M(t),f=c.next();!f.done;f=c.next())l=f.value,a(l)}catch(e){d={error:e}}finally{try{f&&!f.done&&(h=c.return)&&h.call(c)}finally{if(d)throw d.error}}return[4,this.db.batchSave(i,s)];case 1:return[2,p.sent()]}}))}))},e}();t.default=new I},function(e,t,n){"use strict";n.r(t),n.d(t,"fromUtf8",(function(){return r})),n.d(t,"toUtf8",(function(){return i}));var r=function(e){return"function"==typeof TextEncoder?function(e){return(new TextEncoder).encode(e)}(e):function(e){for(var t=[],n=0,r=e.length;n<r;n++){var i=e.charCodeAt(n);if(i<128)t.push(i);else if(i<2048)t.push(i>>6|192,63&i|128);else if(n+1<e.length&&55296==(64512&i)&&56320==(64512&e.charCodeAt(n+1))){var o=65536+((1023&i)<<10)+(1023&e.charCodeAt(++n));t.push(o>>18|240,o>>12&63|128,o>>6&63|128,63&o|128)}else t.push(i>>12|224,i>>6&63|128,63&i|128)}return Uint8Array.from(t)}(e)},i=function(e){return"function"==typeof TextDecoder?function(e){return new TextDecoder("utf-8").decode(e)}(e):function(e){for(var t="",n=0,r=e.length;n<r;n++){var i=e[n];if(i<128)t+=String.fromCharCode(i);else if(192<=i&&i<224){var o=e[++n];t+=String.fromCharCode((31&i)<<6|63&o)}else if(240<=i&&i<365){var s="%"+[i,e[++n],e[++n],e[++n]].map((function(e){return e.toString(16)})).join("%");t+=decodeURIComponent(s)}else t+=String.fromCharCode((15&i)<<12|(63&e[++n])<<6|63&e[++n])}return t}(e)}},,,,,,,,,,,,,,,,,function(e,t,n){"use strict";n.d(t,"a",(function(){return l})),n.d(t,"b",(function(){return d}));var r,i=n(44),o=(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)},function(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),s=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r.throw(e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}u((r=r.apply(e,t||[])).next())}))},a=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,a])}}},u=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)s.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},c=function(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(u(arguments[t]));return e},f=new i.a("Util"),l=function(e){function t(t){var n=e.call(this,t)||this;return n.nonRetryable=!0,n}return o(t,e),t}(Error);var d=function(e,t,n){return void 0===n&&(n=3e5),function e(t,n,r,i){return void 0===i&&(i=1),s(this,void 0,void 0,(function(){var o,s;return a(this,(function(a){switch(a.label){case 0:if("function"!=typeof t)throw Error("functionToRetry must be a function");f.debug(t.name+" attempt #"+i+" with this vars: "+JSON.stringify(n)),a.label=1;case 1:return a.trys.push([1,3,,8]),[4,t.apply(void 0,c(n))];case 2:return[2,a.sent()];case 3:if(o=a.sent(),f.debug("error on "+t.name,o),(u=o)&&u.nonRetryable)throw f.debug(t.name+" non retryable error",o),o;return s=r(i,n,o),f.debug(t.name+" retrying in "+s+" ms"),!1===s?[3,6]:[4,new Promise((function(e){return setTimeout(e,s)}))];case 4:return a.sent(),[4,e(t,n,r,i+1)];case 5:return[2,a.sent()];case 6:throw o;case 7:return[3,8];case 8:return[2]}var u}))}))}(e,t,function(e){return function(t){var n=100*Math.pow(2,t)+100*Math.random();return!(n>e)&&n}}(n))}}])})); - -// version: 3.3.18 diff --git a/docs/javascript/extra.js b/docs/javascript/extra.js deleted file mode 100644 index 0ade322a6..000000000 --- a/docs/javascript/extra.js +++ /dev/null @@ -1,71 +0,0 @@ -const Amplify = window.aws_amplify.Amplify -const Analytics = Amplify.Analytics -const KinesisFirehoseProvider = window.aws_amplify.AWSKinesisFirehoseProvider - -const awsconfig = { - "aws_project_region": "eu-west-1", - "aws_cognito_identity_pool_id": "eu-west-1:3df3caec-4bb6-4891-b154-ee940c8264b8", - "aws_cognito_region": "eu-west-1", - "aws_kinesis_firehose_stream_name": "ClickStreamKinesisFirehose-OGX7PQdrynUo", -}; - -const RUNTIME = "python" - -const attachListeners = () => { - /* Register handler to log search on blur */ - document.addEventListener("DOMContentLoaded", function () { - recordPageView({ - prevLocation: document.referrer - }) - if (document.forms.search) { - let query = document.forms.search.query - query.addEventListener("blur", function () { - // If Search result is ever actionable - // we should populate `value` - if (this.value) { - let path = document.location.pathname; - console.info(`Search value: ${this.value}`) - recordPageView({ - searchPattern: this.value - }) - } - }) - } - }) - - // Register handler for page sections when browser history is changed - window.onpopstate = function (event) { - recordPageView({ - prevLocation: document.referrer - }) - }; -} - -const init = () => { - Analytics.addPluggable(new KinesisFirehoseProvider()) - Amplify.configure(awsconfig); - - Analytics.configure({ - AWSKinesisFirehose: { - region: awsconfig.aws_project_region - } - }) - - attachListeners() -} - -const recordPageView = ({prevLocation, searchPattern}) => { - Analytics.record({ - data: { - // Do not count page view for search - url: searchPattern ? null : window.location.href, - section: searchPattern ? null : location.pathname, - previous: prevLocation || null, - search: searchPattern || null, - language: RUNTIME - }, - streamName: awsconfig.aws_kinesis_firehose_stream_name - }, 'AWSKinesisFirehose') -} - -init() diff --git a/docs/media/idempotent_sequence.png b/docs/media/idempotent_sequence.png new file mode 100644 index 000000000..92593184a Binary files /dev/null and b/docs/media/idempotent_sequence.png differ diff --git a/docs/media/idempotent_sequence_exception.png b/docs/media/idempotent_sequence_exception.png new file mode 100644 index 000000000..4cf065993 Binary files /dev/null and b/docs/media/idempotent_sequence_exception.png differ diff --git a/docs/media/intellij_checkstyle_1.png b/docs/media/intellij_checkstyle_1.png new file mode 100644 index 000000000..322e24744 Binary files /dev/null and b/docs/media/intellij_checkstyle_1.png differ diff --git a/docs/media/intellij_checkstyle_2.png b/docs/media/intellij_checkstyle_2.png new file mode 100644 index 000000000..7fc82187b Binary files /dev/null and b/docs/media/intellij_checkstyle_2.png differ diff --git a/docs/media/intellij_checkstyle_3.png b/docs/media/intellij_checkstyle_3.png new file mode 100644 index 000000000..6e08dde62 Binary files /dev/null and b/docs/media/intellij_checkstyle_3.png differ diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 000000000..5f7a59c02 --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block outdated %} +You're not viewing the latest version. +<a href="{{ '../' ~ base_url }}"> + <strong>Click here to go to latest.</strong> +</a> +{% endblock %} + +{% block extrahead %} + <meta name="guide-name" content="Powertools for AWS Lambda (Java)"> + <meta name="service-name" content="Powertools for AWS Lambda"> +{% endblock %} diff --git a/docs/processes/maintainers.md b/docs/processes/maintainers.md new file mode 100644 index 000000000..f2839c532 --- /dev/null +++ b/docs/processes/maintainers.md @@ -0,0 +1,249 @@ +--- +title: Maintainers playbook +description: Process +--- + +<!-- markdownlint-disable MD043 --> + +## Overview + +!!! note "Please treat this content as a living document." + +This is document explains who the maintainers are, their responsibilities, and how they should be doing it. If you're interested in contributing, + see [CONTRIBUTING](https://github.com/aws-powertools/powertools-lambda-java/blob/main/CONTRIBUTING.md){target="_blank"}. + +## Current Maintainers + +| Maintainer | GitHub ID | Affiliation | +| --------------- | -------------------------------------------------------------------- | ----------- | +| Philipp Page | [phipag](https://github.com/phipag){target="\_blank" rel="nofollow"} | Amazon | + +## Emeritus + +Previous active maintainers who contributed to this project. + +| Maintainer | GitHub ID | Affiliation | +| --------------------- | -------------------------------------------------------------------------------------- | ------------- | +| Simon Thulbourn | [sthulb](https://github.com/sthulb){target="\_blank" rel="nofollow"} | Former Amazon | +| Jerome Van Der Linden | [jeromevdl](https://github.com/jeromevdl){target="\_blank" rel="nofollow"} | Amazon | +| Michele Ricciardi | [mriccia](https://github.com/mriccia){target="\_blank" rel="nofollow"} | Amazon | +| Scott Gerring | [scottgerring](https://github.com/scottgerring){target="\_blank" rel="nofollow"} | DataDog | +| Mark Sailes | [msailes](https://github.com/msailes){target="\_blank" rel="nofollow"} | Former Amazon | +| Pankaj Agrawal | [pankajagrawal16](https://github.com/pankajagrawal16){target="\_blank" rel="nofollow"} | Former Amazon | +| Steve Houel | [stevehouel](https://github.com/stevehouel){target="\_blank" rel="nofollow"} | Amazon | + +## Labels + +These are the most common labels used by maintainers to triage issues, pull requests (PR), and for project management: + +| Label | Usage | Notes | +|----------------------------------|---------------------------------------------------------------------------------------------------|----------------------------------------------------| +| triage | New issues that require maintainers review | Issue template | +| bug | Unexpected, reproducible and unintended software behavior | PR/Release automation; Doc snippets are excluded; | +| documentation | Documentation improvements | PR/Release automation; Doc additions, fixes, etc.; | +| duplicate | Dupe of another issue | | +| enhancement | New or enhancements to existing features | Issue template | +| RFC | Technical design documents related to a feature request | Issue template | +| help wanted | Tasks you want help from anyone to move forward | Bandwidth, complex topics, etc. | +| feature-parity | Adding features present in other Powertools for Lambda libraries | | +| good first issue | Somewhere for new contributors to start | | +| governance | Issues related to project governance - contributor guides, automation, etc. | | +| question | Issues that are raised to ask questions | | +| maven | Related to the build system | | +| need-more-information | Missing information before making any calls | | +| status/staged-next-release | Changes are merged and will be available once the next release is made. | | +| status/staged-next-major-release | Contains breaking changes - merged changes will be available once the next major release is made. | | +| blocked | Issues or PRs that are blocked for varying reasons | Timeline is uncertain | +| priority:1 | Critical - needs urgent attention | | +| priority:2 | High - core feature, or affects 60%+ of users | | +| priority:3 | Neutral - not a core feature, or affects < 40% of users | | +| priority:4 | Low - nice to have | | +| priority:5 | Low - idea for later | | +| invalid | This doesn't seem right | | +| size/XS | PRs between 0-9 LOC | PR automation | +| size/S | PRs between 10-29 LOC | PR automation | +| size/M | PRs between 30-99 LOC | PR automation | +| size/L | PRs between 100-499 LOC | PR automation | +| size/XL | PRs between 500-999 LOC, often PRs that grown with feedback | PR automation | +| size/XXL | PRs with 1K+ LOC, largely documentation related | PR automation | +| dependencies | Changes that touch dependencies, e.g. Dependabot, etc. | PR/ automation | +| maintenance | Address outstanding tech debt | | + +## Maintainer Responsibilities + +Maintainers are active and visible members of the community, and have +[maintain-level permissions on a repository](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization){target="_blank"}. +Use those privileges to serve the community and evolve code as follows. + +Be aware of recurring ambiguous situations and [document them](#common-scenarios) to help your fellow maintainers. + +### Uphold Code of Conduct + +<!-- markdownlint-disable-next-line MD013 --> +Model the behavior set forward by the +[Code of Conduct](https://github.com/aws-powertools/powertools-lambda-java/blob/main/CODE_OF_CONDUCT.md){target="_blank"} +and raise any violations to other maintainers and admins. There could be unusual circumstances where inappropriate +behavior does not immediately fall within the [Code of Conduct](https://github.com/aws-powertools/powertools-lambda-java/blob/main/CODE_OF_CONDUCT.md){target="_blank"}. + +These might be nuanced and should be handled with extra care - when in doubt, do not engage and reach out to other maintainers +and admins. + +### Prioritize Security + +Security is your number one priority. Maintainer's Github keys must be password protected securely and any reported +security vulnerabilities are addressed before features or bugs. + +Note that this repository is monitored and supported 24/7 by Amazon Security, see +[Security disclosures](https://github.com/aws-powertools/powertools-lambda-java/){target="_blank"} for details. + +### Review Pull Requests + +Review pull requests regularly, comment, suggest, reject, merge and close. Accept only high quality pull-requests. +Provide code reviews and guidance on incoming pull requests. + +PRs are [labeled](#labels) based on file changes and semantic title. Pay attention to whether labels reflect the current +state of the PR and correct accordingly. + +Use and enforce [semantic versioning](https://semver.org/){target="_blank" rel="nofollow"} pull request titles, as these will be used for +[CHANGELOG](https://github.com/aws-powertools/powertools-lambda-java/blob/main/CHANGELOG.md){target="_blank"} +and [Release notes](https://github.com/aws-powertools/powertools-lambda-java/releases) - make sure they communicate their +intent at the human level. + +For issues linked to a PR, make sure `status/staged-next-release` label is applied to them when merging. +[Upon release](#releasing-a-new-version), these issues will be notified which release version contains their change. + +See [Common scenarios](#common-scenarios) section for additional guidance. + +### Triage New Issues + +Manage [labels](#labels), review issues regularly, and create new labels as needed by the project. Remove `triage` +label when you're able to confirm the validity of a request, a bug can be reproduced, etc. +Give priority to the original author for implementation, unless it is a sensitive task that is best handled by maintainers. + +Make sure issues are assigned to our [board of activities](https://github.com/orgs/aws-powertools/projects/4). + +Use our [labels](#labels) to signal good first issues to new community members, and to set expectation that this might +need additional feedback from the author, other customers, experienced community members and/or maintainers. + +Be aware of [casual contributors](https://opensource.com/article/17/10/managing-casual-contributors){target="_blank" rel="nofollow"} and recurring contributors. +Provide the experience and attention you wish you had if you were starting in open source. + +See [Common scenarios](#common-scenarios) section for additional guidance. + +### Triage Bug Reports + +Be familiar with [our definition of bug](#is-that-a-bug). If it's not a bug, you can close it or adjust its title and +labels - always communicate the reason accordingly. + +For bugs caused by upstream dependencies, replace `bug` with `bug-upstream` label. Ask the author whether they'd like to +raise the issue upstream or if they prefer us to do so. + +Assess the impact and make the call on whether we need an emergency release. Contact other [maintainers](#current-maintainers) when in doubt. + +See [Common scenarios](#common-scenarios) section for additional guidance. + +### Triage RFCs + +RFC is a collaborative process to help us get to the most optimal solution given the context. Their purpose is to ensure +everyone understands what this context is, their trade-offs, and alternative solutions that were part of the research +before implementation begins. + +Make sure you ask these questions in mind when reviewing: + +- Does it use our [RFC template](https://github.com/aws-powertools/powertools-lambda-java/issues/new?assignees=&labels=RFC%2C+triage&projects=&template=rfc.md&title=RFC%3A+)? +- Does it match our [Tenets](https://docs.powertools.aws.dev/lambda/java/latest/#tenets)? +- Does the proposal address the use case? If so, is the recommended usage explicit? +- Does it focus on the mechanics to solve the use case over fine-grained implementation details? +- Can anyone familiar with the code base implement it? +- If approved, are they interested in contributing? Do they need any guidance? +- Does this significantly increase the overall project maintenance? Do we have the skills to maintain it? +- If we can't take this use case, are there alternative projects we could recommend? Or does it call for a new project altogether? + +When necessary, be upfront that the time to review, approve, and implement a RFC can vary - +see [Contribution is stuck](#contribution-is-stuck). Some RFCs may be further updated after implementation, as certain areas become clearer. + +Some examples using our initial and new RFC templates: #92, #94, #95, #991, #1226 + +### Releasing a new version + +!!! note "The release process is currently a long, multi-step process. The team is in the process of automating at it." + +Firstly, make sure the commit history in the `main` branch **(1)** it's up to date, **(2)** commit messages are semantic, +and **(3)** commit messages have their respective area, for example `feat: <change>`, `chore: ...`). + +**Looks good, what's next?** + +Kickoff the `Prepare for maven central release` workflow with the intended rekease version. Once this has completed, it will +draft a Pull Request named something like `chore: Prep release 1.19.0`. the PR will **(1)** roll all of the POM versions +forward to the new release version and **(2)** release notes. + +Once this is done, check out the branch and clean up the release notes. These will be used both in the +[CHANGELOG.md file](https://github.com/aws-powertools/powertools-lambda-java/blob/main/CHANGELOG.md) +file and the [published github release information](https://github.com/aws-powertools/powertools-lambda-java/releases), +and you can use the existing release notes to see how changes are summarized. + +Next, commit and push, wait for the build to complete, and merge to main. Once main has built successfully (i.e. build, tests and end-to-end tests should pass), create a +tagged release from the Github UI, using the same release notes. + +Next, run the `Publish package to the Maven Central Repository` action to release the library. + +Finally, by hand, create a PR rolling all of the POMs onto the next snapshot version (e.g. `1.20.0-SNAPSHOT`). + + +### Add Continuous Integration Checks + +Add integration checks that validate pull requests and pushes to ease the burden on Pull Request reviewers. +Continuously revisit areas of improvement to reduce operational burden in all parties involved. + +### Negative Impact on the Project +<!-- markdownlint-disable-next-line MD013 --> +Actions that negatively impact the project will be handled by the admins, in coordination with other maintainers, +in balance with the urgency of the issue. Examples would be +[Code of Conduct](https://github.com/aws-powertools/powertools-lambda-java/blob/main/CODE_OF_CONDUCT.md){target="_blank"} +violations, deliberate harmful or malicious actions, spam, monopolization, and security risks. + +## Common scenarios + +These are recurring ambiguous situations that new and existing maintainers may encounter. They serve as guidance. +It is up to each maintainer to follow, adjust, or handle in a different manner as long as +[our conduct is consistent](#uphold-code-of-conduct) + +### Contribution is stuck + +A contribution can get stuck often due to lack of bandwidth and language barrier. For bandwidth issues, +check whether the author needs help. Make sure you get their permission before pushing code into their existing PR - +do not create a new PR unless strictly necessary. + +For language barrier and others, offer a 1:1 chat to get them unblocked. Often times, English might not be their +primary language, and writing in public might put them off, or come across not the way they intended to be. + +In other cases, you may have constrained capacity. Use `help wanted` label when you want to signal other maintainers +and external contributors that you could use a hand to move it forward. + +### Insufficient feedback or information + +When in doubt, use the `need-more-information` label to signal more context and feedback are necessary before proceeding. + +### Crediting contributions + +We credit all contributions as part of each [release note](https://github.com/aws-powertools/powertools-lambda-java/releases){target="_blank"} +as an automated process. If you find contributors are missing from the release note you're producing, please add them manually. + +### Is that a bug? + +A bug produces incorrect or unexpected results at runtime that differ from its intended behavior. +Bugs must be reproducible. They directly affect customers experience at runtime despite following its recommended usage. + +Documentation snippets, use of internal components, or unadvertised functionalities are not considered bugs. + +### Mentoring contributions + +Always favor mentoring issue authors to contribute, unless they're not interested or the implementation is sensitive (_e.g., complexity, time to release, etc._). + +Make use of `help wanted` and `good first issue` to signal additional contributions the community can help. + +### Long running issues or PRs + +Try offering a 1:1 call in the attempt to get to a mutual understanding and clarify areas that maintainers could help. + +In the rare cases where both parties don't have the bandwidth or expertise to continue, it's best to use the `revisit-in-3-months` label. By then, see if it's possible to break the PR or issue in smaller chunks, and eventually close if there is no progress. diff --git a/docs/processes/versioning.md b/docs/processes/versioning.md new file mode 100644 index 000000000..d20269001 --- /dev/null +++ b/docs/processes/versioning.md @@ -0,0 +1,61 @@ +--- +title: Versioning and maintenance policy +description: Versioning and maintenance policy for Powertools for AWS Lambda (Python) +--- + +### Overview + +This document outlines the maintenance policy for Powertools for AWS Lambda and their underlying dependencies. AWS regularly provides Powertools for AWS Lambda with updates that may contain new features, enhancements, bug fixes, security patches, or documentation updates. Updates may also address changes with dependencies, language runtimes, and operating systems. Powertools for AWS Lambda is published to package managers (e.g. PyPi, NPM, Maven, NuGet), and are available as source code on GitHub. + +We recommend users to stay up-to-date with Powertools for AWS Lambda releases to keep up with the latest features, security updates, and underlying dependencies. Continued use of an unsupported Powertools for AWS Lambda version is not recommended and is done at the user’s discretion. + +!!! info "For brevity, we will interchangeably refer to Powertools for AWS Lambda as "SDK" _(Software Development Toolkit)_." + +### Versioning + +Powertools for AWS Lambda release versions are in the form of X.Y.Z where X represents the major version. Increasing the major version of an SDK indicates that this SDK underwent significant and substantial changes to support new idioms and patterns in the language. Major versions are introduced when public interfaces _(e.g. classes, methods, types, etc.)_, behaviors, or semantics have changed. Applications need to be updated in order for them to work with the newest SDK version. It is important to update major versions carefully and in accordance with the upgrade guidelines provided by AWS. + +### SDK major version lifecycle + +The lifecycle for major Powertools for AWS Lambda versions consists of 5 phases, which are outlined below. + +- **Developer Preview** (Phase 0) - During this phase, SDKs are not supported, should not be used in production environments, and are meant for early access and feedback purposes only. It is possible for future releases to introduce breaking changes. Once AWS identifies a release to be a stable product, it may mark it as a Release Candidate. Release Candidates are ready for GA release unless significant bugs emerge, and will receive full AWS support. +- **General Availability (GA)** (Phase 1) - During this phase, SDKs are fully supported. AWS will provide regular SDK releases that include support for new features, enhancements, as well as bug and security fixes. AWS will support the GA version of an SDK for _at least 24 months_, unless otherwise specified. +- **Maintenance Announcement** (Phase 2) - AWS will make a public announcement at least 6 months before an SDK enters maintenance mode. During this period, the SDK will continue to be fully supported. Typically, maintenance mode is announced at the same time as the next major version is transitioned to GA. +- **Maintenance** (Phase 3) - During the maintenance mode, AWS limits SDK releases to address critical bug fixes and security issues only. An SDK will not receive API updates for new or existing services, or be updated to support new regions. Maintenance mode has a _default duration of 6 months_, unless otherwise specified. +- **End-of-Support** (Phase 4) - When an SDK reaches end-of support, it will no longer receive updates or releases. Previously published releases will continue to be available via public package managers and the code will remain on GitHub. The GitHub repository may be archived. Use of an SDK which has reached end-of-support is done at the user’s discretion. We recommend users upgrade to the new major version. + +!!! note "Please note that the timelines shown below are illustrative and not binding" + +![Maintenance policy timelines](https://docs.aws.amazon.com/images/sdkref/latest/guide/images/maint-policy.png) + +### Dependency lifecycle + +Most AWS SDKs have underlying dependencies, such as language runtimes, AWS Lambda runtime, or third party libraries and frameworks. These dependencies are typically tied to the language community or the vendor who owns that particular component. Each community or vendor publishes their own end-of-support schedule for their product. + +The following terms are used to classify underlying third party dependencies: + +- [**AWS Lambda Runtime**](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html): Examples include `java17`, `nodejs20.x`, `python3.13`, etc. +- **Language Runtime**: Examples include Java 17, Python 3.13, NodeJS 20, .NET Core, etc. +- **Third party Library**: Examples include Jackson Project, AWS X-Ray SDK, AWS Encryption SDK, etc. + +Powertools for AWS Lambda follows the [AWS Lambda Runtime deprecation policy cycle](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtime-support-policy), when it comes to Language Runtime. This means we will stop supporting their respective deprecated Language Runtime _(e.g., `java8`)_ without increasing the major SDK version. + +!!! note "AWS reserves the right to stop support for an underlying dependency without increasing the major SDK version" + +### Communication methods + +Maintenance announcements are communicated in several ways: + +- A pinned GitHub Request For Comments (RFC) issue indicating the campaign for the next major version. The RFC will outline the path to end-of-support, specify campaign timelines, and upgrade guidance. +- AWS SDK documentation, such as API reference documentation, user guides, SDK product marketing pages, and GitHub readme(s) are updated to indicate the campaign timeline and provide guidance on upgrading affected applications. +- Deprecation warnings are added to the SDKs, outlining the path to end-of-support and linking to the upgrade guide. + +To see the list of available major versions of Powertools for AWS Lambda and where they are in their maintenance lifecycle, see the [version support matrix](#version-support-matrix). + +### Version support matrix + +| SDK | Major version | Current Phase | General Availability Date | Notes | +| -------------------------------- | ------------- | -------------------- | ------------------------- | ------------------------------------------------------------------------------------------------- | +| Powertools for AWS Lambda (Java) | 2.x | General Availability | 06/12/2025 | See [Release notes](https://github.com/aws-powertools/powertools-lambda-java/releases/tag/v2.0.0) | +| Powertools for AWS Lambda (Java) | 1.x | End-of-life | 11/04/2020 | See [announcement](https://github.com/aws-powertools/powertools-lambda-java/issues/1895) | diff --git a/docs/requirements.in b/docs/requirements.in new file mode 100644 index 000000000..e8acc7112 --- /dev/null +++ b/docs/requirements.in @@ -0,0 +1,3 @@ +mkdocs-git-revision-date-plugin==0.3.2 +mkdocs-macros-plugin==1.3.7 +mkdocs-llmstxt==0.2.0 diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..16529cf3b --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,296 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --generate-hashes --output-file=requirements.txt requirements.in +# +beautifulsoup4==4.13.3 \ + --hash=sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b \ + --hash=sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16 + # via + # markdownify + # mkdocs-llmstxt +click==8.1.8 \ + --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \ + --hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a + # via mkdocs +ghp-import==2.1.0 \ + --hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \ + --hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343 + # via mkdocs +gitdb==4.0.12 \ + --hash=sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571 \ + --hash=sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf + # via gitpython +gitpython==3.1.44 \ + --hash=sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110 \ + --hash=sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269 + # via mkdocs-git-revision-date-plugin +hjson==3.1.0 \ + --hash=sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75 \ + --hash=sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89 + # via + # mkdocs-macros-plugin + # super-collections +jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 + # via + # mkdocs + # mkdocs-git-revision-date-plugin + # mkdocs-macros-plugin +markdown==3.7 \ + --hash=sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2 \ + --hash=sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803 + # via mkdocs +markdown-it-py==3.0.0 \ + --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ + --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb + # via mdformat +markdownify==1.1.0 \ + --hash=sha256:32a5a08e9af02c8a6528942224c91b933b4bd2c7d078f9012943776fc313eeef \ + --hash=sha256:449c0bbbf1401c5112379619524f33b63490a8fa479456d41de9dc9e37560ebd + # via mkdocs-llmstxt +markupsafe==3.0.2 \ + --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ + --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ + --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \ + --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ + --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ + --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \ + --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ + --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \ + --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ + --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \ + --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \ + --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \ + --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \ + --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ + --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ + --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \ + --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ + --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ + --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ + --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \ + --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \ + --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ + --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \ + --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \ + --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \ + --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \ + --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \ + --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ + --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ + --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \ + --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \ + --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ + --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \ + --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \ + --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ + --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \ + --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \ + --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ + --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ + --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \ + --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ + --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ + --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ + --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \ + --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ + --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ + --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ + --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ + --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \ + --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ + --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \ + --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \ + --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ + --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ + --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ + --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \ + --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \ + --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ + --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ + --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ + --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 + # via + # jinja2 + # mkdocs +mdformat==0.7.22 \ + --hash=sha256:61122637c9e1d9be1329054f3fa216559f0d1f722b7919b060a8c2a4ae1850e5 \ + --hash=sha256:eef84fa8f233d3162734683c2a8a6222227a229b9206872e6139658d99acb1ea + # via mkdocs-llmstxt +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +mergedeep==1.3.4 \ + --hash=sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8 \ + --hash=sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 + # via + # mkdocs + # mkdocs-get-deps +mkdocs==1.6.1 \ + --hash=sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2 \ + --hash=sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e + # via + # mkdocs-git-revision-date-plugin + # mkdocs-macros-plugin +mkdocs-get-deps==0.2.0 \ + --hash=sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c \ + --hash=sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134 + # via mkdocs +mkdocs-git-revision-date-plugin==0.3.2 \ + --hash=sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef + # via -r requirements.in +mkdocs-llmstxt==0.2.0 \ + --hash=sha256:104f10b8101167d6baf7761942b4743869be3d8f8a8d909f4e9e0b63307f709e \ + --hash=sha256:907de892e0c8be74002e8b4d553820c2b5bbcf03cc303b95c8bca48fb49c1a29 + # via -r requirements.in +mkdocs-macros-plugin==1.3.7 \ + --hash=sha256:02432033a5b77fb247d6ec7924e72fc4ceec264165b1644ab8d0dc159c22ce59 \ + --hash=sha256:17c7fd1a49b94defcdb502fd453d17a1e730f8836523379d21292eb2be4cb523 + # via -r requirements.in +packaging==24.2 \ + --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ + --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f + # via + # mkdocs + # mkdocs-macros-plugin +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via + # mkdocs + # mkdocs-macros-plugin +platformdirs==4.3.7 \ + --hash=sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94 \ + --hash=sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351 + # via mkdocs-get-deps +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + # via + # ghp-import + # mkdocs-macros-plugin +pyyaml==6.0.2 \ + --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ + --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ + --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \ + --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \ + --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ + --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \ + --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ + --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \ + --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ + --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \ + --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \ + --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \ + --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \ + --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \ + --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \ + --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \ + --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ + --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \ + --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ + --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \ + --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \ + --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \ + --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \ + --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ + --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ + --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \ + --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \ + --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ + --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ + --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \ + --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ + --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \ + --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ + --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \ + --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \ + --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ + --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \ + --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \ + --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \ + --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ + --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ + --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \ + --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ + --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ + --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \ + --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \ + --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \ + --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \ + --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \ + --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ + --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ + --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ + --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 + # via + # mkdocs + # mkdocs-get-deps + # mkdocs-macros-plugin + # pyyaml-env-tag +pyyaml-env-tag==0.1 \ + --hash=sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb \ + --hash=sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069 + # via mkdocs +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via + # markdownify + # python-dateutil +smmap==5.0.2 \ + --hash=sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5 \ + --hash=sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e + # via gitdb +soupsieve==2.6 \ + --hash=sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb \ + --hash=sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9 + # via beautifulsoup4 +super-collections==0.5.3 \ + --hash=sha256:907d35b25dc4070910e8254bf2f5c928348af1cf8a1f1e8259e06c666e902cff \ + --hash=sha256:94c1ec96c0a0d5e8e7d389ed8cde6882ac246940507c5e6b86e91945c2968d46 + # via mkdocs-macros-plugin +termcolor==3.0.1 \ + --hash=sha256:a6abd5c6e1284cea2934443ba806e70e5ec8fd2449021be55c280f8a3731b611 \ + --hash=sha256:da1ed4ec8a5dc5b2e17476d859febdb3cccb612be1c36e64511a6f2485c10c69 + # via mkdocs-macros-plugin +typing-extensions==4.13.2 \ + --hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \ + --hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef + # via beautifulsoup4 +watchdog==6.0.0 \ + --hash=sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a \ + --hash=sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2 \ + --hash=sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f \ + --hash=sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c \ + --hash=sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c \ + --hash=sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c \ + --hash=sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0 \ + --hash=sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13 \ + --hash=sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134 \ + --hash=sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa \ + --hash=sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e \ + --hash=sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379 \ + --hash=sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a \ + --hash=sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11 \ + --hash=sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282 \ + --hash=sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b \ + --hash=sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f \ + --hash=sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c \ + --hash=sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112 \ + --hash=sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948 \ + --hash=sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881 \ + --hash=sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860 \ + --hash=sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3 \ + --hash=sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680 \ + --hash=sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26 \ + --hash=sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26 \ + --hash=sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e \ + --hash=sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8 \ + --hash=sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c \ + --hash=sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2 + # via mkdocs diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 000000000..4ef1ef06e --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,142 @@ +--- +title: Roadmap +description: Public roadmap for Powertools for AWS Lambda (Java) +--- + +## Overview + +Our public roadmap outlines the high level direction we are working towards. We update this document when our priorities change: security and stability are our top priority. + +### Key areas + +Security and operational excellence take precedence above all else. This means bug fixing, stability, customer's support, and internal compliance may delay one or more key areas below. + +!!! info "We may choose to re-prioritize or defer items based on customer feedback, security, and operational impacts, and business value." + +#### Release Security (p0) + +Our top priority is to establish the processes and infrastructure needed for a fully automated and secure end-to-end release process of new versions to Maven Central. + +- [x] [Implement GitHub workflows](https://github.com/aws-powertools/powertools-lambda-java/issues/1231){target="\_blank"} and create infrastructure to release to Maven Central +- [x] [Implement end-to-end tests](https://github.com/aws-powertools/powertools-lambda-java/issues/1815){target="\_blank"} +- [x] Implement [OpenSSF Scorecard](https://openssf.org/projects/scorecard/){target="\_blank"} + +#### `v2` Release: Consistency and Ecosystem (p1) + +As part of a new major version `v2` release, we prioritize the Java project's consistency of core utilities (Logging, Metrics, Tracing) with the other runtimes (Python, TypeScript, .NET). Additionally, we will focus on integrating the library with popular technologies and frameworks from the Java and AWS ecosystem. Particularly, we aim at leveraging new techniques to allow customers to reduce Lambda cold-start time. The `v2` release will also drop support for Java 8 moving to Java 11 as the baseline. + +##### Core Utilities + +- [x] [Review public interfaces and reduce public API surface area](https://github.com/aws-powertools/powertools-lambda-java/issues/1283){target="\_blank"} +- [x] [Release Logging `v2` module](https://github.com/aws-powertools/powertools-lambda-java/issues/965){target="\_blank"} allowing customers to choose the logging framework and adding support for logging deeply nested objects as JSON +- [x] [Support high resolution metrics](https://github.com/aws-powertools/powertools-lambda-java/issues/1041){target="\_blank"} +- [x] [Improve modularity of metrics module](https://github.com/aws-powertools/powertools-lambda-java/issues/1848){target="\_blank"} to remove coupling with EMF library and enable future support for additional metrics providers / backends + +##### Ecosystem + +- [x] [Add GraalVM support for core utilities](https://github.com/aws-powertools/powertools-lambda-java/issues/764){target="\_blank"} +- [ ] [Implement priming using CRaC to improve AWS Snapstart support](https://github.com/aws-powertools/powertools-lambda-java/issues/1588){target="\_blank"} +- [ ] [Evaluate integration with popular Java frameworks such as Micronaut, Spring Cloud Function, or Quarkus](https://github.com/aws-powertools/powertools-lambda-java/issues/1701){target="\_blank"} + +##### Other + +- [x] [Validation module integration with HTTP requests](https://github.com/aws-powertools/powertools-lambda-java/issues/1298){target="\_blank"} +- [x] [Support validation module from within the batch module](https://github.com/aws-powertools/powertools-lambda-java/issues/1496){target="\_blank"} +- [x] [Add support for parallel processing in Batch Processing utility](https://github.com/aws-powertools/powertools-lambda-java/issues/1540){target="\_blank"} +- [ ] [Documentation: Review and improve documentation to be consistent with other runtimes](https://github.com/aws-powertools/powertools-lambda-java/issues/1352){target="\_blank"} + +#### Feature Parity (p2) + +If priorities `p0` and `p1` are addressed, we will also focus on feature parity of non-core utilities. This allows customers to achieve better standardization of their development processes across different Powertools runtimes. + +- [ ] [Re-evaluate if there is a need for adding a lightweight customer Powertools event handler](https://github.com/aws-powertools/powertools-lambda-java/issues/1103){target="\_blank"} +- [ ] [Add comprehensive GraalVM support for all utilities](){target="\_blank"} +- [ ] [Add Feature Flags module](https://github.com/aws-powertools/powertools-lambda-java/issues/1086){target="\_blank"} +- [ ] [Add S3 Streaming module](https://github.com/aws-powertools/powertools-lambda-java/issues/1085){target="\_blank"} +- [ ] Add support for Data Masking during JSON serialization + +### Missing something? + +You can help us prioritize by [upvoting existing feature requests](https://github.com/aws-powertools/powertools-lambda-java/issues?q=is%3Aissue%20state%3Aopen%20label%3Aenhancement){target="\_blank"}, +leaving a comment on what use cases it could unblock for you, and by joining our discussions on Discord. + +[![Join our Discord](https://dcbadge.vercel.app/api/server/B8zZKbbyET)](https://discord.gg/B8zZKbbyET){target="\_blank"} + +### Roadmap status definition + +<center> +```mermaid +graph LR + Ideas --> Backlog --> Work["Working on it"] --> Merged["Coming soon"] --> Shipped +``` +<i>Visual representation</i> +</center> + +Within our [public board](https://github.com/orgs/aws-powertools/projects/4/){target="\_blank"}, you'll see the following values in the `Status` column: + +- **Ideas**. Incoming and existing feature requests that are not being actively considered yet. These will be reviewed + when bandwidth permits. +- **Backlog**. Accepted feature requests or enhancements that we want to work on. +- **Working on it**. Features or enhancements we're currently either researching or implementing it. +- **Coming soon**. Any feature, enhancement, or bug fixes that have been merged and are coming in the next release. +- **Shipped**. Features or enhancements that are now available in the most recent release. + +> Tasks or issues with empty `Status` will be categorized in upcoming review cycles. + +### Process + +<center> +```mermaid +graph LR + PFR[Feature request] --> Triage{Need RFC?} + Triage --> |Complex/major change or new utility?| RFC[Ask or write RFC] --> Approval{Approved?} + Triage --> |Minor feature or enhancement?| NoRFC[No RFC required] --> Approval + Approval --> |Yes| Backlog + Approval --> |No | Reject["Inform next steps"] + Backlog --> |Prioritized| Implementation + Backlog --> |Defer| WelcomeContributions["help-wanted label"] +``` +<i>Visual representation</i> +</center> + +Our end-to-end mechanism follows four major steps: + +- **Feature Request**. Ideas start with a [feature request](https://github.com/aws-powertools/powertools-lambda-java/issues/new?template=feature_request.md){target="\_blank"} to outline their use case at a high level. For complex use cases, maintainers might ask for/write a + RFC. + - Maintainers review requests based on [project tenets](index.md#tenets){target="\_blank"}, customers reaction (👍), + and use cases. +- **Request-for-comments (RFC)**. Design proposals use + our [RFC template](https://github.com/aws-powertools/powertools-lambda-java/issues/new?q=is%3Aissue+state%3Aopen+label%3Aenhancement&template=rfc.md){target="\_blank"} to describe its implementation, challenges, developer experience, dependencies, and alternative solutions. + - This helps refine the initial idea with community feedback before a decision is made. +- **Decision**. After carefully reviewing and discussing them, maintainers make a final decision on whether to start + implementation, defer or reject it, and update everyone with the next steps. +- **Implementation**. For approved features, maintainers give priority to the original authors for implementation unless + it is a sensitive task that is best handled by maintainers. + +!!! info "See [Maintainers](./processes/maintainers.md){target="\_blank"} document to understand how we triage issues and pull requests, labels and governance." + +### Disclaimer + +The Powertools for AWS Lambda (Java) team values feedback and guidance from its community of users, although final +decisions on inclusion into the project will be made by AWS. + +We determine the high-level direction for our open roadmap based on customer feedback and popularity (👍🏽 and comments), +security and operational impacts, and business value. Where features don’t meet our goals and longer-term strategy, we +will communicate that clearly and openly as quickly as possible with an explanation of why the decision was made. + +### FAQs + +**Q: Why did you build this?** + +A: We know that our customers are making decisions and plans based on what we are developing, and we want to provide our +customers the insights they need to plan. + +**Q: Why are there no dates on your roadmap?** + +A: Because job zero is security and operational stability, we can't provide specific target dates for features. The +roadmap is subject to change at any time, and roadmap issues in this repository do not guarantee a feature will be +launched as proposed. + +**Q: How can I provide feedback or ask for more information?** + +A: For existing features, you can directly comment on issues. For anything else, please open an issue. diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index de2f3b5f9..dc08ef51e 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -1,9 +1,11 @@ .md-grid { - max-width: 81vw + max-width: 90vw } .highlight .hll { - background-color: lavender + [data-md-color-scheme="default"] { + background-color: lavender; + } } .md-typeset table:not([class]) { diff --git a/docs/upgrade.md b/docs/upgrade.md new file mode 100644 index 000000000..c9662a3db --- /dev/null +++ b/docs/upgrade.md @@ -0,0 +1,458 @@ +--- +title: Upgrade guide +description: Guide to update between major Powertools for AWS Lambda (Java) versions +--- + +## End of support v1 + +<!-- prettier-ignore-start --> +!!! warning "End of support notice" + On December 12th, 2025, Powertools for AWS Lambda (Java) v1 reached end-of-life and will no longer receive updates or releases. If you are still using v1, we strongly recommend you to read our upgrade guide and update to the latest version. Refer to [our announcement](https://github.com/aws-powertools/powertools-lambda-java/issues/1895) for details. +<!-- prettier-ignore-end --> + +Given our commitment to all of our customers using Powertools for AWS Lambda (Java), we will keep [Maven Central](https://central.sonatype.com/search?q=powertools){target="\_blank"} `v1` releases and a `v1` documentation archive to prevent any disruption. + +## Migrate to v2 from v1 + +!!! info "We strongly encourage you to migrate to `v2`. Refer to our [versioning policy](./processes/versioning.md) to learn more about our version support process." + +We've made minimal breaking changes to make your transition to `v2` as smooth as possible. + +### Quick summary + +The following table shows a summary of the changes made in `v2` and whether code changes are necessary. Each change that requires a code change links to a section below explaining more details. + +| Area | Change | Code change required | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| **Logging** | The [logging module was re-designed](#redesigned-logging-utility) from scratch to support popular Java logging paradigms and libraries like `log4j2`, `logback`, and `slf4j`. | Yes | +| **Metrics** | [Changed public interface](#updated-metrics-utility-interface) to remove direct coupling with `aws-embedded-metrics-java`. | Yes | +| **Tracing** | [Removed deprecated `captureResponse` and `captureError` options](#deprecated-capture-mode-related-tracing-annotation-parameters) on `@Tracing` annotation. | Yes | +| **Idempotency** | The [`powertools-idempotency` module was split by provider](#idempotency-utility-split-into-sub-modules-by-provider) to improve modularity and reduce the deployment package size. | Yes | +| **Idempotency** | Updated `IdempotencyConfig` interface to support addition of response hooks. | No | +| **Parameters** | The [`powertools-parameters` module was split by provider](#parameters-utility-split-into-sub-modules-by-provider) to improve modularity and reduce the deployment package size. | Yes | +| **Batch Processing** | [Removed deprecated `powertools-sqs` module](#removed-powertools-sqs-module-in-favor-of-powertools-batch) in favor of the more generic [Batch Processing](./utilities/batch.md) utility. | Yes | +| **Batch Processing** | Updated Batch Processing `BatchMessageHandler` interface to add support for parallel processing. | No | +| **Validation** | The `@Validation` utility returns 4xx error codes instead of 5xx error codes when used with API Gateway now. | No | +| **Validation** | Validating batch event sources now adds failed events as partial batch failures and does not fail the whole batch anymore. | No | +| **Custom Resources** | [Removed deprecated `Response.failed()` and `Response.success()` methods](#custom-resources-updates-the-response-class). | Yes | +| **Custom Resources** | Changed interface of `Response` class to add an optional `reason` field. | No | +| **Dependencies** | Renamed `powertools-core` to `powertools-common`. This module should not be used as direct dependency and is listed here for completeness. | No | +| **Dependencies** | [Removed `org.aspectj.aspectjrt` as project dependency](#aspectj-runtime-not-included-by-default-anymore) in favor of consumers including the version they prefer. | Yes | +| **Language support** | Removed support for Java 8. The minimum required Java version is Java 11. | N/A | + +### First Steps + +Before you start, we suggest making a copy of your current working project or create a new branch with `git`. + +1. **Upgrade** Java to at least version 11. While version 11 is supported, we recommend using the [newest available LTS version](https://downloads.corretto.aws/#/downloads){target="\_blank"} of Java. +2. **Review** the following section to confirm if you need to make changes to your code. + +## Redesigned Logging Utility + +<!-- +- Add new logging module: https://github.com/aws-powertools/powertools-lambda-java/pull/1539 +- Add advanced features to new logging module: https://github.com/aws-powertools/powertools-lambda-java/pull/1435 +- Block reserved keys: https://github.com/aws-powertools/powertools-lambda-java/commit/374b38db3b91d421a14bb71b4df0194c72304efa +--> + +The logging utility was re-designed from scratch to integrate better with Java idiomatic conventions and to remove the hard dependency on `log4j` as logging implementation. The new logging utility now supports `slfj4` as logging interface and gives you the choice among `log4j2` and `logback` as logging implementations. Consider the following steps to migrate from the v1 logging utility to the v2 logging utility: + +**1. Remove `powertools-logging` dependency and replace it with your logging backend of choice** + +In order to support different logging implementations, dedicated logging modules were created for the different logging implementations. Remove `powertools-logging` as a dependency and replace it with either `powertools-logging-log4j` or `powertools-logging-logback`. + +```diff +<!-- BEFORE v2 --> +- <dependency> +- <groupId>software.amazon.lambda</groupId> +- <artifactId>powertools-logging</artifactId> +- <version>1.x.x</version> +- </dependency> + +<!-- AFTER v2 --> ++ <dependency> ++ <groupId>software.amazon.lambda</groupId> ++ <artifactId>powertools-logging-log4j</artifactId> ++ <version>2.x.x</version> ++ </dependency> +``` + +<!-- prettier-ignore-start --> +!!! info "The AspectJ configuration still needs to depend on `powertools-logging`" + We have only replaced the logging implementation dependency. The AspectJ configuration still needs to depend on `powertools-logging` which contains the main logic. + + ```xml + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + ``` +<!-- prettier-ignore-end --> + +**2. Update `log4j2.xml` including new `JsonTemplateLayout`** + +This step is only required if you are using log4j2 as your logging implementation. The deprecated `#!xml <LambdaJsonLayout/>` element was removed. Replace it with the log4j2 agnostic `#!xml <JsonTemplateLayout/>` element. + +```diff +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> +- <LambdaJsonLayout compact="true" eventEol="true"/> ++ <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + <Root level="info"> + <AppenderRef ref="JsonAppender"/> + </Root> + </Loggers> +</Configuration> +``` + +**3. Migrate all logging specific calls to SLF4J native primitives (recommended)** + +The new logging utility is designed to integrate seamlessly with Java SLF4J to allow customers adopt the Logging utility without large code refactorings. This improvement requires the migration of non-native SLF4J primitives from the v1 Logging utility. + +!!! info "While we recommend using SLF4J as a logging implementation independent facade, you can still use the log4j2 and logback interfaces directly." + +Consider the following code example which gives you hints on how to achieve the same functionality between v1 and v2 Logging: + +```diff +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; +// ... other imports + +public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + // BEFORE v2: Uses org.apache.logging.log4j.LogManager +- private static final Logger LOGGER = LogManager.getLogger(PaymentFunction.class); + // AFTER v2: Use org.slf4j.LoggerFactory ++ private static final Logger LOGGER = LoggerFactory.getLogger(PaymentFunction.class); + + @Logging + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + // ... + + // BEFORE v2: Uses LoggingUtils.appendKey to append custom global keys + // LoggingUtils was removed! +- LoggingUtils.appendKey("cardNumber", card.getId()); + // AFTER v2: Uses native SLF4J Mapped Diagnostic Context (MDC) ++ MDC.put("cardNumber", card.getId()); + + // Regular logging has not changed + LOGGER.info("My log message with argument."); + + // Adding custom keys on a specific log message + // BEFORE v2: No direct way, only supported via LoggingUtils.appendKey and LoggingUtils.removeKey + // AFTER v2: Extensive support for StructuredArguments ++ LOGGER.info("Collecting payment", StructuredArguments.entry("orderId", order.getId())); + // { "message": "Collecting payment", ..., "orderId": 123} + Map<String, String> customKeys = new HashMap<>(); + customKeys.put("paymentId", payment.getId()); + customKeys.put("amount", payment.getAmount); ++ LOGGER.info("Payment successful", StructuredArguments.entries(customKeys)); + // { "message": "Payment successful", ..., "paymentId": 123, "amount": 12.99} + } +} +``` + +!!! info "Make sure to learn more about the advanced structured argument serialization features in the [Logging v2 documentation](./core/logging.md/#custom-keys)." + +## Updated Metrics utility interface + +<!-- - Remove deprecated methods: https://github.com/aws-powertools/powertools-lambda-java/pull/1624/files#diff-0afede8005aa2baeba2770f66d611bf0e8ee3969205be27e803682a7f2d6520a --> +<!-- - Re-designed metrics module: https://github.com/aws-powertools/powertools-lambda-java/issues/1848 --> + +The Metrics utility was redesigned to be more modular and allow for the addition of new metrics providers in the future. The same EMF-based metrics logging still applies but will be called via an updated public interface. Consider the following list to understand some of changes: + +- `#!java @Metrics` was renamed to `#!java @FlushMetrics` +- `#!java MetricsLogger.metricsLogger()` was renamed to `#!java MetricsFactory.getMetricsInstance()` +- `put*` calls such as `#!java putMetric()` where replaced with `add*` nomenclature such as `#!java addMetric()` +- All direct imports from `software.amazon.cloudwatchlogs.emf` need to be replaced with Powertools counterparts from `software.amazon.lambda.powertools.metrics` (see example below) +- The `withSingleMetric` and `withMetricsLogger` methods were removed in favor of `#!java metrics.flushSingleMetric()` +- It is no longer valid to skip declaration of a namespace. If no namespace is provided, an exception will be raised instead of using the default `aws-embedded-metrics` namespace. + +The following example shows a common Lambda handler using the Metrics utility and required refactorings. + +```diff +// Metrics is not a decorator anymore but the replacement for the `MetricsLogger` Singleton +import software.amazon.lambda.powertools.metrics.Metrics; ++ import software.amazon.lambda.powertools.metrics.FlushMetrics; +- import software.amazon.lambda.powertools.metrics.MetricsUtils; ++ import software.amazon.lambda.powertools.metrics.MetricsFactory; +- import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; +- import software.amazon.cloudwatchlogs.emf.model.DimensionSet; +- import software.amazon.cloudwatchlogs.emf.model.Unit; ++ import software.amazon.lambda.powertools.metrics.model.DimensionSet; ++ import software.amazon.lambda.powertools.metrics.model.MetricUnit; + +public class MetricsEnabledHandler implements RequestHandler<Object, Object> { + + // This is still a Singleton +- MetricsLogger metricsLogger = MetricsUtils.metricsLogger(); ++ Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Override +- @Metrics(namespace = "ExampleApplication", service = "booking") ++ @FlushMetrics(namespace = "ExampleApplication", service = "booking") + public Object handleRequest(Object input, Context context) { +- metricsLogger.putDimensions(DimensionSet.of("environment", "prod")); ++ metrics.addDimension(DimensionSet.of("environment", "prod")); + // New method overload for adding 2D dimensions more conveniently ++ metrics.addDimension("environment", "prod"); +- metricsLogger.putMetric("SuccessfulBooking", 1, Unit.COUNT); ++ metrics.addMetric("SuccessfulBooking", 1, MetricUnit.COUNT); + ... + } +} +``` + +Learn more about the redesigned Metrics utility in the [Metrics documentation](./core/metrics.md). + +## Deprecated capture mode related `@Tracing` annotation parameters + +<!-- - Remove deprecated methods: https://github.com/aws-powertools/powertools-lambda-java/pull/1624/files#diff-9b8ed4ca67e310d3ae90e61e2ceffbfec0402082b5a1f741d467f132e3370a21 --> + +The deprecated `captureError` and `captureResponse` arguments to the `@Tracing` annotation were removed in v2 and replaced by a new `captureMode` parameter. The parameter can be passed an Enum value of `CaptureMode`. + +You should update your code using the new `captureMode` argument: + +```diff +- @Tracing(captureError = false, captureResponse = false) ++ @Tracing(captureMode = CaptureMode.DISABLED) +public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + // ... +} +``` + +Learn more about valid `CaptureMode` values in the [Tracing documentation](./core/tracing.md). + +## Idempotency utility split into sub-modules by provider + +The Idempotency utility was split from the common `powertools-idempotency` package into individual packages for different persistence store providers. The main business logic is now in the `powertools-idempotency-core` package. + +You should now include the `powertools-idempotency-core` package as an AspectJ library and the provider package like `powertools-idempotency-dynamodb` as a regular dependency. + +```diff +<!-- BEFORE v2 --> +- <dependency> +- <groupId>software.amazon.lambda</groupId> +- <artifactId>powertools-idempotency</artifactId> +- <version>1.x.x</version> +- </dependency> +<!-- AFTER v2 --> +<!-- In dependencies section --> ++ <dependency> ++ <groupId>software.amazon.lambda</groupId> ++ <artifactId>powertools-idempotency-dynamodb</artifactId> ++ <version>2.x.x</version> ++ </dependency> +<!-- In AspectJ configuration section --> ++ <aspectLibrary> ++ <groupId>software.amazon.lambda</groupId> ++ <artifactId>powertools-idempotency-core</artifactId> ++ </aspectLibrary> +``` + +## Parameters utility split into sub-modules by provider + +Parameters utilities were split from the common `powertools-parameters` package into individual packages for different parameter providers. You should now include the specific parameters dependency for your provider. If you use multiple providers, you can include multiple packages. Each parameter provider needs to be included as a dependency and an AspectJ library to use annotations. + +This new structure reduces the bundle size of your deployment package. + +```diff +<!-- BEFORE v2 --> +<!-- In dependencies section --> +- <dependency> +- <groupId>software.amazon.lambda</groupId> +- <artifactId>powertools-parameters</artifactId> +- <version>1.x.x</version> +- </dependency> +<!-- In AspectJ configuration section --> +- <aspectLibrary> +- <groupId>software.amazon.lambda</groupId> +- <artifactId>powertools-parameters</artifactId> +- </aspectLibrary> +<!-- AFTER v2 --> +<!-- In dependencies section --> ++ <dependency> ++ <groupId>software.amazon.lambda</groupId> ++ <artifactId>powertools-parameters-secrets</artifactId> ++ <version>2.x.x</version> ++ </dependency> +<!-- ... your other providers --> +<!-- In AspectJ configuration section --> ++ <aspectLibrary> ++ <groupId>software.amazon.lambda</groupId> ++ <artifactId>powertools-parameters-secrets</artifactId> ++ </aspectLibrary> +<!-- ... your other providers --> +``` + +!!! info "Find the full list of supported providers in the [Parameters utility documentation](./utilities/parameters.md)." + +## Custom Resources updates the `Response` class + +<!-- - Remove deprecated methods: https://github.com/aws-powertools/powertools-lambda-java/pull/1624/files#diff-0afede8005aa2baeba2770f66d611bf0e8ee3969205be27e803682a7f2d6520a --> + +The `Response` class supporting CloudFormation Custom Resource implementations was updated to remove deprecated methods. + +The `#!java Response.failed()` and `#!java Response.success()` methods without parameters were removed and require the physical resource ID now. You should update your code to use: + +- `#!java Response.failed(String physicalResourceId)` +- `#!java Response.success(String physicalResourceId)` + +```diff +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler; +import software.amazon.lambda.powertools.cloudformation.Response; + +public class MyCustomResourceHandler extends AbstractCustomResourceHandler { + + // ... + + @Override + protected Response update(CloudFormationCustomResourceEvent updateEvent, Context context) { ++ String physicalResourceId = updateEvent.getPhysicalResourceId(); + UpdateResult updateResult = doUpdates(physicalResourceId); + if (updateResult.isSuccessful()) { +- return Response.success(); ++ return Response.success(physicalResourceId); + } else { +- return Response.failed(); ++ return Response.failed(physicalResourceId); + } + } + + // ... +} +``` + +## Improved integration of Validation utility with other utilities + +<!-- +- Partial failure batch validation: https://github.com/aws-powertools/powertools-lambda-java/pull/1621 +- Return 4xx errors codes with API GW: https://github.com/aws-powertools/powertools-lambda-java/pull/1489 +--> + +The Validation utility includes two updates that change the behavior of integration with other utilities and AWS services. + +**1. Updated HTTP status code when using `@Validation` with API Gateway** + +This does not require a code change in the Lambda function using the Validation utility but might impact how your calling application treats exceptions. Prior to `v2`, a 500 HTTP status code was returned when the validation did not pass. Consistent with the [HTTP specification](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status){target="\_blank"}, a 400 status code is returned now indicating a user error instead of a server error. + +Consider the following example: + +```java +import software.amazon.lambda.powertools.validation.Validation; + +public class MyFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + @Override + @Validation(inboundSchema = "classpath:/schema_in.json", outboundSchema = "classpath:/schema_out.json") + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + // ... + return something; + } +} +``` + +If the request validation fails, you can expect the following change in the HTTP response status code on the client-side: + +```sh +# BEFORE v2: 500 Internal Server Error +❯ curl -s -o /dev/null -w "%{http_code}" https://{API_ID}.execute-api.{REGION}.amazonaws.com/{STAGE}/{PATH} +500 +# AFTER v2: 400 Bad Request +❯ curl -s -o /dev/null -w "%{http_code}" https://{API_ID}.execute-api.{REGION}.amazonaws.com/{STAGE}/{PATH} +400 +``` + +**2. Integration with partial batch failures when using Batch utility** + +This does not require a code change but might affect the batch processing flow when using the Validation utility in combination with the Batch processing utility. + +Consider the following example: + +```java +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; + +public class SqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; + + public SqsBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithMessageHandler(this::processMessage, Product.class); + } + + @Override + @Validation(inboundSchema = "classpath:/schema_in.json", outboundSchema = "classpath:/schema_out.json") + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + return handler.processBatch(sqsEvent, context); + } + + private void processMessage(Product p, Context c) { + // Process the product + } +} +``` + +- **Prior to `v2`** this caused the whole batch to fail. +- **After `v2`** this will add only the failed events to the batch item failure list in the response and process the remaining messages. + +!!! info "Check if your workload can tolerate this behavior and make sure it is designed for idempotency when using partial batch item failures. We offer the [Idempotency](./utilities/idempotency.md) utility to simplify integration of idempotent behavior in your workloads." + +## AspectJ runtime not included by default anymore + +The AspectJ runtime is no longer included as a transitive dependency of Powertools. For all utilities offering annotations using AspectJ compile-time weaving, you need to include the AspectJ runtime yourself now. This is also documented, with a complete example, in our [installation guide](./index.md). For Maven projects, make sure to add the following dependency in your dependencies section: + +```diff ++ <dependency> ++ <groupId>org.aspectj</groupId> ++ <artifactId>aspectjrt</artifactId> ++ <version>1.9.22</version> ++ </dependency> +``` + +## Removed `powertools-sqs` module in favor of `powertools-batch` + +The archived documentation contains a migration guide for both large message handling using `powertools-sqs` and batch processing using `powertools-sqs`. The sections below explain the high-level steps for your convenience. + +### Migrating SQS Batch processing (`@SqsBatch`) + +The [batch processing library](./utilities/batch.md) provides a way to process messages and gracefully handle partial failures for SQS, Kinesis Streams, and DynamoDB Streams batch sources. In comparison to the legacy SQS Batch library, it relies on [Lambda partial batch responses](https://docs.aws.amazon.com/lambda/latest/dg/services-sqs-errorhandling.html#services-sqs-batchfailurereporting){target="\_blank"}, which allows the library to provide a simpler, more reliable interface for processing batches. + +In order to get started, check out the new [processing messages from SQS](./utilities/batch.md/#processing-messages-from-sqs) documentation. In most cases, you will simply be able to retain your existing batch message handler function, and wrap it with the new batch processing interface. Unlike the `powertools-sqs` module, the new `powertools-batch` module uses _partial batch responses_ to communicate to Lambda which messages have been processed and must be removed from the queue. The return value of the handler's process function must be returned to Lambda. + +The new library also no longer requires the `SQS:DeleteMessage` action on the Lambda function's role policy, as Lambda +itself now manages removal of messages from the queue. + +<!-- prettier-ignore-start --> +!!! info "Some tuneables from `powertools-sqs` are no longer provided." + - **Non-retryable Exceptions** - there is no mechanism to indicate in a partial batch response that a particular message + should not be retried and instead moved to DLQ - a message either succeeds, or fails and is retried. A message + will be moved to the DLQ once the normal retry process has expired. + - **Suppress Exception** - The new batch processor does not throw an exception on failure of a handler. Instead, + its result must be returned by your code from your message handler to Lambda, so that Lambda can manage + the completed messages and retry behaviour. +<!-- prettier-ignore-end --> + +### Migrating SQS Large message handling (`@SqsLargeMessage`) + +- Replace the dependency in Maven / Gradle: `powertools-sqs` ==> `powertools-large-messages` +- Replace the annotation: `@SqsLargeMessage` ==> `@LargeMessage` (the new module handles both SQS and SNS) +- Move the annotation away from the Lambda `handleRequest` method and put it on a method with `SQSEvent.SQSMessage` or `SNSEvent.SNSRecord` as first parameter. +- The annotation now handles a single message, contrary to the previous version that was handling the complete batch. This gives more control, especially when dealing with partial failures with SQS (see the batch module). +- The new module only provides an annotation: an equivalent to the `SqsUtils` class is not available anymore in this new version. diff --git a/docs/usage-patterns.md b/docs/usage-patterns.md new file mode 100644 index 000000000..e66538937 --- /dev/null +++ b/docs/usage-patterns.md @@ -0,0 +1,183 @@ +--- +title: Usage patterns +description: Getting to know the Powertools for AWS Lambda toolkit +--- + +<!-- markdownlint-disable MD043 --> + +Powertools for AWS Lambda (Java) is a collection of utilities designed to help you build serverless applications on AWS. + +The toolkit is modular, so you can pick and choose the utilities you need for your application, but also combine them for a complete solution for your serverless applications. + +## Patterns + +Many of the utilities provided can be used with different patterns, depending on your preferences and the structure of your code. + +### AspectJ Annotation + +If you prefer using annotations to apply cross-cutting concerns to your Lambda handlers, the AspectJ annotation pattern is a good fit. This approach lets you decorate methods with Powertools utilities using annotations, applying their functionality with minimal code changes. + +This pattern works well when you want to keep your business logic clean and separate concerns using aspect-oriented programming. + +<!-- prettier-ignore --> +!!! note + This approach requires configuring AspectJ compile-time weaving in your build tool (Maven or Gradle). See the [installation guide](./index.md#install) for setup instructions. + +=== "Logging" + + ```java + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.logging.CorrelationIdPaths; + import software.amazon.lambda.powertools.logging.Logging; + + public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger log = LoggerFactory.getLogger(App.class); + + @Logging(logEvent = true, correlationIdPath = CorrelationIdPaths.API_GATEWAY_REST) + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + log.info("Processing request"); + return new APIGatewayProxyResponseEvent().withStatusCode(200).withBody("Success"); + } + } + ``` + +=== "Metrics" + + ```java + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + import software.amazon.lambda.powertools.metrics.FlushMetrics; + import software.amazon.lambda.powertools.metrics.Metrics; + import software.amazon.lambda.powertools.metrics.MetricsFactory; + import software.amazon.lambda.powertools.metrics.model.MetricUnit; + + public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @FlushMetrics(namespace = "ServerlessApp", service = "payment") + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + metrics.addMetric("SuccessfulBooking", 1, MetricUnit.COUNT); + return new APIGatewayProxyResponseEvent().withStatusCode(200).withBody("Success"); + } + } + ``` + +=== "Tracing" + + ```java + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + import software.amazon.lambda.powertools.tracing.Tracing; + import software.amazon.lambda.powertools.tracing.TracingUtils; + + public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + @Tracing + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + TracingUtils.putAnnotation("operation", "payment"); + return processPayment(); + } + + @Tracing + private APIGatewayProxyResponseEvent processPayment() { + return new APIGatewayProxyResponseEvent().withStatusCode(200).withBody("Success"); + } + } + ``` + +### Functional Approach + +If you prefer a more functional programming style or want to avoid AspectJ configuration, you can use the Powertools for AWS Lambda (Java) utilities directly in your code. This approach is more explicit and provides full control over how the utilities are applied. + +This pattern is ideal when you want to avoid AspectJ setup or prefer a more imperative style. It also eliminates the AspectJ runtime dependency, making your deployment package more lightweight. + +=== "Logging" + + ```java + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.logging.CorrelationIdPaths; + import software.amazon.lambda.powertools.logging.PowertoolsLogging; + + public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger log = LoggerFactory.getLogger(App.class); + + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + return PowertoolsLogging.withLogging( + context, + 0.7, + CorrelationIdPaths.API_GATEWAY_REST, + input, + () -> processRequest(input)); + } + + private APIGatewayProxyResponseEvent processRequest(APIGatewayProxyRequestEvent input) { + // do something with input + log.info("Processing request"); + return new APIGatewayProxyResponseEvent().withStatusCode(200).withBody("Success"); + } + } + ``` + +=== "Metrics" + + ```java + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + import software.amazon.lambda.powertools.metrics.Metrics; + import software.amazon.lambda.powertools.metrics.MetricsFactory; + import software.amazon.lambda.powertools.metrics.model.MetricUnit; + + public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + try { + metrics.addMetric("SuccessfulBooking", 1, MetricUnit.COUNT); + return new APIGatewayProxyResponseEvent().withStatusCode(200).withBody("Success"); + } finally { + metrics.flush(); + } + } + } + ``` + +=== "Tracing" + + ```java + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + import software.amazon.lambda.powertools.tracing.TracingUtils; + + public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + TracingUtils.withSubsegment("processPayment", subsegment -> { + subsegment.putAnnotation("operation", "payment"); + // Business logic here + }); + return new APIGatewayProxyResponseEvent().withStatusCode(200).withBody("Success"); + } + } + ``` + +<!-- prettier-ignore --> +!!! note + The functional approach is available for all utilities. Further examples and detailed usage can be found in the individual documentation pages for each utility. diff --git a/docs/utilities/batch.md b/docs/utilities/batch.md index ebb98b08b..b535a90f6 100644 --- a/docs/utilities/batch.md +++ b/docs/utilities/batch.md @@ -1,381 +1,712 @@ --- -title: SQS Batch Processing +title: Batch Processing description: Utility --- -The SQS batch processing utility provides a way to handle partial failures when processing batches of messages from SQS. +The batch processing utility provides a way to handle partial failures when processing batches of messages from SQS queues, +SQS FIFO queues, Kinesis Streams, or DynamoDB Streams. + +```mermaid +stateDiagram-v2 + direction LR + BatchSource: Amazon SQS <br/><br/> Amazon Kinesis Data Streams <br/><br/> Amazon DynamoDB Streams <br/><br/> + LambdaInit: Lambda invocation + BatchProcessor: Batch Processor + RecordHandler: Record Handler function + YourLogic: Your logic to process each batch item + LambdaResponse: Lambda response + BatchSource --> LambdaInit + LambdaInit --> BatchProcessor + BatchProcessor --> RecordHandler + state BatchProcessor { + [*] --> RecordHandler: Your function + RecordHandler --> YourLogic + } + RecordHandler --> BatchProcessor: Collect results + BatchProcessor --> LambdaResponse: Report items that failed processing +``` **Key Features** -* Prevent successfully processed messages from being returned to SQS -* A simple interface for individually processing messages from a batch +* Reports batch item failures to reduce number of retries for a record upon errors +* Simple interface to process each batch record +* Parallel processing of batches +* Integrates with Java Events library and the deserialization module +* Build your own batch processor by extending primitives **Background** -When using SQS as a Lambda event source mapping, Lambda functions can be triggered with a batch of messages from SQS. -If your function fails to process any message from the batch, the entire batch returns to your SQS queue, and your -Lambda function will be triggered with the same batch again. With this utility, messages within a batch will be handled individually - only messages that were not successfully processed -are returned to the queue. +When using SQS, Kinesis Data Streams, or DynamoDB Streams as a Lambda event source, your Lambda functions are +triggered with a batch of messages. +If your function fails to process any message from the batch, the entire batch returns to your queue or stream. +This same batch is then retried until either condition happens first: +**a)** your Lambda function returns a successful response, +**b)** record reaches maximum retry attempts, or +**c)** records expire. + +```mermaid +journey + section Conditions + Successful response: 5: Success + Maximum retries: 3: Failure + Records expired: 1: Failure +``` + +This behavior changes when you enable Report Batch Item Failures feature in your Lambda function event source configuration: + +<!-- markdownlint-disable MD013 --> +* [**SQS queues**](#sqs-standard). Only messages reported as failure will return to the queue for a retry, while successful ones will be deleted. +* [**Kinesis data streams**](#kinesis-and-dynamodb-streams) and [**DynamoDB streams**](#kinesis-and-dynamodb-streams). +Single reported failure will use its sequence number as the stream checkpoint. +Multiple reported failures will use the lowest sequence number as checkpoint. + +With this utility, batch records are processed individually – only messages that failed to be processed +return to the queue or stream for a further retry. You simply build a `BatchProcessor` in your handler, +and return its response from the handler's `processMessage` implementation. Exceptions are handled +internally and an appropriate partial response for the message source is returned to Lambda for you. !!! warning - While this utility lowers the chance of processing messages more than once, it is not guaranteed. We recommend implementing processing logic in an idempotent manner wherever possible. + While this utility lowers the chance of processing messages more than once, it is still not guaranteed. + We recommend implementing processing logic in an idempotent manner wherever possible, for instance, + by taking advantage of [the idempotency module](idempotency.md). More details on how Lambda works with SQS can be found in the [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html) ## Install -To install this utility, add the following dependency to your project. +We simply add `powertools-batch` to our build dependencies. Note - if you are using other Powertools +modules that require code-weaving, such as `powertools-core`, you will need to configure that also. === "Maven" - ```xml hl_lines="3 4 5 6 7 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36" + + ```xml <dependencies> ... <dependency> <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-sqs</artifactId> + <artifactId>powertools-batch</artifactId> <version>{{ powertools.version }}</version> </dependency> ... </dependencies> - <!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project --> - <build> - <plugins> - ... - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>aspectj-maven-plugin</artifactId> - <version>1.14.0</version> - <configuration> - <source>1.8</source> - <target>1.8</target> - <complianceLevel>1.8</complianceLevel> - <aspectLibraries> - <aspectLibrary> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-sqs</artifactId> - </aspectLibrary> - </aspectLibraries> - </configuration> - <executions> - <execution> - <goals> - <goal>compile</goal> - </goals> - </execution> - </executions> - </plugin> - ... - </plugins> - </build> ``` === "Gradle" ```groovy - plugins{ - id 'java' - id 'io.freefair.aspectj.post-compile-weaving' version '6.3.0' - } - - repositories { - mavenCentral() - } - - dependencies { - ... - aspect 'software.amazon.lambda:powertools-sqs:{{ powertools.version }}' - } + + repositories { + mavenCentral() + } + + dependencies { + implementation 'software.amazon.lambda:powertools-batch:{{ powertools.version }}' + } ``` +## Getting Started -## IAM Permissions +For this feature to work, you need to **(1)** configure your Lambda function event source to use `ReportBatchItemFailures`, +and **(2)** return a specific response to report which records failed to be processed. -This utility requires additional permissions to work as expected. Lambda functions using this utility require the `sqs:DeleteMessageBatch` permission. +You can use your preferred deployment framework to set the correct configuration while this utility, +while the `powertools-batch` module handles generating the response, which simply needs to be returned as the result of +your Lambda handler. -If you are also using [nonRetryableExceptions](#move-non-retryable-messages-to-a-dead-letter-queue) attribute, utility will need additional permission of `sqs:GetQueueAttributes` on source SQS. -It also needs `sqs:SendMessage` and `sqs:SendMessageBatch` on configured dead letter queue. +A complete [Serverless Application Model](https://aws.amazon.com/serverless/sam/) example can be found [here](https://github.com/aws-powertools/powertools-lambda-java/tree/main/examples/powertools-examples-batch) covering all the batch sources. -Refer [example project](https://github.com/aws-samples/aws-lambda-powertools-examples/blob/main/java/SqsBatchProcessing/template.yaml#L67) for policy details example. +For more information on configuring `ReportBatchItemFailures`, see the details for [SQS](https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting), [Kinesis](https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-batchfailurereporting), and [DynamoDB Streams](https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-batchfailurereporting). -## Processing messages from SQS -You can use either **[SqsBatch annotation](#sqsbatch-annotation)**, or **[SqsUtils Utility API](#sqsutils-utility-api)** as a fluent API. +!!! note "You do not need any additional IAM permissions to use this utility, except for what each event source requires." -Both have nearly the same behaviour when it comes to processing messages from the batch: +### Processing messages from SQS -* **Entire batch has been successfully processed**, where your Lambda handler returned successfully, we will let SQS delete the batch to optimize your cost -* **Entire Batch has been partially processed successfully**, where exceptions were raised within your `SqsMessageHandler` interface implementation, we will: - - **1)** Delete successfully processed messages from the queue by directly calling `sqs:DeleteMessageBatch` - - **2)** if, non retryable exceptions occur, messages resulting in configured exceptions during processing will be immediately moved to the dead letter queue associated to the source SQS queue or deleted from the source SQS queue if `deleteNonRetryableMessageFromQueue` is set to `true`. - - **3)** Raise `SQSBatchProcessingException` to ensure failed messages return to your SQS queue - -The only difference is that **SqsUtils Utility API** will give you access to return from the processed messages if you need. Exception `SQSBatchProcessingException` thrown from the -utility will have access to both successful and failed messaged along with failure exceptions. - -## Functional Interface SqsMessageHandler - -Both [annotation](#sqsbatch-annotation) and [SqsUtils Utility API](#sqsutils-utility-api) requires an implementation of functional interface `SqsMessageHandler`. - -This implementation is responsible for processing each individual message from the batch, and to raise an exception if unable to process any of the messages sent. - -**Any non-exception/successful return from your record handler function** will instruct utility to queue up each individual message for deletion. - -### SqsBatch annotation - -When using this annotation, you need provide a class implementation of `SqsMessageHandler` that will process individual messages from the batch - It should raise an exception if it is unable to process the record. - -All records in the batch will be passed to this handler for processing, even if exceptions are thrown - Here's the behaviour after completing the batch: - -* **Any successfully processed messages**, we will delete them from the queue via `sqs:DeleteMessageBatch`. -* **if, nonRetryableExceptions attribute is used**, messages resulting in configured exceptions during processing will be immediately moved to the dead letter queue associated to the source SQS queue or deleted from the source SQS queue if `deleteNonRetryableMessageFromQueue` is set to `true`. -* **Any unprocessed messages detected**, we will raise `SQSBatchProcessingException` to ensure failed messages return to your SQS queue. +=== "SQSBatchHandler" + + ```java hl_lines="10 13-15 20 25" + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; + import com.amazonaws.services.lambda.runtime.events.SQSEvent; + import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; + import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; + + public class SqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { -!!! warning - You will not have access to the **processed messages** within the Lambda Handler - all processing logic will and should be performed by the implemented `#!java SqsMessageHandler#process()` function. + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; + + public SqsBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithMessageHandler(this::processMessage, Product.class); + } + + @Override + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + return handler.processBatch(sqsEvent, context); + } -=== "AppSqsEvent.java" + private void processMessage(Product p, Context c) { + // Process the product + } + } + ``` - ```java hl_lines="7" - import software.amazon.lambda.powertools.sqs.SqsBatch; - import software.amazon.lambda.powertools.sqs.SqsMessageHandler; - import software.amazon.lambda.powertools.sqs.SqsUtils; +=== "SQS Product" - public class AppSqsEvent implements RequestHandler<SQSEvent, String> { - @Override - @SqsBatch(SampleMessageHandler.class) - public String handleRequest(SQSEvent input, Context context) { - return "{\"statusCode\": 200}"; + ```java + public class Product { + private long id; + + private String name; + + private double price; + + public Product() { } - public class SampleMessageHandler implements SqsMessageHandler<Object> { + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } - @Override - public String process(SQSMessage message) { - // This will be called for each individual message from a batch - // It should raise an exception if the message was not processed successfully - String returnVal = doSomething(message.getBody()); - return returnVal; - } + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; } } + ``` + +=== "SQS Example Event" + + ```json + { + "Records": [ + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1234,\n \"name\": \"product\",\n \"price\": 42\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "e9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 12345,\n \"name\": \"product5\",\n \"price\": 45\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }] + } ``` -=== "AppSqsEventWithNonRetryableExceptions.java" +### Processing messages from Kinesis Streams - ```java hl_lines="7 21" - import software.amazon.lambda.powertools.sqs.SqsBatch; - import software.amazon.lambda.powertools.sqs.SqsMessageHandler; - import software.amazon.lambda.powertools.sqs.SqsUtils; +=== "KinesisBatchHandler" + + ```java hl_lines="10 13-15 20 24" + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import com.amazonaws.services.lambda.runtime.events.KinesisEvent; + import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; + import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; + import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; + + public class KinesisBatchHandler implements RequestHandler<KinesisEvent, StreamsEventResponse> { + + private final BatchMessageHandler<KinesisEvent, StreamsEventResponse> handler; + + public KinesisBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withKinesisBatchHandler() + .buildWithMessageHandler(this::processMessage, Product.class); + } - public class AppSqsEvent implements RequestHandler<SQSEvent, String> { @Override - @SqsBatch(value = SampleMessageHandler.class, nonRetryableExceptions = {IllegalArgumentException.class}) - public String handleRequest(SQSEvent input, Context context) { - return "{\"statusCode\": 200}"; + public StreamsEventResponse handleRequest(KinesisEvent kinesisEvent, Context context) { + return handler.processBatch(kinesisEvent, context); + } + + private void processMessage(Product p, Context c) { + // process the product + } + } + ``` + +=== "Kinesis Product" + + ```java + public class Product { + private long id; + + private String name; + + private double price; + + public Product() { + } + + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; } - public class SampleMessageHandler implements SqsMessageHandler<Object> { + public long getId() { + return id; + } - @Override - public String process(SQSMessage message) { - // This will be called for each individual message from a batch - // It should raise an exception if the message was not processed successfully - String returnVal = doSomething(message.getBody()); + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + } + ``` + +=== "Kinesis Example Event" + + ```json + { + "Records": [ + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ==", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200962", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + } + ] + } + ``` +### Processing messages from DynamoDB Streams - if(/**Business validation failure**/) { - throw new IllegalArgumentException("Failed business validation. No point of retrying. Move me to DLQ." + message.getMessageId()); - } +=== "DynamoDBStreamBatchHandler" + + ```java hl_lines="10 13-15 20 24" + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import com.amazonaws.services.lambda.runtime.events.DynamodbEvent; + import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; + import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; + import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; + + public class DynamoDBStreamBatchHandler implements RequestHandler<DynamodbEvent, StreamsEventResponse> { + + private final BatchMessageHandler<DynamodbEvent, StreamsEventResponse> handler; + + public DynamoDBStreamBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withDynamoDbBatchHandler() + .buildWithRawMessageHandler(this::processMessage); + } + + @Override + public StreamsEventResponse handleRequest(DynamodbEvent ddbEvent, Context context) { + return handler.processBatch(ddbEvent, context); + } - return returnVal; - } + private void processMessage(DynamodbEvent.DynamodbStreamRecord dynamodbStreamRecord, Context context) { + // Process the change record } } ``` +=== "DynamoDB Example Event" + + ```json + { + "Records": [ + { + "eventID": "c4ca4238a0b923820dcc509a6f75849b", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439091", + "SizeBytes": 26, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", + "userIdentity": { + "principalId": "dynamodb.amazonaws.com", + "type": "Service" + } + }, + { + "eventID": "c81e728d9d4c2f636f067f89cc14862c", + "eventName": "MODIFY", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439092", + "SizeBytes": 59, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + } + ] + } + ``` -### SqsUtils Utility API +## Parallel processing +You can choose to process batch items in parallel using the `BatchMessageHandler#processBatchInParallel()` +instead of `BatchMessageHandler#processBatch()`. Partial batch failure works the same way but items are processed +in parallel rather than sequentially. -If you require access to the result of processed messages, you can use this utility. The result from calling **`#!java SqsUtils#batchProcessor()`** on the context manager will be a list of all the return values -from your **`#!java SqsMessageHandler#process()`** function. +This feature is available for SQS, Kinesis and DynamoDB Streams but cannot be +used with SQS FIFO. In that case, an `UnsupportedOperationException` is thrown. -You can also use the utility in functional way by providing inline implementation of functional interface **`#!java SqsMessageHandler#process()`** +!!! warning + Note that parallel processing is not always better than sequential processing, + and you should benchmark your code to determine the best approach for your use case. +!!! info + To get more threads available (more vCPUs), you need to increase the amount of memory allocated to your Lambda function. + While it is possible to increase the number of threads using Java options or custom thread pools, + in most cases the defaults work well, and changing them is more likely to decrease performance + (see [here](https://www.baeldung.com/java-when-to-use-parallel-stream#fork-join-framework) + and [here](https://dzone.com/articles/be-aware-of-forkjoinpoolcommonpool)). + In situations where this may be useful, such as performing IO-bound work in parallel, make sure to measure before and after! -=== "Utility API" - - ```java hl_lines="4" - public class AppSqsEvent implements RequestHandler<SQSEvent, List<String>> { - @Override - public List<String> handleRequest(SQSEvent input, Context context) { - List<String> returnValues = SqsUtils.batchProcessor(input, SampleMessageHandler.class); +When using parallel processing with X-Ray tracing enabled, the Tracing utility automatically handles trace context propagation to worker threads. This ensures that subsegments created during parallel message processing appear under the correct parent segment in your X-Ray trace, maintaining proper trace hierarchy and visibility into your batch processing performance. + + +=== "Example with SQS" + + ```java hl_lines="13" + public class SqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; - return returnValues; + public SqsBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithMessageHandler(this::processMessage, Product.class); } - public class SampleMessageHandler implements SqsMessageHandler<String> { + @Override + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + return handler.processBatchInParallel(sqsEvent, context); + } - @Override - public String process(SQSMessage message) { - // This will be called for each individual message from a batch - // It should raise an exception if the message was not processed successfully - String returnVal = doSomething(message.getBody()); - return returnVal; - } + private void processMessage(Product p, Context c) { + // Process the product } } ``` +=== "Example with SQS (using custom executor)" -=== "Function implementation" + ```java hl_lines="4 10 15" + public class SqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { - ```java hl_lines="5 6 7 8 9 10" - public class AppSqsEvent implements RequestHandler<SQSEvent, List<String>> { + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; + private final ExecutorService executor; + + public SqsBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithMessageHandler(this::processMessage, Product.class); + executor = Executors.newFixedThreadPool(2); + } @Override - public List<String> handleRequest(SQSEvent input, Context context) { - List<String> returnValues = SqsUtils.batchProcessor(input, (message) -> { - // This will be called for each individual message from a batch - // It should raise an exception if the message was not processed successfully - String returnVal = doSomething(message.getBody()); - return returnVal; - }); + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + return handler.processBatchInParallel(sqsEvent, context, executor); + } - return returnValues; + private void processMessage(Product p, Context c) { + // Process the product } } ``` -## Passing custom SqsClient - -If you need to pass custom SqsClient such as region to the SDK, you can pass your own `SqsClient` to be used by utility either for -**[SqsBatch annotation](#sqsbatch-annotation)**, or **[SqsUtils Utility API](#sqsutils-utility-api)**. +=== "Example with X-Ray Tracing" -=== "App.java" + ```java hl_lines="12 17" + public class SqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { - ```java hl_lines="3 4" - public class AppSqsEvent implements RequestHandler<SQSEvent, List<String>> { - static { - SqsUtils.overrideSqsClient(SqsClient.builder() - .build()); + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; + + public SqsBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithMessageHandler(this::processMessage, Product.class); } @Override - public List<String> handleRequest(SQSEvent input, Context context) { - List<String> returnValues = SqsUtils.batchProcessor(input, SampleMessageHandler.class); - - return returnValues; + @Tracing + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + return handler.processBatchInParallel(sqsEvent, context); } - public class SampleMessageHandler implements SqsMessageHandler<String> { - - @Override - public String process(SQSMessage message) { - // This will be called for each individual message from a batch - // It should raise an exception if the message was not processed successfully - String returnVal = doSomething(message.getBody()); - return returnVal; - } + @Tracing // This will appear correctly under the handleRequest subsegment + private void processMessage(Product p, Context c) { + // Process the product - subsegments will appear under handleRequest } } ``` -## Suppressing exceptions +### Choosing the right concurrency model -If you want to disable the default behavior where `SQSBatchProcessingException` is raised if there are any exception, you can pass the `suppressException` boolean argument. +The `processBatchInParallel` method has two overloads with different concurrency characteristics: -=== "Within SqsBatch annotation" +#### Without custom executor (parallelStream) - ```java hl_lines="2" - @Override - @SqsBatch(value = SampleMessageHandler.class, suppressException = true) - public String handleRequest(SQSEvent input, Context context) { - return "{\"statusCode\": 200}"; - } - ``` +When you call `processBatchInParallel(event, context)` without providing an executor, the implementation uses Java's `parallelStream()` which leverages the common `ForkJoinPool`. -=== "Within SqsUtils Utility API" +**Best for: CPU-bound workloads** - ```java hl_lines="3" - @Override - public List<String> handleRequest(SQSEvent input, Context context) { - List<String> returnValues = SqsUtils.batchProcessor(input, true, SampleMessageHandler.class); - - return returnValues; - } - ``` +- Thread pool size matches available CPU cores +- Optimized for computational tasks (data transformation, calculations, parsing) +- Main thread participates in work-stealing +- Simple to use with no configuration needed -## Move non retryable messages to a dead letter queue +```java +// Good for CPU-intensive processing +return handler.processBatchInParallel(sqsEvent, context); +``` -If you want certain exceptions to be treated as permanent failures during batch processing, i.e. exceptions where the result of retrying will -always be a failure and want these can be immediately moved to the dead letter queue associated to the source SQS queue, you can use `SqsBatch#nonRetryableExceptions()` -to configure such exceptions. +#### With custom executor (CompletableFuture) -If you want such messages to be deleted instead, set `SqsBatch#deleteNonRetryableMessageFromQueue()` to `true`. By default, its value is `false`. +When you call `processBatchInParallel(event, context, executor)` with a custom executor, the implementation uses `CompletableFuture` which gives you full control over the thread pool. -Same capability is also provided by [SqsUtils Utility API](#sqsutils-utility-api). +**Best for: I/O-bound workloads** -!!! info - Make sure the lambda function has required permissions needed by utility. Refer [this section](#iam-permissions). +- You control thread pool size and characteristics +- Ideal for I/O operations (HTTP calls, database queries, S3 operations) +- Can use larger thread pools since threads spend time waiting, not computing +- Main thread only waits; worker threads do all processing -=== "SqsBatch annotation" +```java +// Good for I/O-intensive processing (API calls, DB queries, etc.) +ExecutorService executor = Executors.newFixedThreadPool(50); +return handler.processBatchInParallel(sqsEvent, context, executor); +``` - ```java hl_lines="7 21" - import software.amazon.lambda.powertools.sqs.SqsBatch; - import software.amazon.lambda.powertools.sqs.SqsMessageHandler; - import software.amazon.lambda.powertools.sqs.SqsUtils; - - public class AppSqsEvent implements RequestHandler<SQSEvent, String> { - @Override - @SqsBatch(value = SampleMessageHandler.class, nonRetryableExceptions = {IllegalArgumentException.class}) - public String handleRequest(SQSEvent input, Context context) { - return "{\"statusCode\": 200}"; - } - - public class SampleMessageHandler implements SqsMessageHandler<Object> { - - @Override - public String process(SQSMessage message) { - // This will be called for each individual message from a batch - // It should raise an exception if the message was not processed successfully - String returnVal = doSomething(message.getBody()); +**For Java 21+: Virtual Threads** - if(/**Business validation failure**/) { - throw new IllegalArgumentException("Failed business validation. No point of retrying. Move me to DLQ." + message.getMessageId()); - } +If you're using Java 21 or later, virtual threads are ideal for I/O-bound workloads: - return returnVal; - } - } +```java +ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); +return handler.processBatchInParallel(sqsEvent, context, executor); +``` + +Virtual threads are lightweight and can handle thousands of concurrent I/O operations efficiently without the overhead of platform threads. + +**Recommendation for typical Lambda SQS processing:** + +Most Lambda functions processing SQS messages perform I/O operations (calling APIs, querying databases, writing to S3). For these workloads, use the custom executor approach with a thread pool sized appropriately for your I/O operations or virtual threads for Java 21+. + + +## Handling Messages + +### Raw message and deserialized message handlers +You must provide either a raw message handler, or a deserialized message handler. The raw message handler receives +the envelope record type relevant for the particular event source - for instance, the SQS event source provides +[SQSMessage](https://javadoc.io/doc/com.amazonaws/aws-lambda-java-events/2.2.2/com/amazonaws/services/lambda/runtime/events/SQSEvent.html) +instances. The deserialized message handler extracts the body from this envelope, and deserializes it to a user-defined +type. Note that deserialized message handlers are not relevant for the DynamoDB provider, as the format of the inner +message is fixed by DynamoDB. + +In general, the deserialized message handler should be used unless you need access to information on the envelope. + +=== "Raw Message Handler" + + ```java hl_lines="4 7" + public void setup() { + BatchMessageHandler<SQSEvent, SQSBatchResponse> handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithRawMessageHandler(this::processRawMessage); + } + + private void processRawMessage(SQSEvent.SQSMessage sqsMessage) { + // Do something with the raw message } + ``` -=== "SqsBatch API" +=== "Deserialized Message Handler" - ```java hl_lines="9 23" - import software.amazon.lambda.powertools.sqs.SqsBatch; - import software.amazon.lambda.powertools.sqs.SqsMessageHandler; - import software.amazon.lambda.powertools.sqs.SqsUtils; - - public class AppSqsEvent implements RequestHandler<SQSEvent, String> { - @Override - public String handleRequest(SQSEvent input, Context context) { - - SqsUtils.batchProcessor(input, BatchProcessor.class, IllegalArgumentException.class); - - return "{\"statusCode\": 200}"; - } - - public class SampleMessageHandler implements SqsMessageHandler<Object> { + ```java hl_lines="4 7" + public void setup() { + BatchMessageHandler<SQSEvent, SQSBatchResponse> handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWitMessageHandler(this::processRawMessage, Product.class); + } + + private void processMessage(Product product) { + // Do something with the deserialized message + } - @Override - public String process(SQSMessage message) { - // This will be called for each individual message from a batch - // It should raise an exception if the message was not processed successfully - String returnVal = doSomething(message.getBody()); + ``` - if(/**Business validation failure**/) { - throw new IllegalArgumentException("Failed business validation. No point of retrying. Move me to DLQ." + message.getMessageId()); - } +### Success and failure handlers + +You can register a success or failure handler which will be invoked as each message is processed by the batch +module. This may be useful for reporting - for instance, writing metrics or logging failures. + +These handlers are optional. Batch failures are handled by the module regardless of whether or not you +provide a custom failure handler. + +Handlers can be provided when building the batch processor and are available for all event sources. +For instance for DynamoDB: + +```java hl_lines="3 8" +BatchMessageHandler<DynamodbEvent, StreamsEventResponse> handler = new BatchMessageHandlerBuilder() + .withDynamoDbBatchHandler() + .withSuccessHandler((m) -> { + // Success handler receives the raw message + LOGGER.info("Message with sequenceNumber {} was successfully processed", + m.getDynamodb().getSequenceNumber()); + }) + .withFailureHandler((m, e) -> { + // Failure handler receives the raw message and the exception thrown. + LOGGER.info("Message with sequenceNumber {} failed to be processed: {}" + , e.getDynamodb().getSequenceNumber(), e); + }) + .buildWithMessageHander(this::processMessage); +``` - return returnVal; - } +!!! info + If the success handler throws an exception, the item it is processing will be marked as failed by the + batch processor. + If the failure handler throws, the batch processing will continue; the item it is processing has + already been marked as failed. + + +### Lambda Context + +Both raw and deserialized message handlers can choose to take the Lambda context as an argument if they +need it, or not: + +```java + public class ClassWithHandlers { + + private void processMessage(Product product) { + // Do something with the raw message + } + + private void processMessageWithContext(Product product, Context context) { + // Do something with the raw message and the lambda Context } } - ``` +``` diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index 3cf75800f..053e8a9d7 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -1,18 +1,16 @@ --- -title: Custom Resources description: Utility +title: Custom Resources +description: Utility --- -[Custom resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html) +[CloudFormation Custom resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html) provide a way for [AWS Lambda functions]( https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html) to execute -provisioning logic whenever CloudFormation stacks are created, updated, or deleted. The CloudFormation utility enables -developers to write these Lambda functions in Java. +provisioning logic whenever CloudFormation stacks are created, updated, or deleted. -The utility provides a base `AbstractCustomResourceHandler` class which handles [custom resource request events]( -https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html), constructs -[custom resource responses](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html), and -sends them to the custom resources. Subclasses implement the provisioning logic and configure certain properties of -these response objects. +Powertools-cloudformation makes it easy to write Lambda functions in Java that are used as CloudFormation custom resources. +The utility reads incoming CloudFormation events, calls your custom code depending on the operation (CREATE, UPDATE or DELETE) and sends responses back to CloudFormation. +By using this library you do not need to write code to integrate with CloudFormation, and you only focus on writing the custom provisioning logic inside the Lambda function. ## Install @@ -39,57 +37,90 @@ To install this utility, add the following dependency to your project. ## Usage -Create a new `AbstractCustomResourceHandler` subclass and implement the `create`, `update`, and `delete` methods with -provisioning logic in the appropriate methods(s). +To utilise the feature, extend the `AbstractCustomResourceHandler` class in your Lambda handler class. +Next, implement and override the following 3 methods: `create`, `update` and `delete`. The `AbstractCustomResourceHandler` invokes the right method according to the CloudFormation [custom resource request event]( +https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html) it receives. +Inside the methods, implement your custom provisioning logic, and return a `Response`. The `AbstractCustomResourceHandler` takes your `Response`, builds a +[custom resource responses](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html) and sends it to CloudFormation automatically. -As an example, if a Lambda function only needs to provision something when a stack is created, put the provisioning -logic exclusively within the `create` method; the other methods can just return `null`. +Custom resources notify cloudformation either of `SUCCESS` or `FAILED` status. You have 2 utility methods to represent these responses: `Response.success(physicalResourceId)` and `Response.failed(physicalResourceId)`. +The `physicalResourceId` is an identifier that is used during the lifecycle operations of the Custom Resource. +You should generate a `physicalResourceId` during the `CREATE` operation, CloudFormation stores the `physicalResourceId` and includes it in `UPDATE` and `DELETE` events. -```java hl_lines="8 9 10 11" +Here an example of how to implement a Custom Resource using the powertools-cloudformation library: + +```java hl_lines="10-16 21-27 32-38" import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler; import software.amazon.lambda.powertools.cloudformation.Response; -public class ProvisionOnCreateHandler extends AbstractCustomResourceHandler { +public class MyCustomResourceHandler extends AbstractCustomResourceHandler { @Override protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { - doProvisioning(); - return Response.success(); + String physicalResourceId = "sample-resource-id-" + UUID.randomUUID(); //Create a unique ID for your resource + ProvisioningResult provisioningResult = doProvisioning(physicalResourceId); + if(provisioningResult.isSuccessful()){ //check if the provisioning was successful + return Response.success(physicalResourceId); + }else{ + return Response.failed(physicalResourceId); + } } @Override protected Response update(CloudFormationCustomResourceEvent updateEvent, Context context) { - return null; + String physicalResourceId = updateEvent.getPhysicalResourceId(); //Get the PhysicalResourceId from CloudFormation + UpdateResult updateResult = doUpdates(physicalResourceId); + if(updateResult.isSuccessful()){ //check if the update operations were successful + return Response.success(physicalResourceId); + }else{ + return Response.failed(physicalResourceId); + } } @Override protected Response delete(CloudFormationCustomResourceEvent deleteEvent, Context context) { - return null; + String physicalResourceId = deleteEvent.getPhysicalResourceId(); //Get the PhysicalResourceId from CloudFormation + DeleteResult deleteResult = doDeletes(physicalResourceId); + if(deleteResult.isSuccessful()){ //check if the delete operations were successful + return Response.success(physicalResourceId); + }else{ + return Response.failed(physicalResourceId); + } } } ``` -### Signaling Provisioning Failures +### Missing `Response` and exception handling + +If a `Response` is not returned by your code, `AbstractCustomResourceHandler` defaults the response to `SUCCESS`. +If your code raises an exception (which is not handled), the `AbstractCustomResourceHandler` defaults the response to `FAILED`. -If provisioning fails, the stack creation/modification/deletion as a whole can be failed by either throwing a -`RuntimeException` or by explicitly returning a `Response` with a failed status, e.g. `Response.failure()`. +In both of the scenarios, powertools-java will return the `physicalResourceId` to CloudFormation based on the following logic: +- For CREATE operations, the `LogStreamName` from the Lambda context is used. +- For UPDATE and DELETE operations, the `physicalResourceId` provided in the `CloudFormationCustomResourceEvent` is used. -### Configuring Response Objects +#### Why do you need a physicalResourceId? -When provisioning results in data to be shared with other parts of the stack, include this data within the returned -`Response` instance. +It is recommended that you always explicitly provide a `physicalResourceId` in your response rather than letting Powertools for AWS Lambda (Java) generate if for you because `physicalResourceId` has a crucial role in the lifecycle of a CloudFormation custom resource. +If the `physicalResourceId` changes between calls from Cloudformation, for instance in response to an `Update` event, Cloudformation [treats the resource update as a replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html). -This Lambda function creates a [Chime AppInstance](https://docs.aws.amazon.com/chime/latest/dg/create-app-instance.html) +### Customising a response + +As well as the `Response.success(physicalResourceId)` and `Response.failed(physicalResourceId)`, you can customise the `Response` by using the `Response.builder()`. +You customise the responses when you need additional attributes to be shared with other parts of the CloudFormation stack. + +In the example below, the Lambda function creates a [Chime AppInstance](https://docs.aws.amazon.com/chime/latest/dg/create-app-instance.html) and maps the returned ARN to a "ChimeAppInstanceArn" attribute. -```java hl_lines="11 12 13 14" +```java hl_lines="12-17" public class ChimeAppInstanceHandler extends AbstractCustomResourceHandler { @Override protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { + String physicalResourceId = "my-app-name-" + UUID.randomUUID(); //Create a unique ID CreateAppInstanceRequest chimeRequest = CreateAppInstanceRequest.builder() - .name("my-app-name") + .name(physicalResourceId) .build(); CreateAppInstanceResponse chimeResponse = ChimeClient.builder() .region("us-east-1") @@ -98,6 +129,8 @@ public class ChimeAppInstanceHandler extends AbstractCustomResourceHandler { Map<String, String> chimeAtts = Map.of("ChimeAppInstanceArn", chimeResponse.appInstanceArn()); return Response.builder() .value(chimeAtts) + .status(Response.Status.SUCCESS) + .physicalResourceId(physicalResourceId) .build(); } } @@ -112,6 +145,7 @@ For the example above the following response payload will be sent. "StackId": "arn:aws:cloudformation:us-east-1:123456789000:stack/Custom-stack/59e4d2d0-2fe2-10ec-b00e-124d7c1c5f15", "RequestId": "7cae0346-0359-4dff-b80a-a82f247467b6", "LogicalResourceId:": "ChimeTriggerResource", + "PhysicalResourceId:": "my-app-name-db4a47b9-0cac-45ba-8cc4-a480490c5779", "NoEcho": false, "Data": { "ChimeAppInstanceArn": "arn:aws:chime:us-east-1:123456789000:app-instance/150972c2-5490-49a9-8ba7-e7da4257c16a" @@ -119,7 +153,7 @@ For the example above the following response payload will be sent. } ``` -Once the custom resource receives this response, it's "ChimeAppInstanceArn" attribute is set and the +Once the custom resource receives this response, its "ChimeAppInstanceArn" attribute is set and the [Fn::GetAtt function]( https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html) may be used to retrieve the attribute value and make it available to other resources in the stack. @@ -129,11 +163,14 @@ retrieve the attribute value and make it available to other resources in the sta If any attributes are sensitive, enable the "noEcho" flag to mask the output of the custom resource when it's retrieved with the Fn::GetAtt function. -```java hl_lines="6" +```java hl_lines="9" public class SensitiveDataHandler extends AbstractResourceHandler { @Override protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { + String physicalResourceId = "my-sensitive-resource-" + UUID.randomUUID(); //Create a unique ID return Response.builder() + .status(Response.Status.SUCCESS) + .physicalResourceId(physicalResourceId) .value(Map.of("SomeSecret", sensitiveValue)) .noEcho(true) .build(); @@ -147,7 +184,7 @@ Although using a `Map` as the Response's value is the most straightforward way t any arbitrary `java.lang.Object` may be used. By default, these objects are serialized with an internal Jackson `ObjectMapper`. If the object requires special serialization logic, a custom `ObjectMapper` can be specified. -```java hl_lines="21 22 23 24" +```java hl_lines="14-16 26" public class CustomSerializationHandler extends AbstractResourceHandler { /** * Type representing the custom response Data. @@ -167,11 +204,90 @@ public class CustomSerializationHandler extends AbstractResourceHandler { @Override protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { + String physicalResourceId = "my-policy-name-" + UUID.randomUUID(); //Create a unique ID Policy policy = new Policy(); return Response.builder() + .status(Response.Status.SUCCESS) + .physicalResourceId(physicalResourceId) .value(policy) .objectMapper(policyMapper) // customize serialization .build(); } } -``` \ No newline at end of file +``` + +## Advanced + +### Understanding the CloudFormation custom resource lifecycle + +While the library provides an easy-to-use interface, we recommend that you understand the lifecycle of CloudFormation custom resources before using them in production. + +#### Creating a custom resource +When CloudFormation issues a `CREATE` on a custom resource, there are 2 possible states: `CREATE_COMPLETE` and `CREATE_FAILED` +```mermaid +stateDiagram + direction LR + createState: Create custom resource + [*] --> createState + createState --> CREATE_COMPLETE + createState --> CREATE_FAILED +``` + +If the resource is created successfully, the `physicalResourceId` is stored by CloudFormation for future operations. +If the resource failed to create, CloudFormation triggers a rollback operation by default (rollback can be disabled, see [stack failure options](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stack-failure-options.html)) + +#### Updating a custom resource +CloudFormation issues an `UPDATE` operation on a custom resource only when one or more custom resource properties change. +During the update, the custom resource may update successfully, or may fail the update. +```mermaid +stateDiagram + direction LR + updateState: Update custom resource + [*] --> updateState + updateState --> UPDATE_COMPLETE + updateState --> UPDATE_FAILED +``` + +In both of these scenarios, the custom resource can return the same `physicalResourceId` it received in the CloudFormation event, or a different `physicalResourceId`. +Semantically an `UPDATE_COMPLETE` that returns the same `physicalResourceId` it received indicates that the existing resource was updated successfully. +Instead, an `UPDATE_COMPLETE` with a different `physicalResourceId` means that a new physical resource was created successfully. +```mermaid +flowchart BT + id1(Logical resource) + id2(Previous physical Resource) + id3(New physical Resource) + id2 --> id1 + id3 --> id1 +``` +Therefore, after the custom resource update completed or failed, there may be other cleanup operations by Cloudformation during the rollback, as described in the diagram below: +```mermaid +stateDiagram + state if_state <<choice>> + updateState: Update custom resource + deletePrev: DELETE resource with previous physicalResourceId + updatePrev: Rollback - UPDATE resource with previous properties + noOp: No further operations + [*] --> updateState + updateState --> UPDATE_COMPLETE + UPDATE_COMPLETE --> if_state + if_state --> noOp : Same physicalResourceId + if_state --> deletePrev : Different physicalResourceId + updateState --> UPDATE_FAILED + UPDATE_FAILED --> updatePrev +``` + +#### Deleting a custom resource + +CloudFormation issues a `DELETE` on a custom resource when: + +- the CloudFormation stack is being deleted +- a new `physicalResourceId` was received during an update, and CloudFormation proceeds to rollback(DELETE) the custom resource with the previous `physicalResourceId`. + +```mermaid +stateDiagram + direction LR + deleteState: Delete custom resource + [*] --> deleteState + deleteState --> DELETE_COMPLETE + deleteState --> DELETE_FAILED +``` diff --git a/docs/utilities/idempotency.md b/docs/utilities/idempotency.md new file mode 100644 index 000000000..cecc65d7b --- /dev/null +++ b/docs/utilities/idempotency.md @@ -0,0 +1,1488 @@ +--- +title: Idempotency +description: Utility +--- + +The idempotency utility provides a simple solution to convert your Lambda functions into idempotent operations which +are safe to retry. + +## Terminology + +The property of idempotency means that an operation does not cause additional side effects if it is called more than +once with the same input parameters. + +**Idempotent operations will return the same result when they are called multiple +times with the same parameters**. This makes idempotent operations safe to retry. [Read more](https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/) about idempotency. + +**Idempotency key** is a hash representation of either the entire event or a specific configured subset of the event, and invocation results are **JSON serialized** and stored in your persistence storage layer. + +## Key features + +* Prevent Lambda handler function from executing more than once on the same event payload during a time window +* Ensure Lambda handler returns the same result when called with the same payload +* Select a subset of the event as the idempotency key using JMESPath expressions +* Set a time window in which records with the same payload should be considered duplicates + +## Getting started + +### Installation + +=== "Maven" + + ```xml hl_lines="3-7 16 18 25-28" + <dependencies> + ... + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-dynamodb</artifactId> + <version>{{ powertools.version }}</version> + </dependency> + ... + </dependencies> + ... + <!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project --> + <!-- Note: This AspectJ configuration is not needed when using the functional approach --> + <build> + <plugins> + ... + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14</version> + <configuration> + <source>11</source> <!-- or higher --> + <target>11</target> <!-- or higher --> + <complianceLevel>11</complianceLevel> <!-- or higher --> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-core</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <!-- AspectJ compiler version, in sync with runtime --> + <version>1.9.22</version> + </dependency> + </dependencies> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + ... + </plugins> + </build> + ``` + +=== "Gradle" + + ```groovy hl_lines="3 11 12" + plugins { + id 'java' + id 'io.freefair.aspectj.post-compile-weaving' version '8.1.0' // Not needed when using the functional approach + } + + repositories { + mavenCentral() + } + + dependencies { + aspect 'software.amazon.lambda:powertools-idempotency-core:{{ powertools.version }}' // Not needed when using the functional approach + implementation 'software.amazon.lambda:powertools-idempotency-dynamodb:{{ powertools.version }}' + } + + sourceCompatibility = 11 // or higher + targetCompatibility = 11 // or higher + ``` + +### Required resources + +Before getting started, you need to create a persistent storage layer where the idempotency utility can store its state - your Lambda functions will need read and write access to it. + +As of now, Amazon DynamoDB is the only supported persistent storage layer, so you'll need to create a table first or [bring your own persistence store](#bring-your-own-persistent-store). + +**Default table configuration** + +If you're not [changing the default configuration for the DynamoDB persistence layer](#dynamodbpersistencestore), this is the expected default configuration: + +| Configuration | Value | Notes | +|--------------------|--------------|-------------------------------------------------------------------------------------| +| Partition key | `id` | | +| TTL attribute name | `expiration` | This can only be configured after your table is created if you're using AWS Console | + +!!! Tip "Tip: You can share a single state table for all functions" + You can reuse the same DynamoDB table to store idempotency state. We add your function name in addition to the idempotency key as a hash key. + +```yaml hl_lines="5-13 21-23 26" title="AWS Serverless Application Model (SAM) example" +Resources: + IdempotencyTable: + Type: AWS::DynamoDB::Table + Properties: + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + TimeToLiveSpecification: + AttributeName: expiration + Enabled: true + BillingMode: PAY_PER_REQUEST + + IdempotencyFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: Function + Handler: helloworld.App::handleRequest + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref IdempotencyTable + Environment: + Variables: + TABLE_NAME: !Ref IdempotencyTable +``` + +!!! warning "Warning: Large responses with DynamoDB persistence layer" + When using this utility with DynamoDB, your function's responses must be [smaller than 400KB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Constraints.html#limits-items). + Larger items cannot be written to DynamoDB and will cause exceptions. + +!!! info "Info: DynamoDB" + Each function invocation will generally make 1 request to DynamoDB. If the + result returned by your Lambda is less than 1kb, you can expect 1 WCUs per invocation. For retried invocations, you will + see 1 WCU. In some cases, the utility might make 2 requests to DynamoDB in which case you will see 1 RCU and 1 WCU. Review the [DynamoDB pricing documentation](https://aws.amazon.com/dynamodb/pricing/) to + estimate the cost. + +### Basic usage + +You can use Powertools for AWS Lambda Idempotency with either the `@Idempotent` annotation or the functional API. + +!!! warning "Important" + Initialization and configuration of the `DynamoDBPersistenceStore` must be performed outside the handler, preferably in the constructor. + +=== "@Idempotent annotation" + + ```java hl_lines="5-9 12 19" + public class App implements RequestHandler<Subscription, SubscriptionResult> { + + public App() { + // We need to initialize idempotency store before the handleRequest method is called + Idempotency.config().withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build() + ).configure(); + } + + @Idempotent + public SubscriptionResult handleRequest(final Subscription event, final Context context) { + SubscriptionPayment payment = createSubscriptionPayment( + event.getUsername(), + event.getProductId() + ); + + return new SubscriptionResult(payment.getId(), "success", 200); + } + } + + ``` + +=== "Functional API" + + ```java hl_lines="5-9 13-14" + public class App implements RequestHandler<Subscription, SubscriptionResult> { + + public App() { + // We need to initialize idempotency store before the handleRequest method is called + Idempotency.config().withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build() + ).configure(); + } + + public SubscriptionResult handleRequest(final Subscription event, final Context context) { + Idempotency.registerLambdaContext(context); + return Idempotency.makeIdempotent(this::processSubscription, event, SubscriptionResult.class); + } + + private SubscriptionResult processSubscription(Subscription event) { + SubscriptionPayment payment = createSubscriptionPayment(event.getUsername(), event.getProductId()); + return new SubscriptionResult(payment.getId(), "success", 200); + } + } + + ``` + +=== "Example event" + + ```json + { + "username": "xyz", + "product_id": "123456789" + } + ``` + +#### Making non-handler methods idempotent + +You can make any synchronous Java function idempotent, not only the `handleRequest` handler. + +**With the `@Idempotent` annotation**, you must specify which parameter contains the idempotency key: + + - If the method only has one parameter, it will be used by default. + - If there are 2 or more parameters, you must set the `@IdempotencyKey` on the parameter to use. + +**With the functional API**, you explicitly pass the idempotency key: + + - For single-parameter methods, use `Idempotency.makeIdempotent(this::method, param, ReturnType.class)` + - For multi-parameter methods, use `Idempotency.makeIdempotent(idempotencyKey, () -> method(param1, param2), ReturnType.class)` + +!!! info "The parameter must be serializable in JSON. We use Jackson internally to (de)serialize objects" + +=== "@Idempotent annotation" + + This example also demonstrates how you can integrate with [Batch utility](batch.md), so you can process each record in an idempotent manner. + + ```java hl_lines="6-15 17-19 27-28" + public class SqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; + + public SqsBatchHandler() { + Idempotency.config() + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build() + ).withConfig( + IdempotencyConfig.builder() + .withEventKeyJMESPath("messageId") + .build() + ).configure(); + + handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithRawMessageHandler(this::processMessage); + } + + @Override + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + return handler.processBatch(sqsEvent, context); + } + + @Idempotent + private void processMessage(@IdempotencyKey SQSEvent.SQSMessage message) { + // Process message + } + } + ``` + +=== "Functional API" + + This example also demonstrates how you can integrate with the [Batch utility](batch.md), so you can process each record in an idempotent manner. **Note: The JMESPath function still applies even when passing the idempotency key manually.** + + ```java hl_lines="6-15 17-19 24 29" + public class SqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; + + public SqsBatchHandler() { + Idempotency.config() + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build() + ).withConfig( + IdempotencyConfig.builder() + .withEventKeyJMESPath("messageId") + .build() + ).configure(); + + handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithRawMessageHandler(this::processMessage); + } + + @Override + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + Idempotency.registerLambdaContext(context); + return handler.processBatch(sqsEvent, context); + } + + private void processMessage(SQSEvent.SQSMessage message) { + Idempotency.makeIdempotent(this::handleMessage, message, Void.class); + } + + private Void handleMessage(SQSEvent.SQSMessage message) { + // Process message + return null; + } + } + ``` + +=== "Batch event" + + ```json hl_lines="4" + { + "Records": [ + { + "messageId": "059f36b4-87a3-44ab-83d2-661975830a7d", + "receiptHandle": "AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...", + "body": "Test message.", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1545082649183", + "SenderId": "AIDAIENQZJOLO23YVJ4VO", + "ApproximateFirstReceiveTimestamp": "1545082649185" + }, + "messageAttributes": { + "testAttr": { + "stringValue": "100", + "binaryValue": "base64Str", + "dataType": "Number" + } + }, + "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:my-queue", + "awsRegion": "us-east-2" + } + ] + } + ``` + +### Choosing a payload subset for idempotency + +!!! tip "Tip: Dealing with always changing payloads" + When dealing with an elaborate payload (API Gateway request for example), where parts of the payload always change, you should configure the **`EventKeyJMESPath`**. + +Use [`IdempotencyConfig`](#customizing-the-default-behavior) to instruct the Idempotent annotation to only use a portion of your payload to verify whether a request is idempotent, and therefore it should not be retried. + +> **Payment scenario** + +In this example, we have a Lambda handler that creates a payment for a user subscribing to a product. We want to ensure that we don't accidentally charge our customer by subscribing them more than once. + +Imagine the function executes successfully, but the client never receives the response due to a connection issue. It is safe to retry in this instance, as the idempotent decorator will return a previously saved response. + +!!! warning "Warning: Idempotency for JSON payloads" + The payload extracted by the `EventKeyJMESPath` is treated as a string by default, so will be sensitive to differences in whitespace even when the JSON payload itself is identical. + + To alter this behaviour, you can use the [JMESPath built-in function](serialization.md#jmespath-functions) `powertools_json()` to treat the payload as a JSON object rather than a string. + +=== "@Idempotent annotation" + + ```java hl_lines="7 16" + public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + public PaymentFunction() { + Idempotency.config() + .withConfig( + IdempotencyConfig.builder() + .withEventKeyJMESPath("powertools_json(body)") + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build()) + .configure(); + } + + @Idempotent + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent event, final Context context) { + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); + + try { + Subscription subscription = JsonConfig.get().getObjectMapper().readValue(event.getBody(), Subscription.class); + + SubscriptionPayment payment = createSubscriptionPayment( + subscription.getUsername(), + subscription.getProductId() + ); + + return response + .withStatusCode(200) + .withBody(String.format("{\"paymentId\":\"%s\"}", payment.getId())); + + } catch (JsonProcessingException e) { + return response.withStatusCode(500); + } + } + ``` + +=== "Functional API" + + ```java hl_lines="7 17-18" + public class PaymentFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + public PaymentFunction() { + Idempotency.config() + .withConfig( + IdempotencyConfig.builder() + .withEventKeyJMESPath("powertools_json(body)") + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build()) + .configure(); + } + + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent event, final Context context) { + Idempotency.registerLambdaContext(context); + return Idempotency.makeIdempotent(this::processPayment, event, APIGatewayProxyResponseEvent.class); + } + + private APIGatewayProxyResponseEvent processPayment(APIGatewayProxyRequestEvent event) { + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); + + try { + Subscription subscription = JsonConfig.get().getObjectMapper().readValue(event.getBody(), Subscription.class); + + SubscriptionPayment payment = createSubscriptionPayment( + subscription.getUsername(), + subscription.getProductId() + ); + + return response + .withStatusCode(200) + .withBody(String.format("{\"paymentId\":\"%s\"}", payment.getId())); + + } catch (JsonProcessingException e) { + return response.withStatusCode(500); + } + } + ``` + +=== "Example event" + + ```json hl_lines="3" + { + "version":"2.0", + "body":"{\"username\":\"xyz\",\"productId\":\"123456789\"}", + "routeKey":"ANY /createpayment", + "rawPath":"/createpayment", + "rawQueryString":"", + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "requestContext":{ + "accountId":"123456789012", + "apiId":"api-id", + "domainName":"id.execute-api.us-east-1.amazonaws.com", + "domainPrefix":"id", + "http":{ + "method":"POST", + "path":"/createpayment", + "protocol":"HTTP/1.1", + "sourceIp":"ip", + "userAgent":"agent" + }, + "requestId":"id", + "routeKey":"ANY /createpayment", + "stage":"$default", + "time":"10/Feb/2021:13:40:43 +0000", + "timeEpoch":1612964443723 + }, + "isBase64Encoded":false + } + ``` + + +### Idempotency request flow + +This sequence diagram shows an example flow of what happens in the payment scenario: + +<center> +```mermaid +sequenceDiagram + participant Client + participant Lambda + participant Persistence Layer + alt initial request + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set (id=event.search(payload)) + activate Persistence Layer + Note right of Persistence Layer: Locked to prevent concurrent<br/>invocations with <br/> the same payload. + Lambda-->>Lambda: Call handler (event) + Lambda->>Persistence Layer: Update record with result + deactivate Persistence Layer + Persistence Layer-->>Persistence Layer: Update record with result + Lambda-->>Client: Response sent to client + else retried request + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set (id=event.search(payload)) + Persistence Layer-->>Lambda: Already exists in persistence layer. Return result + Lambda-->>Client: Response sent to client + end +``` +<i>Idempotent sequence</i> +</center> + +The client was successful in receiving the result after the retry. Since the Lambda handler was only executed once, our customer hasn't been charged twice. + +!!! note + Bear in mind that the entire Lambda handler is treated as a single idempotent operation. If your Lambda handler can cause multiple side effects, consider splitting it into separate functions. + +#### Lambda timeouts + +To prevent against extended failed retries when a [Lambda function times out](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-verify-invocation-timeouts/), Powertools for AWS Lambda (Java) calculates and includes the remaining invocation available time as part of the idempotency record. + +!!! example + If a second invocation happens **after** this timestamp, and the record is marked as `INPROGRESS`, we will execute the invocation again as if it was in the `EXPIRED` state. + This means that if an invocation expired during execution, it will be quickly executed again on the next retry. + +**With the `@Idempotent` annotation**, this is automatically done when you annotate your Lambda handler. + +**With the functional API** or when using the `@Idempotent` annotation on methods other than the handler, you must call `Idempotency.registerLambdaContext(context)` to benefit from this protection. + +!!! important + Here is an example on how you register the Lambda context in your handler: + + === "@Idempotent annotation" + + ```java hl_lines="14" title="Registering the Lambda context" + public class PaymentHandler implements RequestHandler<SQSEvent, List<String>> { + + public PaymentHandler() { + Idempotency.config() + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build()) + .configure(); + } + + @Override + public List<String> handleRequest(SQSEvent sqsEvent, Context context) { + Idempotency.registerLambdaContext(context); + return sqsEvent.getRecords().stream().map(record -> process(record.getMessageId(), record.getBody())).collect(Collectors.toList()); + } + + @Idempotent + private String process(String messageId, @IdempotencyKey String messageBody) { + logger.info("Processing messageId: {}", messageId); + PaymentRequest request = extractDataFrom(messageBody).as(PaymentRequest.class); + return paymentService.process(request); + } + + } + ``` + + === "Functional API" + + ```java hl_lines="14" title="Registering the Lambda context" + public class PaymentHandler implements RequestHandler<SQSEvent, List<String>> { + + public PaymentHandler() { + Idempotency.config() + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build()) + .configure(); + } + + @Override + public List<String> handleRequest(SQSEvent sqsEvent, Context context) { + Idempotency.registerLambdaContext(context); + return sqsEvent.getRecords().stream() + .map(record -> Idempotency.makeIdempotent( + record.getBody(), + () -> process(record.getMessageId(), record.getBody()), + String.class)) + .collect(Collectors.toList()); + } + + private String process(String messageId, String messageBody) { + logger.info("Processing messageId: {}", messageId); + PaymentRequest request = extractDataFrom(messageBody).as(PaymentRequest.class); + return paymentService.process(request); + } + + } + ``` + +#### Lambda timeout sequence diagram + +This sequence diagram shows an example flow of what happens if a Lambda function times out: + +<center> +```mermaid +sequenceDiagram + participant Client + participant Lambda + participant Persistence Layer + alt initial request + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set (id=event.search(payload)) + activate Persistence Layer + Note right of Persistence Layer: Locked to prevent concurrent<br/>invocations with <br/> the same payload. + Note over Lambda: Time out + Lambda--xLambda: Call handler (event) + Lambda-->>Client: Return error response + deactivate Persistence Layer + else concurrent request before timeout + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set (id=event.search(payload)) + Persistence Layer-->>Lambda: Request already INPROGRESS + Lambda--xClient: Return IdempotencyAlreadyInProgressError + else retry after Lambda timeout + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set (id=event.search(payload)) + activate Persistence Layer + Note right of Persistence Layer: Locked to prevent concurrent<br/>invocations with <br/> the same payload. + Lambda-->>Lambda: Call handler (event) + Lambda->>Persistence Layer: Update record with result + deactivate Persistence Layer + Persistence Layer-->>Persistence Layer: Update record with result + Lambda-->>Client: Response sent to client + end +``` +<i>Idempotent sequence for Lambda timeouts</i> +</center> + +### Handling exceptions + +**With the `@Idempotent` annotation**, any unhandled exceptions that are thrown during the code execution will cause **the record in the persistence layer to be deleted**. +This means that new invocations will execute your code again despite having the same payload. If you don't want the record to be deleted, you need to catch exceptions within the idempotent function and return a successful response. + +**With the functional API**, exceptions are handled the same way - unhandled exceptions will cause the record to be deleted. You should catch and handle exceptions within your idempotent function if you want to preserve the record. + +<center> +```mermaid +sequenceDiagram + participant Client + participant Lambda + participant Persistence Layer + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set (id=event.search(payload)) + activate Persistence Layer + Note right of Persistence Layer: Locked during this time. Prevents multiple<br/>Lambda invocations with the same<br/>payload running concurrently. + Lambda--xLambda: Call handler (event).<br/>Raises exception + Lambda->>Persistence Layer: Delete record (id=event.search(payload)) + deactivate Persistence Layer + Lambda-->>Client: Return error response +``` +<i>Idempotent sequence exception</i> +</center> + +If an Exception is raised _outside_ the scope of a decorated method and after your method has been called, the persistent record will not be affected. In this case, idempotency will be maintained for your decorated function. Example: + +```java hl_lines="2-4 8-10" title="Exception not affecting idempotency record sample" + public SubscriptionResult handleRequest(final Subscription event, final Context context) { + // If an exception is thrown here, no idempotent record will ever get created as the + // idempotent function does not get called + doSomeStuff(); + + result = idempotentMethod(event); + + // This exception will not cause the idempotent record to be deleted, since it + // happens after the decorated function has been successfully called + throw new Exception(); + } + + @Idempotent + private String idempotentMethod(final Subscription subscription) { + // perform some operation with no exception thrown + } +``` + +!!! warning + **We will throw an `IdempotencyPersistenceLayerException`** if any of the calls to the persistence layer fail unexpectedly. + + As this happens outside the scope of your decorated function, you are not able to catch it. + +### Persistence stores + +#### DynamoDBPersistenceStore + +This persistence store is built-in, and you can either use an existing DynamoDB table or create a new one dedicated for idempotency state (recommended). + +Use the builder to customize the table structure: +```java hl_lines="2-7" title="Customizing DynamoDBPersistenceStore to suit your table structure" +DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .withKeyAttr("idempotency_key") + .withExpiryAttr("expires_at") + .withStatusAttr("current_status") + .withDataAttr("result_data") + .withValidationAttr("validation_key") + .build() +``` + +When using DynamoDB as a persistence layer, you can alter the attribute names by passing these parameters when initializing the persistence layer: + +| Parameter | Required | Default | Description | +|--------------------|----------|--------------------------------------|--------------------------------------------------------------------------------------------------------| +| **TableName** | Y | | Table name to store state | +| **KeyAttr** | | `id` | Partition key of the table. Hashed representation of the payload (unless **SortKeyAttr** is specified) | +| **ExpiryAttr** | | `expiration` | Unix timestamp of when record expires | +| **StatusAttr** | | `status` | Stores status of the Lambda execution during and after invocation | +| **DataAttr** | | `data` | Stores results of successfully idempotent methods | +| **ValidationAttr** | | `validation` | Hashed representation of the parts of the event used for validation | +| **SortKeyAttr** | | | Sort key of the table (if table is configured with a sort key). | +| **StaticPkValue** | | `idempotency#{LAMBDA_FUNCTION_NAME}` | Static value to use as the partition key. Only used when **SortKeyAttr** is set. | + +## Advanced + +### Using explicit function names + +When using the functional API, if you need to call different methods with the same payload as the idempotency key, you must provide explicit function names to differentiate between them. This ensures each function has its own idempotency scope. + +=== "Functional API with explicit names" + + ```java hl_lines="5-9 11-15" + public Response handleRequest(Order order, Context context) { + Idempotency.registerLambdaContext(context); + + // Same orderId, different operations - need explicit function names + Idempotency.makeIdempotent( + "processPayment", + order.getId(), + () -> processPayment(order), + PaymentResult.class); + + Idempotency.makeIdempotent( + "sendConfirmation", + order.getId(), + () -> sendEmail(order), + EmailResult.class); + + return new Response("success"); + } + ``` + +!!! note + When using the `@Idempotent` annotation, the function name is automatically inferred from the method name, so this is not needed. + +### Generic return types support + +The functional API supports making methods with generic return types idempotent using Jackson's `TypeReference`. This is not possible with the `@Idempotent` annotation due to type erasure. + +=== "Functional API with TypeReference" + + ```java hl_lines="1 6-10" + import com.fasterxml.jackson.core.type.TypeReference; + + public Map<String, Basket> handleRequest(Product input, Context context) { + Idempotency.registerLambdaContext(context); + + return Idempotency.makeIdempotent( + this::processProduct, + input, + new TypeReference<Map<String, Basket>>() {} + ); + } + + private Map<String, Basket> processProduct(Product product) { + // business logic returning generic type + Map<String, Basket> result = new HashMap<>(); + // ... + return result; + } + ``` + +### Customizing the default behavior + +Idempotency behavior can be further configured with **`IdempotencyConfig`** using a builder: + +```java hl_lines="2-9" title="Customizing IdempotencyConfig" +IdempotencyConfig.builder() + .withEventKeyJMESPath("id") + .withPayloadValidationJMESPath("paymentId") + .withThrowOnNoIdempotencyKey(true) + .withExpiration(Duration.of(5, ChronoUnit.MINUTES)) + .withUseLocalCache(true) + .withLocalCacheMaxItems(432) + .withHashFunction("SHA-256") + .withResponseHook((responseData, dataRecord) -> responseData) + .build() +``` + +These are the available options for further configuration: + +| Parameter | Default | Description | +|---------------------------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------| +| **EventKeyJMESPath** | `""` | JMESPath expression to extract the idempotency key from the event record. See available [built-in functions](serialization) | +| **PayloadValidationJMESPath** | `""` | JMESPath expression to validate whether certain parameters have changed in the event | +| **ThrowOnNoIdempotencyKey** | `false` | Throw exception if no idempotency key was found in the request | +| **ExpirationInSeconds** | 3600 | The number of seconds to wait before a record is expired | +| **UseLocalCache** | `false` | Whether to locally cache idempotency results (LRU cache) | +| **LocalCacheMaxItems** | 256 | Max number of items to store in local cache | +| **HashFunction** | `MD5` | Algorithm to use for calculating hashes, as supported by `java.security.MessageDigest` (eg. SHA-1, SHA-256, ...) | +| **ResponseHook** | `null` | Response hook to apply modifications to idempotent responses | + +These features are detailed below. + +### Handling concurrent executions with the same payload + +This utility will throw an **`IdempotencyAlreadyInProgressException`** if we receive **multiple invocations with the same payload while the first invocation hasn't completed yet**. + +!!! info + If you receive `IdempotencyAlreadyInProgressException`, you can safely retry the operation. + +This is a locking mechanism for correctness. Since we don't know the result from the first invocation yet, we can't safely allow another concurrent execution. + +### Using in-memory cache + +**By default, in-memory local caching is disabled**, to avoid using memory in an unpredictable way. + +!!! warning Memory configuration of your function + Be sure to configure the Lambda memory according to the number of records and the potential size of each record. + +You can enable it as seen before with: +```java title="Enable local cache" + IdempotencyConfig.builder() + .withUseLocalCache(true) + .build() +``` +When enabled, we cache a maximum of 256 records in each Lambda execution environment - You can change it with the **`LocalCacheMaxItems`** parameter. + +!!! note "Note: This in-memory cache is local to each Lambda execution environment" + This means it will be effective in cases where your function's concurrency is low in comparison to the number of "retry" invocations with the same payload, because cache might be empty. + + +### Expiring idempotency records + +!!! note + By default, we expire idempotency records after **an hour** (3600 seconds). + +In most cases, it is not desirable to store the idempotency records forever. Rather, you want to guarantee that the same payload won't be executed within a period of time. + +You can change this window with the **`ExpirationInSeconds`** parameter: +```java title="Customizing expiration time" +IdempotencyConfig.builder() + .withExpiration(Duration.of(5, ChronoUnit.MINUTES)) + .build() +``` + +Records older than 5 minutes will be marked as expired, and the Lambda handler will be executed normally even if it is invoked with a matching payload. + +!!! note "Note: DynamoDB time-to-live field" + This utility uses **`expiration`** as the TTL field in DynamoDB, as [demonstrated in the SAM example earlier](#required-resources). + +### Payload validation + +!!! question "Question: What if your function is invoked with the same payload except some outer parameters have changed?" + Example: A payment transaction for a given productID was requested twice for the same customer, **however the amount to be paid has changed in the second transaction**. + +By default, we will return the same result as it returned before, however in this instance it may be misleading; we provide a fail fast payload validation to address this edge case. + +With **`PayloadValidationJMESPath`**, you can provide an additional JMESPath expression to specify which part of the event body should be validated against previous idempotent invocations + +=== "@Idempotent annotation" + + ```java hl_lines="8 13 20 26" + public App() { + Idempotency.config() + .withPersistenceStore(DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build()) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("[userDetail, productId]") + .withPayloadValidationJMESPath("amount") + .build()) + .configure(); + } + + @Idempotent + public SubscriptionResult handleRequest(final Subscription input, final Context context) { + // Creating a subscription payment is a side + // effect of calling this function! + SubscriptionPayment payment = createSubscriptionPayment( + input.getUserDetail().getUsername(), + input.getProductId(), + input.getAmount() + ) + // ... + return new SubscriptionResult( + "success", 200, + payment.getId(), + payment.getAmount() + ); + } + ``` + +=== "Functional API" + + ```java hl_lines="8 14-15 24 30" + public App() { + Idempotency.config() + .withPersistenceStore(DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build()) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("[userDetail, productId]") + .withPayloadValidationJMESPath("amount") + .build()) + .configure(); + } + + public SubscriptionResult handleRequest(final Subscription input, final Context context) { + Idempotency.registerLambdaContext(context); + return Idempotency.makeIdempotent(this::processSubscription, input, SubscriptionResult.class); + } + + private SubscriptionResult processSubscription(Subscription input) { + // Creating a subscription payment is a side + // effect of calling this function! + SubscriptionPayment payment = createSubscriptionPayment( + input.getUserDetail().getUsername(), + input.getProductId(), + input.getAmount() + ) + // ... + return new SubscriptionResult( + "success", 200, + payment.getId(), + payment.getAmount() + ); + } + ``` + +=== "Example Event 1" + + ```json hl_lines="8" + { + "userDetail": { + "username": "User1", + "user_email": "user@example.com" + }, + "productId": 1500, + "charge_type": "subscription", + "amount": 500 + } + ``` + +=== "Example Event 2" + + ```json hl_lines="8" + { + "userDetail": { + "username": "User1", + "user_email": "user@example.com" + }, + "productId": 1500, + "charge_type": "subscription", + "amount": 1 + } + ``` + +In this example, the **`userDetail`** and **`productId`** keys are used as the payload to generate the idempotency key, as per **`EventKeyJMESPath`** parameter. + +!!! note + If we try to send the same request but with a different amount, we will raise **`IdempotencyValidationException`**. + +Without payload validation, we would have returned the same result as we did for the initial request. Since we're also returning an amount in the response, this could be quite confusing for the client. + +By using **`withPayloadValidationJMESPath("amount")`**, we prevent this potentially confusing behavior and instead throw an Exception. + +### Making idempotency key required + +If you want to enforce that an idempotency key is required, you can set **`ThrowOnNoIdempotencyKey`** to `true`. + +This means that we will throw **`IdempotencyKeyException`** if the evaluation of **`EventKeyJMESPath`** is `null`. + +When set to `false` (the default), if the idempotency key is null, then the data is not persisted in the store. + +=== "@Idempotent annotation" + + ```java hl_lines="9" + public App() { + Idempotency.config() + .withPersistenceStore(DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build()) + .withConfig(IdempotencyConfig.builder() + // Requires "user"."uid" and "orderId" to be present + .withEventKeyJMESPath("[user.uid, orderId]") + .withThrowOnNoIdempotencyKey(true) + .build()) + .configure(); + } + + @Idempotent + public OrderResult handleRequest(final Order input, final Context context) { + // ... + } + ``` + +=== "Functional API" + + ```java hl_lines="9" + public App() { + Idempotency.config() + .withPersistenceStore(DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build()) + .withConfig(IdempotencyConfig.builder() + // Requires "user"."uid" and "orderId" to be present + .withEventKeyJMESPath("[user.uid, orderId]") + .withThrowOnNoIdempotencyKey(true) + .build()) + .configure(); + } + + public OrderResult handleRequest(final Order input, final Context context) { + Idempotency.registerLambdaContext(context); + return Idempotency.makeIdempotent(this::processOrder, input, OrderResult.class); + } + + private OrderResult processOrder(Order input) { + // ... + } + ``` + +=== "Success Event" + + ```json hl_lines="3 6" + { + "user": { + "uid": "BB0D045C-8878-40C8-889E-38B3CB0A61B1", + "name": "Foo" + }, + "orderId": 10000 + } + ``` + +=== "Failure Event" + + Notice that `orderId` is now accidentally within `user` key + + ```json hl_lines="3 5" + { + "user": { + "uid": "DE0D000E-1234-10D1-991E-EAC1DD1D52C8", + "name": "Joe Bloggs", + "orderId": 10000 + }, + } + ``` + +### Customizing DynamoDB configuration + +When creating the `DynamoDBPersistenceStore`, you can set a custom [`DynamoDbClient`](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html) if you need to customize the configuration: + +=== "Custom DynamoDbClient with X-Ray interceptor" + + ```java hl_lines="2-8 13" + public App() { + DynamoDbClient customClient = DynamoDbClient.builder() + .region(Region.US_WEST_2) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(new TracingInterceptor()) + .build() + ) + .build(); + + Idempotency.config().withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .withDynamoDbClient(customClient) + .build() + ).configure(); + } + ``` + +!!! info "Default configuration is the following:" + + ```java + DynamoDbClient.builder() + .credentialsProvider(EnvironmentVariableCredentialsProvider.create()) + .httpClient(UrlConnectionHttpClient.builder().build()) + .region(Region.of(System.getenv(AWS_REGION_ENV))) + .build(); + ``` + +### Using a DynamoDB table with a composite primary key + +When using a composite primary key table (hash+range key), use `SortKeyAttr` parameter when initializing your persistence store. + +With this setting, we will save the idempotency key in the sort key instead of the primary key. By default, the primary key will now be set to `idempotency#{LAMBDA_FUNCTION_NAME}`. + +You can optionally set a static value for the partition key using the `StaticPkValue` parameter. + +```java hl_lines="5" title="Reusing a DynamoDB table that uses a composite primary key" +Idempotency.config().withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .withSortKeyAttr("sort_key") + .build()) + .configure(); +``` + +Data would then be stored in DynamoDB like this: + +| id | sort_key | expiration | status | data | +|------------------------------|----------------------------------|------------|-------------|--------------------------------------| +| idempotency#MyLambdaFunction | 1e956ef7da78d0cb890be999aecc0c9e | 1636549553 | COMPLETED | {"id": 12391, "message": "success"} | +| idempotency#MyLambdaFunction | 2b2cdb5f86361e97b4383087c1ffdf27 | 1636549571 | COMPLETED | {"id": 527212, "message": "success"} | +| idempotency#MyLambdaFunction | f091d2527ad1c78f05d54cc3f363be80 | 1636549585 | IN_PROGRESS | | + +### Bring your own persistent store + +This utility provides an abstract base class, so that you can implement your choice of persistent storage layer. + +You can extend the `BasePersistenceStore` class and implement the abstract methods `getRecord`, `putRecord`, +`updateRecord` and `deleteRecord`. You can have a look at [`DynamoDBPersistenceStore`](https://github.com/aws-powertools/powertools-lambda-java/blob/master/powertools-idempotency/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/DynamoDBPersistenceStore.java) as an implementation reference. + +!!! danger + Pay attention to the documentation for each method - you may need to perform additional checks inside these methods to ensure the idempotency guarantees remain intact. + + For example, the `putRecord` method needs to throw an exception if a non-expired record already exists in the data store with a matching key. + +### Manipulating the Idempotent Response + +You can set up a response hook in the Idempotency configuration to manipulate the returned data when an operation is idempotent. The hook function will be called with the current de-serialized response `Object` and the Idempotency `DataRecord`. + +The example below shows how to append an HTTP header to an `APIGatewayProxyResponseEvent`. + +=== "Using an Idempotent Response Hook" + + ```java hl_lines="3-20" + Idempotency.config().withConfig( + IdempotencyConfig.builder() + .withResponseHook((responseData, dataRecord) -> { + if (responseData instanceof APIGatewayProxyResponseEvent) { + APIGatewayProxyResponseEvent proxyResponse = + (APIGatewayProxyResponseEvent) responseData; + final Map<String, String> headers = new HashMap<>(); + headers.putAll(proxyResponse.getHeaders()); + // Append idempotency headers + headers.put("x-idempotency-response", "true"); + headers.put("x-idempotency-expiration", + String.valueOf(dataRecord.getExpiryTimestamp())); + + proxyResponse.setHeaders(headers); + + return proxyResponse; + } + + return responseData; + }) + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build()) + .configure(); + ``` + +???+ info "Info: Using custom de-serialization?" + + The response hook is called after de-serialization so the payload you process will be the de-serialized Java object. + +#### Being a good citizen + +When using response hooks to manipulate returned data from idempotent operations, it's important to follow best practices to avoid introducing complexity or issues. Keep these guidelines in mind: + +1. **Response hook works exclusively when operations are idempotent.** The hook will not be called when an operation is not idempotent, or when the idempotent logic fails. + +2. **Catch and Handle Exceptions.** Your response hook code should catch and handle any exceptions that may arise from your logic. Unhandled exceptions will cause the Lambda function to fail unexpectedly. + +3. **Keep Hook Logic Simple** Response hooks should consist of minimal and straightforward logic for manipulating response data. Avoid complex conditional branching and aim for hooks that are easy to reason about. + + +## Compatibility with other utilities + +### Validation utility + +The idempotency utility can be used with the `@Validation` annotation from the [validation module](validation.md). Ensure that idempotency is the innermost annotation. + +```java hl_lines="1 2" title="Using Idempotency with JSONSchema Validation utility" +@Validation(inboundSchema = "classpath:/schema_in.json") +@Idempotent +public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + // ... +} +``` + +!!! tip "Tip: JMESPath Powertools for AWS Lambda (Java) functions are also available" + Built-in functions like `powertools_json`, `powertools_base64`, `powertools_base64_gzip` are also available to use in this utility. See [JMESPath Powertools for AWS Lambda (Java) functions](serialization.md) + + +## Testing your code + +The idempotency utility provides several routes to test your code. + +### Disabling the idempotency utility +When testing your code, you may wish to disable the idempotency logic altogether and focus on testing your business logic. To do this, you can set the environment variable `POWERTOOLS_IDEMPOTENCY_DISABLED` to true. +If you prefer setting this for specific tests, and are using JUnit 5, you can use [junit-pioneer](https://junit-pioneer.org/docs/environment-variables/) library: + +=== "MyFunctionTest.java" + + ```java hl_lines="2" + @Test + @SetEnvironmentVariable(key = Constants.IDEMPOTENCY_DISABLED_ENV, value = "true") + public void testIdempotencyDisabled_shouldJustRunTheFunction() { + MyFunction func = new MyFunction(); + func.handleRequest(someInput, mockedContext); + } + ``` + +You can also disable the idempotency for all tests using `maven-surefire-plugin` and adding the environment variable: + +=== "pom.xml" +```xml hl_lines="5-7" +<plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <POWERTOOLS_IDEMPOTENCY_DISABLED>true</POWERTOOLS_IDEMPOTENCY_DISABLED> + </environmentVariables> + </configuration> +</plugin> +``` + +### Testing with DynamoDB Local + +#### Unit tests + +To unit test your function with DynamoDB Local, you can refer to this guide to [setup with Maven](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html#apache-maven). + +=== "pom.xml" + + ```xml + <dependencies> + <!-- maven dependency for DynamoDB local --> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>DynamoDBLocal</artifactId> + <!-- Use newest version if you are on Java >11 --> + <!-- https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocalHistory.html --> + <version>2.2.0</version> + <scope>test</scope> + </dependency> + <!-- Needed when building locally on M1 Mac --> + <dependency> + <groupId>io.github.ganadist.sqlite4java</groupId> + <artifactId>libsqlite4java-osx-aarch64</artifactId> + <version>1.0.392</version> + <scope>test</scope> + <type>dylib</type> + </dependency> + </dependencies> + <repositories> + <!-- custom repository to get the dependency --> + <!-- see https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html#apache-maven --> + <repository> + <id>dynamodb-local-oregon</id> + <name>DynamoDB Local Release Repository</name> + <url>https://s3-us-west-2.amazonaws.com/dynamodb-local/release</url> + </repository> + </repositories> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>3.0.0-M5</version> + <configuration> + <!-- need sqlite native libs --> + <systemPropertyVariables> + <sqlite4java.library.path>${project.build.directory}/native-libs</sqlite4java.library.path> + </systemPropertyVariables> + <!-- environment variables for the tests --> + <environmentVariables> + <TABLE_NAME>idempotency</TABLE_NAME> + <AWS_REGION>eu-central-1</AWS_REGION> + </environmentVariables> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>copy</id> + <phase>test-compile</phase> + <goals> + <goal>copy-dependencies</goal> + </goals> + <configuration> + <includeScope>test</includeScope> + <includeTypes>so,dll,dylib</includeTypes> + <outputDirectory>${project.build.directory}/native-libs</outputDirectory> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + ``` + +=== "AppTest.java" + + ```java + public class AppTest { + @Mock + private Context context; + private App app; + private static DynamoDbClient client; + + @BeforeAll + public static void setupDynamoLocal() { + int port = getFreePort(); + + // Initialize DynamoDBLocal + try { + DynamoDBProxyServer dynamoProxy = ServerRunner.createServerFromCommandLineArgs(new String[]{ + "-inMemory", + "-port", + Integer.toString(port) + }); + dynamoProxy.start(); + } catch (Exception e) { + throw new RuntimeException(); + } + + // Initialize DynamoDBClient + client = DynamoDbClient.builder() + .httpClient(UrlConnectionHttpClient.builder().build()) + .region(Region.EU_WEST_1) + .endpointOverride(URI.create("http://localhost:" + port)) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create("FAKE", "FAKE"))) + .build(); + + // create the table (use same table name as in pom.xml) + client.createTable(CreateTableRequest.builder() + .tableName("idempotency") + .keySchema(KeySchemaElement.builder().keyType(KeyType.HASH).attributeName("id").build()) + .attributeDefinitions( + AttributeDefinition.builder().attributeName("id").attributeType(ScalarAttributeType.S).build() + ) + .billingMode(BillingMode.PAY_PER_REQUEST) + .build()); + } + + private static int getFreePort() { + try { + ServerSocket socket = new ServerSocket(0); + int port = socket.getLocalPort(); + socket.close(); + return port; + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + app = new App(client); + } + + @Test + public void testApp() { + app.handleRequest(..., context); + // ... assert + } + } + ``` + +=== "App.java" + + ```java + public class App implements RequestHandler<Subscription, SubscriptionResult> { + + public App(DynamoDbClient ddbClient) { + Idempotency.config().withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .withDynamoDbClient(ddbClient) + .build() + ).configure(); + } + + public App() { + this(null); + } + + @Idempotent + public SubscriptionResult handleRequest(final Subscription event, final Context context) { + // ... + } + ``` + + +#### SAM Local + +=== "App.java" + + ```java + public class App implements RequestHandler<Subscription, SubscriptionResult> { + + public App() { + DynamoDbClientBuilder ddbBuilder = DynamoDbClient.builder() + .credentialsProvider(EnvironmentVariableCredentialsProvider.create()) + .httpClient(UrlConnectionHttpClient.builder().build()); + + if (System.getenv("AWS_SAM_LOCAL") != null) { + ddbBuilder.endpointOverride(URI.create("http://dynamo:8000")); + } else { + ddbBuilder.region(Region.of(System.getenv("AWS_REGION"))); + } + + Idempotency.config().withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .withDynamoDbClient(ddbBuilder.build()) + .build() + ).configure(); + } + + @Idempotent + public SubscriptionResult handleRequest(final Subscription event, final Context context) { + // ... + } + } + ``` + +=== "shell" + + ```shell + # use or create a docker network + docker network inspect sam-local || docker network create sam-local + + # start dynamodb-local with docker + docker run -d --rm -p 8000:8000 \ + --network sam-local \ + --name dynamo \ + amazon/dynamodb-local + + # create the idempotency table + aws dynamodb create-table + --table-name idempotency \ + --attribute-definitions AttributeName=id,AttributeType=S \ + --key-schema AttributeName=id,KeyType=HASH \ + --billing-mode PAY_PER_REQUEST \ + --endpoint-url http://localhost:8000 + + # invoke the function locally + sam local invoke IdempotentFunction \ + --event event.json \ + --env-vars env.json \ + --docker-network sam-local + ``` + +=== "env.json" + + ```json + { + "IdempotentFunction": { + "TABLE_NAME": "idempotency" + } + } + ``` + +## Extra resources + +If you're interested in a deep dive on how Amazon uses idempotency when building our APIs, check out +[this article](https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/). diff --git a/docs/utilities/kafka.md b/docs/utilities/kafka.md new file mode 100644 index 000000000..da179bc5c --- /dev/null +++ b/docs/utilities/kafka.md @@ -0,0 +1,1001 @@ +--- +title: Kafka Consumer +description: Utility +status: new +--- + +<!-- markdownlint-disable MD043 --> + +The Kafka utility transparently handles message deserialization, provides an intuitive developer experience, and integrates seamlessly with the rest of the Powertools for AWS Lambda ecosystem. + +```mermaid +flowchart LR + KafkaTopic["Kafka Topic"] --> MSK["Amazon MSK"] + KafkaTopic --> MSKServerless["Amazon MSK Serverless"] + KafkaTopic --> SelfHosted["Self-hosted Kafka"] + MSK --> EventSourceMapping["Event Source Mapping"] + MSKServerless --> EventSourceMapping + SelfHosted --> EventSourceMapping + EventSourceMapping --> Lambda["Lambda Function"] + Lambda --> KafkaUtility["Kafka Utility"] + KafkaUtility --> Deserialization["Deserialization"] + Deserialization --> YourLogic["Your Business Logic"] +``` + +## Key features + +- Automatic deserialization of Kafka messages (JSON, Avro, and Protocol Buffers) +- Simplified event record handling with familiar Kafka `ConsumerRecords` interface +- Support for key and value deserialization +- Support for ESM with and without Schema Registry integration +- Proper error handling for deserialization issues + +## Terminology + +**Event Source Mapping (ESM)** A Lambda feature that reads from streaming sources (like Kafka) and invokes your Lambda function. It manages polling, batching, and error handling automatically, eliminating the need for consumer management code. + +**Record Key and Value** A Kafka messages contain two important parts: an optional key that determines the partition and a value containing the actual message data. Both are base64-encoded in Lambda events and can be independently deserialized. + +**Deserialization** Is the process of converting binary data (base64-encoded in Lambda events) into usable Java objects according to a specific format like JSON, Avro, or Protocol Buffers. Powertools handles this conversion automatically. + +**DeserializationType enum** Contains parameters that tell Powertools how to interpret message data, including the format type (JSON, Avro, Protocol Buffers). + +**Schema Registry** Is a centralized service that stores and validates schemas, ensuring producers and consumers maintain compatibility when message formats evolve over time. + +## Moving from traditional Kafka consumers + +Lambda processes Kafka messages as discrete events rather than continuous streams, requiring a different approach to consumer development that Powertools for AWS helps standardize. + +| Aspect | Traditional Kafka Consumers | Lambda Kafka Consumer | +| --------------------- | ----------------------------------- | -------------------------------------------------------------- | +| **Model** | Pull-based (you poll for messages) | Push-based (Lambda invoked with messages) | +| **Scaling** | Manual scaling configuration | Automatic scaling to partition count | +| **State** | Long-running application with state | Stateless, ephemeral executions | +| **Offsets** | Manual offset management | Automatic offset commitment | +| **Schema Validation** | Client-side schema validation | Optional Schema Registry integration with Event Source Mapping | +| **Error Handling** | Per-message retry control | Batch-level retry policies | + +## Getting started + +### Installation + +Add the Powertools for AWS Lambda Kafka dependency to your project. Make sure to also add the `kafka-clients` library as a dependency. The utility supports `kafka-clients >= 3.0.0`. + +=== "Maven" + + ```xml + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-kafka</artifactId> + <version>{{ powertools.version }}</version> + </dependency> + <!-- Kafka clients dependency - compatibility works for >= 3.0.0 --> + <dependency> + <groupId>org.apache.kafka</groupId> + <artifactId>kafka-clients</artifactId> + <version>4.0.0</version> + </dependency> + ``` + +=== "Gradle" + + ```gradle + dependencies { + implementation 'software.amazon.lambda:powertools-kafka:{{ powertools.version }}' + // Kafka clients dependency - compatibility works for >= 3.0.0 + implementation 'org.apache.kafka:kafka-clients:4.0.0' + } + ``` + +### Required resources + +To use the Kafka utility, you need an AWS Lambda function configured with a Kafka event source. This can be Amazon MSK, MSK Serverless, or a self-hosted Kafka cluster. + +=== "getting_started_with_msk.yaml" + + ```yaml + AWSTemplateFormatVersion: '2010-09-09' + Transform: AWS::Serverless-2016-10-31 + Resources: + KafkaConsumerFunction: + Type: AWS::Serverless::Function + Properties: + Handler: org.example.KafkaHandler::handleRequest + Runtime: java21 + Timeout: 30 + Events: + MSKEvent: + Type: MSK + Properties: + StartingPosition: LATEST + Stream: !GetAtt MyMSKCluster.Arn + Topics: + - my-topic-1 + - my-topic-2 + Policies: + - AWSLambdaMSKExecutionRole + ``` + +### Using ESM with Schema Registry + +The Event Source Mapping configuration determines which mode is used. With `JSON`, Lambda converts all messages to JSON before invoking your function. With `SOURCE` mode, Lambda preserves the original format, requiring you function to handle the appropriate deserialization. + +Powertools for AWS supports both Schema Registry integration modes in your Event Source Mapping configuration. + +### Processing Kafka events + +The Kafka utility transforms raw Lambda Kafka events into an intuitive format for processing. To handle messages effectively, you'll need to configure the `@Deserialization` annotation that matches your data format. Based on the deserializer you choose, incoming records are directly transformed into your business objects which can be auto-generated classes from Avro / Protobuf or simple POJOs. + +<!-- prettier-ignore --> +???+ tip "Using Avro is recommended" + We recommend Avro for production Kafka implementations due to its schema evolution capabilities, compact binary format, and integration with Schema Registry. This offers better type safety and forward/backward compatibility compared to JSON. + +=== "Avro Messages" + + ```java hl_lines="18 21" + package org.example; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import org.apache.kafka.clients.consumer.ConsumerRecord; + import org.apache.kafka.clients.consumer.ConsumerRecords; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.kafka.Deserialization; + import software.amazon.lambda.powertools.kafka.DeserializationType; + import software.amazon.lambda.powertools.logging.Logging; + + public class AvroKafkaHandler implements RequestHandler<ConsumerRecords<String, User>, String> { + private static final Logger LOGGER = LoggerFactory.getLogger(AvroKafkaHandler.class); + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_AVRO) + public String handleRequest(ConsumerRecords<String, User> records, Context context) { + for (ConsumerRecord<String, User> record : records) { + User user = record.value(); // User class is auto-generated from Avro schema + LOGGER.info("Processing user: {}, age {}", user.getName(), user.getAge()); + } + return "OK"; + } + } + ``` + +=== "Protocol Buffers" + + ```java hl_lines="18 21" + package org.example; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import org.apache.kafka.clients.consumer.ConsumerRecord; + import org.apache.kafka.clients.consumer.ConsumerRecords; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.kafka.Deserialization; + import software.amazon.lambda.powertools.kafka.DeserializationType; + import software.amazon.lambda.powertools.logging.Logging; + + public class ProtobufKafkaHandler implements RequestHandler<ConsumerRecords<String, UserProto.User>, String> { + private static final Logger LOGGER = LoggerFactory.getLogger(ProtobufKafkaHandler.class); + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_PROTOBUF) + public String handleRequest(ConsumerRecords<String, UserProto.User> records, Context context) { + for (ConsumerRecord<String, UserProto.User> record : records) { + UserProto.User user = record.value(); // UserProto.User class is auto-generated from Protocol Buffer schema + LOGGER.info("Processing user: {}, age {}", user.getName(), user.getAge()); + } + return "OK"; + } + } + ``` + +=== "JSON Messages" + + ```java hl_lines="18 21" + package org.example; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import org.apache.kafka.clients.consumer.ConsumerRecord; + import org.apache.kafka.clients.consumer.ConsumerRecords; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.kafka.Deserialization; + import software.amazon.lambda.powertools.kafka.DeserializationType; + import software.amazon.lambda.powertools.logging.Logging; + + public class JsonKafkaHandler implements RequestHandler<ConsumerRecords<String, User>, String> { + private static final Logger LOGGER = LoggerFactory.getLogger(JsonKafkaHandler.class); + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_JSON) + public String handleRequest(ConsumerRecords<String, User> records, Context context) { + for (ConsumerRecord<String, User> record : records) { + User user = record.value(); // Deserialized JSON object into User POJO + LOGGER.info("Processing user: {}, age {}", user.getName(), user.getAge()); + } + return "OK"; + } + } + ``` + +<!-- prettier-ignore --> +???+ tip "Full examples on GitHub" + A full example including how to generate Avro and Protobuf Java classes can be found on GitHub at [https://github.com/aws-powertools/powertools-lambda-java/tree/main/examples/powertools-examples-kafka](https://github.com/aws-powertools/powertools-lambda-java/tree/main/examples/powertools-examples-kafka). + +### Deserializing keys and values + +The `@Deserialization` annotation deserializes both keys and values based on your type configuration. This flexibility allows you to work with different data formats in the same message. + +=== "Key and Value Deserialization" + + ```java hl_lines="22" + package org.example; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import org.apache.kafka.clients.consumer.ConsumerRecord; + import org.apache.kafka.clients.consumer.ConsumerRecords; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.kafka.Deserialization; + import software.amazon.lambda.powertools.kafka.DeserializationType; + import software.amazon.lambda.powertools.logging.Logging; + + public class KeyValueKafkaHandler implements RequestHandler<ConsumerRecords<ProductKey, ProductInfo>, String> { + private static final Logger LOGGER = LoggerFactory.getLogger(KeyValueKafkaHandler.class); + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_AVRO) + public String handleRequest(ConsumerRecords<ProductKey, ProductInfo> records, Context context) { + for (ConsumerRecord<ProductKey, ProductInfo> record : records) { + // Access both deserialized components + ProductKey key = record.key(); // ProductKey class is auto-generated from Avro schema + ProductInfo product = record.value(); // ProductInfo class is auto-generated from Avro schema + + LOGGER.info("Processing product ID: {}", key.getProductId()); + LOGGER.info("Product: {} - ${}", product.getName(), product.getPrice()); + } + return "OK"; + } + } + ``` + +=== "Value-Only Deserialization" + + ```java hl_lines="22" + package org.example; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import org.apache.kafka.clients.consumer.ConsumerRecord; + import org.apache.kafka.clients.consumer.ConsumerRecords; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.kafka.Deserialization; + import software.amazon.lambda.powertools.kafka.DeserializationType; + import software.amazon.lambda.powertools.logging.Logging; + + public class ValueOnlyKafkaHandler implements RequestHandler<ConsumerRecords<String, Order>, String> { + private static final Logger LOGGER = LoggerFactory.getLogger(ValueOnlyKafkaHandler.class); + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_JSON) + public String handleRequest(ConsumerRecords<String, Order> records, Context context) { + for (ConsumerRecord<String, Order> record : records) { + // Key remains as string (if present) + String key = record.key(); + if (key != null) { + LOGGER.info("Message key: {}", key); + } + + // Value is deserialized as JSON + Order order = record.value(); + LOGGER.info("Order #{} - Total: ${}", order.getOrderId(), order.getTotal()); + } + return "OK"; + } + } + ``` + +### Handling primitive types + +When working with primitive data types (strings, integers, etc.) rather than structured objects, you can use any deserialization type such as `KAFKA_JSON`. Simply place the primitive type like `Integer` or `String` in the `ConsumerRecords` generic type parameters, and the library will automatically handle primitive type deserialization. + +<!-- prettier-ignore --> +???+ tip "Common pattern: Keys with primitive values" + Using primitive types (strings, integers) as Kafka message keys is a common pattern for partitioning and identifying messages. Powertools automatically handles these primitive keys without requiring special configuration, making it easy to implement this popular design pattern. + +=== "Primitive key" + + ```java hl_lines="18 22" + package org.example; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import org.apache.kafka.clients.consumer.ConsumerRecord; + import org.apache.kafka.clients.consumer.ConsumerRecords; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.kafka.Deserialization; + import software.amazon.lambda.powertools.kafka.DeserializationType; + import software.amazon.lambda.powertools.logging.Logging; + + public class PrimitiveKeyHandler implements RequestHandler<ConsumerRecords<Integer, Customer>, String> { + private static final Logger LOGGER = LoggerFactory.getLogger(PrimitiveKeyHandler.class); + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_JSON) + public String handleRequest(ConsumerRecords<Integer, Customer> records, Context context) { + for (ConsumerRecord<Integer, Customer> record : records) { + // Key is automatically deserialized as Integer + Integer key = record.key(); + + // Value is deserialized as JSON + Customer customer = record.value(); + + LOGGER.info("Key: {}", key); + LOGGER.info("Name: {}", customer.getName()); + LOGGER.info("Email: {}", customer.getEmail()); + } + return "OK"; + } + } + ``` + +=== "Primitive key and value" + + ```java hl_lines="18 22" + package org.example; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import org.apache.kafka.clients.consumer.ConsumerRecord; + import org.apache.kafka.clients.consumer.ConsumerRecords; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.kafka.Deserialization; + import software.amazon.lambda.powertools.kafka.DeserializationType; + import software.amazon.lambda.powertools.logging.Logging; + + public class PrimitiveHandler implements RequestHandler<ConsumerRecords<String, String>, String> { + private static final Logger LOGGER = LoggerFactory.getLogger(PrimitiveHandler.class); + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_JSON) + public String handleRequest(ConsumerRecords<String, String> records, Context context) { + for (ConsumerRecord<String, String> record : records) { + // Key is automatically deserialized as String + String key = record.key(); + + // Value is automatically deserialized as String + String value = record.value(); + + LOGGER.info("Key: {}", key); + LOGGER.info("Value: {}", value); + } + return "OK"; + } + } + ``` + +### Message format support and comparison + +The Kafka utility supports multiple serialization formats to match your existing Kafka implementation. Choose the format that best suits your needs based on performance, schema evolution requirements, and ecosystem compatibility. + +<!-- prettier-ignore --> +???+ tip "Selecting the right format" + For new applications, consider Avro or Protocol Buffers over JSON. Both provide schema validation, evolution support, and significantly better performance with smaller message sizes. Avro is particularly well-suited for Kafka due to its built-in schema evolution capabilities. + +=== "Supported Formats" + + | Format | DeserializationType | Description | Required Dependencies | + |--------|---------------------|-------------|----------------------| + | **JSON** | `KAFKA_JSON` | Human-readable text format | Jackson | + | **Avro** | `KAFKA_AVRO` | Compact binary format with schema | Apache Avro | + | **Protocol Buffers** | `KAFKA_PROTOBUF` | Efficient binary format | Protocol Buffers | + | **Lambda Default** | `LAMBDA_DEFAULT` | Uses Lambda's built-in deserialization (equivalent to removing the `@Deserialization` annotation) | None | + +=== "Format Comparison" + + | Feature | JSON | Avro | Protocol Buffers | + |---------|------|------|-----------------| + | **Schema Definition** | Optional | Required schema file | Required .proto file | + | **Schema Evolution** | None | Strong support | Strong support | + | **Size Efficiency** | Low | High | Highest | + | **Processing Speed** | Slower | Fast | Fastest | + | **Human Readability** | High | Low | Low | + | **Implementation Complexity** | Low | Medium | Medium | + | **Additional Dependencies** | None | Apache Avro | Protocol Buffers | + +Choose the serialization format that best fits your needs: + +- **JSON**: Best for simplicity and when schema flexibility is important +- **Avro**: Best for systems with evolving schemas and when compatibility is critical +- **Protocol Buffers**: Best for performance-critical systems with structured data +- **Lambda Default**: Best for simple string-based messages or when using Lambda's built-in deserialization + +## Advanced + +### Accessing record metadata + +Each Kafka record contains important metadata that you can access alongside the deserialized message content. This metadata helps with message processing, troubleshooting, and implementing advanced patterns like exactly-once processing. + +=== "Working with Record Metadata" + + ```java + package org.example; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import org.apache.kafka.clients.consumer.ConsumerRecord; + import org.apache.kafka.clients.consumer.ConsumerRecords; + import org.apache.kafka.common.header.Header; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.kafka.Deserialization; + import software.amazon.lambda.powertools.kafka.DeserializationType; + import software.amazon.lambda.powertools.logging.Logging; + + public class MetadataKafkaHandler implements RequestHandler<ConsumerRecords<String, Customer>, String> { + private static final Logger LOGGER = LoggerFactory.getLogger(MetadataKafkaHandler.class); + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_AVRO) + public String handleRequest(ConsumerRecords<String, Customer> records, Context context) { + for (ConsumerRecord<String, Customer> record : records) { + // Log record coordinates for tracing + LOGGER.info("Processing message from topic '{}'", record.topic()); + LOGGER.info(" Partition: {}, Offset: {}", record.partition(), record.offset()); + LOGGER.info(" Produced at: {}", record.timestamp()); + + // Process message headers + if (record.headers() != null) { + for (Header header : record.headers()) { + LOGGER.info(" Header: {} = {}", + header.key(), new String(header.value())); + } + } + + // Access the Avro deserialized message content + Customer customer = record.value(); // Customer class is auto-generated from Avro schema + LOGGER.info("Processing order for: {}", customer.getName()); + LOGGER.info("Order total: ${}", customer.getOrderTotal()); + } + return "OK"; + } + } + ``` + +#### Available metadata properties + +| Property | Description | Example Use Case | +| ----------------- | ----------------------------------------------- | ------------------------------------------- | +| `topic()` | Topic name the record was published to | Routing logic in multi-topic consumers | +| `partition()` | Kafka partition number | Tracking message distribution | +| `offset()` | Position in the partition | De-duplication, exactly-once processing | +| `timestamp()` | Unix timestamp when record was created | Event timing analysis | +| `timestampType()` | Timestamp type (CREATE_TIME or LOG_APPEND_TIME) | Data lineage verification | +| `headers()` | Key-value pairs attached to the message | Cross-cutting concerns like correlation IDs | +| `key()` | Deserialized message key | Customer ID or entity identifier | +| `value()` | Deserialized message content | The actual business data | + +### Error handling + +Handle errors gracefully when processing Kafka messages to ensure your application maintains resilience and provides clear diagnostic information. The Kafka utility integrates with standard Java exception handling patterns. + +<!-- prettier-ignore --> +!!! info "Treating Deserialization errors" + Read [Deserialization failures](#deserialization-failures). Deserialization failures will fail the whole batch and do not execute your handler. + +=== "Error Handling" + + ```java + package org.example; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import org.apache.kafka.clients.consumer.ConsumerRecord; + import org.apache.kafka.clients.consumer.ConsumerRecords; + import software.amazon.lambda.powertools.kafka.Deserialization; + import software.amazon.lambda.powertools.kafka.DeserializationType; + import software.amazon.lambda.powertools.metrics.FlushMetrics; + import software.amazon.lambda.powertools.metrics.Metrics; + import software.amazon.lambda.powertools.metrics.MetricsFactory; + import software.amazon.lambda.powertools.metrics.model.MetricUnit; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + public class ErrorHandlingKafkaHandler implements RequestHandler<ConsumerRecords<String, Order>, String> { + + private static final Logger LOGGER = LoggerFactory.getLogger(ErrorHandlingKafkaHandler.class); + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Override + @Logging + @FlushMetrics(namespace = "KafkaProcessing", service = "order-processing") + @Deserialization(type = DeserializationType.KAFKA_AVRO) + public String handleRequest(ConsumerRecords<String, Order> records, Context context) { + metrics.addMetric("TotalRecords", records.count(), MetricUnit.COUNT); + int successfulRecords = 0; + int failedRecords = 0; + + for (ConsumerRecord<String, Order> record : records) { + try { + Order order = record.value(); // Order class is auto-generated from Avro schema + processOrder(order); + successfulRecords++; + metrics.addMetric("ProcessedRecords", 1, MetricUnit.COUNT); + } catch (Exception e) { + failedRecords++; + LOGGER.error("Error processing Kafka message from topic: {}, partition: {}, offset: {}", + record.topic(), record.partition(), record.offset(), e); + metrics.addMetric("ProcessingErrors", 1, MetricUnit.COUNT); + // Optionally send to DLQ or error topic + sendToDlq(record); + } + } + + return String.format("Processed %d records successfully, %d failed", + successfulRecords, failedRecords); + } + + private void processOrder(Order order) { + // Your business logic here + LOGGER.info("Processing order: {}", order.getOrderId()); + } + + private void sendToDlq(ConsumerRecord<String, Order> record) { + // Implementation to send failed records to dead letter queue + } + } + ``` + +### Integrating with Idempotency + +When processing Kafka messages in Lambda, failed batches can result in message reprocessing. The idempotency utility prevents duplicate processing by tracking which messages have already been handled, ensuring each message is processed exactly once. + +The Idempotency utility automatically stores the result of each successful operation, returning the cached result if the same message is processed again, which prevents potentially harmful duplicate operations like double-charging customers or double-counting metrics. + +=== "Idempotent Kafka Processing" + + ```java + package org.example; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import org.apache.kafka.clients.consumer.ConsumerRecord; + import org.apache.kafka.clients.consumer.ConsumerRecords; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.kafka.Deserialization; + import software.amazon.lambda.powertools.kafka.DeserializationType; + import software.amazon.lambda.powertools.idempotency.Idempotency; + import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; + import software.amazon.lambda.powertools.idempotency.Idempotent; + import software.amazon.lambda.powertools.idempotency.persistence.dynamodb.DynamoDBPersistenceStore; + import software.amazon.lambda.powertools.logging.Logging; + + public class IdempotentKafkaHandler implements RequestHandler<ConsumerRecords<String, Payment>, String> { + private static final Logger LOGGER = LoggerFactory.getLogger(IdempotentKafkaHandler.class); + + public IdempotentKafkaHandler() { + // Configure idempotency with DynamoDB persistence store + Idempotency.config() + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName("IdempotencyTable") + .build()) + .configure(); + } + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_JSON) + public String handleRequest(ConsumerRecords<String, Payment> records, Context context) { + for (ConsumerRecord<String, Payment> record : records) { + // Payment class deserialized from JSON + Payment payment = record.value(); + + // Process each message with idempotency protection + processPayment(payment); + } + return "OK"; + } + + @Idempotent + private void processPayment(Payment payment) { + LOGGER.info("Processing payment {}", payment.getPaymentId()); + + // Your business logic here + PaymentService.process(payment.getPaymentId(), payment.getCustomerId(), payment.getAmount()); + } + } + ``` + +<!-- prettier-ignore --> +???+ tip "Ensuring exactly-once processing" + The `@Idempotent` annotation will use the JSON representation of the Payment object to make sure that the same object is only processed exactly once. Even if a batch fails and Lambda retries the messages, each unique payment will be processed exactly once. + +### Best practices + +#### Batch size configuration + +The number of Kafka records processed per Lambda invocation is controlled by your Event Source Mapping configuration. Properly sized batches optimize cost and performance. + +=== "Batch size configuration" + + ```yaml + Resources: + OrderProcessingFunction: + Type: AWS::Serverless::Function + Properties: + Handler: org.example.OrderHandler::handleRequest + Runtime: java21 + Events: + KafkaEvent: + Type: MSK + Properties: + Stream: !GetAtt OrdersMSKCluster.Arn + Topics: + - order-events + - payment-events + # Configuration for optimal throughput/latency balance + BatchSize: 100 + MaximumBatchingWindowInSeconds: 5 + StartingPosition: LATEST + # Enable partial batch success reporting + FunctionResponseTypes: + - ReportBatchItemFailures + ``` + +Different workloads benefit from different batch configurations: + +- **High-volume, simple processing**: Use larger batches (100-500 records) with short timeout +- **Complex processing with database operations**: Use smaller batches (10-50 records) +- **Mixed message sizes**: Set appropriate batching window (1-5 seconds) to handle variability + +#### Cross-language compatibility + +When using binary serialization formats across multiple programming languages, ensure consistent schema handling to prevent deserialization failures. + +=== "Using Python naming convention" + + ```java hl_lines="33 36 39 42 56" + package org.example; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import org.apache.kafka.clients.consumer.ConsumerRecord; + import org.apache.kafka.clients.consumer.ConsumerRecords; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import software.amazon.lambda.powertools.kafka.Deserialization; + import software.amazon.lambda.powertools.kafka.DeserializationType; + import software.amazon.lambda.powertools.logging.Logging; + import com.fasterxml.jackson.annotation.JsonProperty; + import java.time.Instant; + + public class CrossLanguageKafkaHandler implements RequestHandler<ConsumerRecords<String, OrderEvent>, String> { + private static final Logger LOGGER = LoggerFactory.getLogger(CrossLanguageKafkaHandler.class); + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_JSON) + public String handleRequest(ConsumerRecords<String, OrderEvent> records, Context context) { + for (ConsumerRecord<String, OrderEvent> record : records) { + OrderEvent order = record.value(); // OrderEvent class handles JSON with Python field names + LOGGER.info("Processing order {} from {}", + order.getOrderId(), order.getOrderDate()); + } + return "OK"; + } + } + + // Example class that handles Python snake_case field names + public class OrderEvent { + @JsonProperty("order_id") + private String orderId; + + @JsonProperty("customer_id") + private String customerId; + + @JsonProperty("total_amount") + private double totalAmount; + + @JsonProperty("order_date") + private long orderDateMillis; + + // Getters and setters + public String getOrderId() { return orderId; } + public void setOrderId(String orderId) { this.orderId = orderId; } + + public String getCustomerId() { return customerId; } + public void setCustomerId(String customerId) { this.customerId = customerId; } + + public double getTotalAmount() { return totalAmount; } + public void setTotalAmount(double totalAmount) { this.totalAmount = totalAmount; } + + public Instant getOrderDate() { + return Instant.ofEpochMilli(orderDateMillis); + } + public void setOrderDate(long orderDateMillis) { + this.orderDateMillis = orderDateMillis; + } + } + ``` + +Common cross-language challenges to address: + +- **Field naming conventions**: camelCase in Java vs snake_case in Python +- **Date/time**: representation differences +- **Numeric precision handling**: especially decimals + +### Troubleshooting + +#### Deserialization failures + +The Java Kafka utility registers a [custom Lambda serializer](https://docs.aws.amazon.com/lambda/latest/dg/java-custom-serialization.html) that performs **eager deserialization** of all records in the batch before your handler method is invoked. + +This means that if any record in the batch fails deserialization, a `RuntimeException` will be thrown with a concrete error message explaining why deserialization failed, and your handler method will never be called. + +**Key implications:** + +- **Batch-level failure**: If one record fails deserialization, the entire batch fails +- **Early failure detection**: Deserialization errors are caught before your business logic runs +- **Clear error messages**: The `RuntimeException` provides specific details about what went wrong +- **No partial processing**: You cannot process some records while skipping failed ones within the same batch + +**Example of deserialization failure:** + +```java +// If any record in the batch has invalid Avro data, you'll see: +// RuntimeException: Failed to deserialize Kafka record: Invalid Avro schema for record at offset 12345 +``` + +<!-- prettier-ignore --> +!!! warning "Handler method not invoked on deserialization failure" + When deserialization fails, your `handleRequest` method will not be invoked at all. The `RuntimeException` is thrown before your handler code runs, preventing any processing of the batch. + +**Handling deserialization failures:** + +Since deserialization happens before your handler is called, you cannot catch these exceptions within your handler method. Instead, configure your Event Source Mapping with appropriate error handling: + +- **Dead Letter Queue (DLQ)**: Configure a DLQ to capture failed batches for later analysis +- **Maximum Retry Attempts**: Set appropriate retry limits to avoid infinite retries +- **Batch Size**: Use smaller batch sizes to minimize the impact of individual record failures + +```yaml +# Example SAM template configuration for error handling +Events: + KafkaEvent: + Type: MSK + Properties: + # ... other properties + BatchSize: 10 # Smaller batches reduce failure impact + MaximumRetryAttempts: 3 + DestinationConfig: + OnFailure: + Type: SQS + Destination: !GetAtt DeadLetterQueue.Arn +``` + +#### Schema compatibility issues + +Schema compatibility issues often manifest as successful connections but failed deserialization. Common causes include: + +- **Schema evolution without backward compatibility**: New producer schema is incompatible with consumer schema +- **Field type mismatches**: For example, a field changed from String to Integer across systems +- **Missing required fields**: Fields required by the consumer schema but absent in the message +- **Default value discrepancies**: Different handling of default values between languages + +When using Schema Registry, verify schema compatibility rules are properly configured for your topics and that all applications use the same registry. + +#### Memory and timeout optimization + +Lambda functions processing Kafka messages may encounter resource constraints, particularly with large batches or complex processing logic. + +For memory errors: + +- Increase Lambda memory allocation, which also provides more CPU resources +- Process fewer records per batch by adjusting the `BatchSize` parameter in your event source mapping +- Consider optimizing your message format to reduce memory footprint + +For timeout issues: + +- Extend your Lambda function timeout setting to accommodate processing time +- Implement chunked or asynchronous processing patterns for time-consuming operations +- Monitor and optimize database operations, external API calls, or other I/O operations in your handler + +<!-- prettier-ignore --> +???+ tip "Monitoring memory usage" + Use CloudWatch metrics to track your function's memory utilization. If it consistently exceeds 80% of allocated memory, consider increasing the memory allocation or optimizing your code. + +## Kafka workflow + +### Using ESM with Schema Registry validation (SOURCE) + +<center> +```mermaid +sequenceDiagram + participant Kafka + participant ESM as Event Source Mapping + participant SchemaRegistry as Schema Registry + participant Lambda + participant KafkaUtility + participant YourCode + Kafka->>+ESM: Send batch of records + ESM->>+SchemaRegistry: Validate schema + SchemaRegistry-->>-ESM: Confirm schema is valid + ESM->>+Lambda: Invoke with validated records (still encoded) + Lambda->>+KafkaUtility: Pass Kafka event + KafkaUtility->>KafkaUtility: Parse event structure + loop For each record + KafkaUtility->>KafkaUtility: Decode base64 data + KafkaUtility->>KafkaUtility: Deserialize based on DeserializationType + end + KafkaUtility->>+YourCode: Provide ConsumerRecords + YourCode->>YourCode: Process records + YourCode-->>-KafkaUtility: Return result + KafkaUtility-->>-Lambda: Pass result back + Lambda-->>-ESM: Return response + ESM-->>-Kafka: Acknowledge processed batch +``` +</center> + +### Using ESM with Schema Registry deserialization (JSON) + +<center> +```mermaid +sequenceDiagram + participant Kafka + participant ESM as Event Source Mapping + participant SchemaRegistry as Schema Registry + participant Lambda + participant KafkaUtility + participant YourCode + Kafka->>+ESM: Send batch of records + ESM->>+SchemaRegistry: Validate and deserialize + SchemaRegistry->>SchemaRegistry: Deserialize records + SchemaRegistry-->>-ESM: Return deserialized data + ESM->>+Lambda: Invoke with pre-deserialized JSON records + Lambda->>+KafkaUtility: Pass Kafka event + KafkaUtility->>KafkaUtility: Parse event structure + loop For each record + KafkaUtility->>KafkaUtility: Decode base64 data + KafkaUtility->>KafkaUtility: Record is already deserialized + KafkaUtility->>KafkaUtility: Map to POJO (if specified) + end + KafkaUtility->>+YourCode: Provide ConsumerRecords + YourCode->>YourCode: Process records + YourCode-->>-KafkaUtility: Return result + KafkaUtility-->>-Lambda: Pass result back + Lambda-->>-ESM: Return response + ESM-->>-Kafka: Acknowledge processed batch +``` +</center> + +### Using ESM without Schema Registry integration + +<center> +```mermaid +sequenceDiagram + participant Kafka + participant Lambda + participant KafkaUtility + participant YourCode + Kafka->>+Lambda: Invoke with batch of records (direct integration) + Lambda->>+KafkaUtility: Pass raw Kafka event + KafkaUtility->>KafkaUtility: Parse event structure + loop For each record + KafkaUtility->>KafkaUtility: Decode base64 data + KafkaUtility->>KafkaUtility: Deserialize based on DeserializationType + end + KafkaUtility->>+YourCode: Provide ConsumerRecords + YourCode->>YourCode: Process records + YourCode-->>-KafkaUtility: Return result + KafkaUtility-->>-Lambda: Pass result back + Lambda-->>-Kafka: Acknowledge processed batch +``` +</center> + +## Testing your code + +Testing Kafka consumer functions is straightforward with JUnit. You can construct Kafka `ConsumerRecords` in the default way provided by the kafka-clients library without needing a real Kafka cluster. + +=== "Testing your code" + + ```java + package org.example; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.events.KafkaEvent; + import org.apache.kafka.clients.consumer.ConsumerRecord; + import org.apache.kafka.clients.consumer.ConsumerRecords; + import org.apache.kafka.common.TopicPartition; + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + import java.util.*; + + import static org.junit.jupiter.api.Assertions.*; + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class KafkaHandlerTest { + + @Mock + private Context context; + + @Test + void testProcessJsonMessage() { + // Create a test Kafka event with JSON data + Order testOrder = new Order("12345", 99.95); + ConsumerRecord<String, Order> record = new ConsumerRecord<>( + "orders-topic", 0, 15L, null, testOrder); + + Map<TopicPartition, List<ConsumerRecord<String, Order>>> recordsMap = new HashMap<>(); + recordsMap.put(new TopicPartition("orders-topic", 0), Arrays.asList(record)); + ConsumerRecords<String, Order> records = new ConsumerRecords<>(recordsMap); + + // Create handler and invoke + JsonKafkaHandler handler = new JsonKafkaHandler(); + String response = handler.handleRequest(records, context); + + // Verify the response + assertEquals("OK", response); + } + + @Test + void testProcessMultipleRecords() { + // Create a test event with multiple records + Customer customer1 = new Customer("A1", "Alice"); + Customer customer2 = new Customer("B2", "Bob"); + + List<ConsumerRecord<String, Customer>> recordList = Arrays.asList( + new ConsumerRecord<>("customers-topic", 0, 10L, null, customer1), + new ConsumerRecord<>("customers-topic", 0, 11L, null, customer2) + ); + + Map<TopicPartition, List<ConsumerRecord<String, Customer>>> recordsMap = new HashMap<>(); + recordsMap.put(new TopicPartition("customers-topic", 0), recordList); + ConsumerRecords<String, Customer> records = new ConsumerRecords<>(recordsMap); + + // Create handler and invoke + JsonKafkaHandler handler = new JsonKafkaHandler(); + String response = handler.handleRequest(records, context); + + // Verify the response + assertEquals("OK", response); + } + } + ``` + +## Extra Resources + +### Lambda Custom Serializers Compatibility + +This Kafka utility uses [Lambda custom serializers](https://docs.aws.amazon.com/lambda/latest/dg/java-custom-serialization.html) to provide automatic deserialization of Kafka messages. + +**Important compatibility considerations:** + +- **Existing custom serializers**: This utility will not be compatible if you already use your own custom Lambda serializer in your project +- **Non-Kafka handlers**: Installing this library will not affect default Lambda serialization behavior for non-Kafka related handlers +- **Kafka-specific**: The custom serialization only applies to handlers annotated with `@Deserialization` +- **Lambda default fallback**: Using `@Deserialization(type = DeserializationType.LAMBDA_DEFAULT)` will proxy to Lambda's default serialization behavior + +**Need help with compatibility?** + +If you are blocked from adopting this utility due to existing custom serializers or other compatibility concerns, please contact us with your specific use-cases. We'd like to understand your requirements and explore potential solutions. + +For more information about Lambda custom serialization, see the [official AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/java-custom-serialization.html). diff --git a/docs/utilities/large_messages.md b/docs/utilities/large_messages.md new file mode 100644 index 000000000..9d14c8228 --- /dev/null +++ b/docs/utilities/large_messages.md @@ -0,0 +1,494 @@ +--- +title: Large Messages +description: Utility +--- + +The large message utility handles SQS and SNS messages which have had their payloads +offloaded to S3 if they are larger than the maximum allowed size (1 MB). + +## Features + +- Automatically retrieve the content of S3 objects when SQS or SNS messages have been offloaded to S3. +- Automatically delete the S3 Objects after processing succeeds. +- Compatible with the batch module (with SQS). + +## Background + +```mermaid +stateDiagram-v2 + direction LR + Function : Lambda Function + + state Application { + direction TB + sendMsg: sendMessage(QueueUrl, MessageBody) + extendLib: extended-client-lib + [*] --> sendMsg + sendMsg --> extendLib + state extendLib { + state if_big <<choice>> + bigMsg: MessageBody > 1MB ? + putObject: putObject(S3Bucket, S3Key, Body) + updateMsg: Update MessageBody<br>with a pointer to S3<br>and add a message attribute + bigMsg --> if_big + if_big --> [*]: size(body) <= 1MB + if_big --> putObject: size(body) > 1MB + putObject --> updateMsg + updateMsg --> [*] + } + } + + state Function { + direction TB + iterateMsgs: Iterate over messages + ptLargeMsg: powertools-large-messages + [*] --> Handler + Handler --> iterateMsgs + iterateMsgs --> ptLargeMsg + state ptLargeMsg { + state if_pointer <<choice>> + pointer: Message attribute <br>for large message ? + normalMsg: Small message,<br>body left unchanged + getObject: getObject(S3Pointer) + deleteObject: deleteObject(S3Pointer) + updateBody: Update message body<br>with content from S3 object<br>and remove message attribute + updateMD5: Update MD5 of the body<br>and attributes (SQS only) + yourcode: <b>YOUR CODE HERE!</b> + pointer --> if_pointer + if_pointer --> normalMsg : False + normalMsg --> [*] + if_pointer --> getObject : True + getObject --> updateBody + updateBody --> updateMD5 + updateMD5 --> yourcode + yourcode --> deleteObject + deleteObject --> [*] + } + } + + [*] --> Application + Application --> Function : Lambda Invocation + Function --> [*] + +``` + +SQS and SNS message payload is limited to 1MB. If you wish to send messages with a larger payload, you can leverage the +[amazon-sqs-java-extended-client-lib](https://github.com/awslabs/amazon-sqs-java-extended-client-lib) +or [amazon-sns-java-extended-client-lib](https://github.com/awslabs/amazon-sns-java-extended-client-lib) which +offload the message to Amazon S3. See documentation +([SQS](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-s3-messages.html) +/ [SNS](https://docs.aws.amazon.com/sns/latest/dg/large-message-payloads.html)) + +When offloaded to S3, the message contains a specific message attribute and the payload only contains a pointer to the +S3 object (bucket and object key). + +This utility automatically retrieves messages which have been offloaded to S3 using the +extended client libraries. Once a message's payload has been processed successfully, the +utility deletes the payload from S3. + +This utility is compatible with +versions *[1.1.0+](https://github.com/awslabs/amazon-sqs-java-extended-client-lib/releases/tag/1.1.0)* and *[2.0.0+](https://github.com/awslabs/amazon-sqs-java-extended-client-lib/releases/tag/2.0.0)* +of [amazon-sqs-java-extended-client-lib](https://github.com/awslabs/amazon-sqs-java-extended-client-lib) / [amazon-sns-java-extended-client-lib](https://github.com/awslabs/amazon-sns-java-extended-client-lib). + +## Install + +=== "Maven" + + ```xml hl_lines="3-7 25-28" + <dependencies> + ... + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-large-messages</artifactId> + <version>{{ powertools.version }}</version> + </dependency> + ... + </dependencies> + ... + <!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project --> + <!-- Note: This AspectJ configuration is not needed when using the functional approach --> + <build> + <plugins> + ... + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14</version> + <configuration> + <source>11</source> <!-- or higher --> + <target>11</target> <!-- or higher --> + <complianceLevel>11</complianceLevel> <!-- or higher --> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-large-messages</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <!-- AspectJ compiler version, in sync with runtime --> + <version>1.9.22</version> + </dependency> + </dependencies> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + ... + </plugins> + </build> + ``` + +=== "Gradle" + + ```groovy hl_lines="3 11" + plugins { + id 'java' + id 'io.freefair.aspectj.post-compile-weaving' version '8.1.0' // Not needed when using the functional approach + } + + repositories { + mavenCentral() + } + + dependencies { + aspect 'software.amazon.lambda:powertools-large-messages:{{ powertools.version }}' // Use 'implementation' instead of 'aspect' when using the functional approach + } + + sourceCompatibility = 11 // or higher + targetCompatibility = 11 // or higher + ``` + +## Permissions + +As the utility interacts with Amazon S3, the lambda function must have the following permissions +on the S3 bucket used for the large messages offloading: + +- `s3:GetObject` +- `s3:DeleteObject` + +## Usage + +You can use the Large Messages utility with either the `@LargeMessage` annotation or the functional API. + +The `@LargeMessage` annotation can be used on any method where the *first* parameter is one of: + +- `SQSEvent.SQSMessage` +- `SNSEvent.SNSRecord` + +The functional API `LargeMessages.processLargeMessage()` accepts the same message types. + +### Basic usage + +=== "@LargeMessage annotation - SQS" + + ```java hl_lines="8 13 15" + import software.amazon.lambda.powertools.largemessages.LargeMessage; + + public class SqsMessageHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + + @Override + public SQSBatchResponse handleRequest(SQSEvent event, Context context) { + for (SQSMessage message: event.getRecords()) { + processRawMessage(message, context); + } + return SQSBatchResponse.builder().build(); + } + + @LargeMessage + private void processRawMessage(SQSEvent.SQSMessage sqsMessage, Context context) { + // sqsMessage.getBody() will contain the content of the S3 object + } + } + ``` + +=== "Functional API - SQS" + + ```java hl_lines="1 8" + import software.amazon.lambda.powertools.largemessages.LargeMessages; + + public class SqsMessageHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + + @Override + public SQSBatchResponse handleRequest(SQSEvent event, Context context) { + for (SQSMessage message: event.getRecords()) { + LargeMessages.processLargeMessage(message, this::processRawMessage); + } + return SQSBatchResponse.builder().build(); + } + + private void processRawMessage(SQSEvent.SQSMessage sqsMessage) { + // sqsMessage.getBody() will contain the content of the S3 object + } + } + ``` + +=== "@LargeMessage annotation - SNS" + + ```java hl_lines="7 11 13" + import software.amazon.lambda.powertools.largemessages.LargeMessage; + + public class SnsRecordHandler implements RequestHandler<SNSEvent, String> { + + @Override + public String handleRequest(SNSEvent event, Context context) { + processSNSRecord(event.records.get(0)); // there are always only one message + return "Hello World"; + } + + @LargeMessage + private void processSNSRecord(SNSEvent.SNSRecord snsRecord) { + // snsRecord.getSNS().getMessage() will contain the content of the S3 object + } + } + ``` + +=== "Functional API - SNS" + + ```java hl_lines="1 7" + import software.amazon.lambda.powertools.largemessages.LargeMessages; + + public class SnsRecordHandler implements RequestHandler<SNSEvent, String> { + + @Override + public String handleRequest(SNSEvent event, Context context) { + return LargeMessages.processLargeMessage(event.records.get(0), this::processSNSRecord); + } + + private String processSNSRecord(SNSEvent.SNSRecord snsRecord) { + // snsRecord.getSNS().getMessage() will contain the content of the S3 object + return "Hello World"; + } + } + ``` + +When the Lambda function is invoked with a SQS or SNS event, the utility first +checks if the content was offloaded to S3. In the case of a large message, there is a message attribute +specifying the size of the offloaded message and the message contains a pointer to the S3 object. + +If this is the case, the utility will retrieve the object from S3 using the `getObject(bucket, key)` API, +and place the content of the object in the message payload. You can then directly use the content of the message. +If there was an error during the S3 download, the function will fail with a `LargeMessageProcessingException`. + +After your code is invoked and returns without error, the object is deleted from S3 +using the `deleteObject(bucket, key)` API. You can disable the deletion of S3 objects: + +=== "@LargeMessage annotation" + ```java + @LargeMessage(deleteS3Object = false) + private void processRawMessage(SQSEvent.SQSMessage sqsMessage) { + // do something with the message + } + ``` + +=== "Functional API" + ```java + LargeMessages.processLargeMessage(message, this::processRawMessage, false); + ``` + +!!! tip "Use together with batch module" + This utility works perfectly together with the batch module (`powertools-batch`), especially for SQS: + + === "@LargeMessage annotation" + ```java hl_lines="2 5-7 12 15 16" title="Combining batch and large message modules" + public class SqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; + + public SqsBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithRawMessageHandler(this::processMessage); + } + + @Override + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + return handler.processBatch(sqsEvent, context); + } + + @LargeMessage + private void processMessage(SQSEvent.SQSMessage sqsMessage) { + // do something with the message + } + } + ``` + + === "Functional API" + ```java hl_lines="7-9 14 18" title="Combining batch and large message modules" + import software.amazon.lambda.powertools.largemessages.LargeMessages; + + public class SqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; + + public SqsBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithRawMessageHandler(this::processMessage); + } + + @Override + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + return handler.processBatch(sqsEvent, context); + } + + private void processMessage(SQSEvent.SQSMessage sqsMessage) { + LargeMessages.processLargeMessage(sqsMessage, this::handleProcessedMessage); + } + + private void handleProcessedMessage(SQSEvent.SQSMessage processedMessage) { + // do something with the message + } + } + ``` + +!!! tip "Use together with idempotency module" + When using the `@LargeMessage` annotation, you can combine it with the `@Idempotent` annotation on the same method. + The `@Idempotent` takes precedence over the `@LargeMessage` annotation, meaning the Idempotency module will use the initial raw message (containing the S3 pointer) and not the large message. + + When using the functional API, call `LargeMessages.processLargeMessage()` from within the `@Idempotent` method to ensure idempotency is based on the S3 pointer, not the unwrapped large blob. + + === "@LargeMessage annotation" + ```java hl_lines="6 23-25" title="Combining idempotency and large message modules" + public class SqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + + public SqsBatchHandler() { + Idempotency.config().withConfig( + IdempotencyConfig.builder() + .withEventKeyJMESPath("body") // get the body of the message for the idempotency key + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("IDEMPOTENCY_TABLE")) + .build() + ).configure(); + } + + @Override + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + for (SQSMessage message: event.getRecords()) { + processRawMessage(message, context); + } + return SQSBatchResponse.builder().build(); + } + + @Idempotent + @LargeMessage + private String processRawMessage(@IdempotencyKey SQSEvent.SQSMessage sqsMessage, Context context) { + // do something with the message + } + } + ``` + + === "Functional API" + ```java hl_lines="8 25 27" title="Combining idempotency and large message modules" + import software.amazon.lambda.powertools.largemessages.LargeMessages; + + public class SqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + + public SqsBatchHandler() { + Idempotency.config().withConfig( + IdempotencyConfig.builder() + .withEventKeyJMESPath("body") // get the body of the message for the idempotency key + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("IDEMPOTENCY_TABLE")) + .build() + ).configure(); + } + + @Override + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + for (SQSMessage message: event.getRecords()) { + processRawMessage(message, context); + } + return SQSBatchResponse.builder().build(); + } + + @Idempotent + private String processRawMessage(@IdempotencyKey SQSEvent.SQSMessage sqsMessage, Context context) { + return LargeMessages.processLargeMessage(sqsMessage, this::handleProcessedMessage); + } + + private String handleProcessedMessage(SQSEvent.SQSMessage processedMessage) { + // do something with the message + } + } + ``` + +## Customizing S3 client configuration + +To interact with S3, the utility creates a default S3 Client: + +=== "Default S3 Client" + ```java + S3Client client = S3Client.builder() + .httpClient(UrlConnectionHttpClient.builder().build()) + .region(Region.of(System.getenv(AWS_REGION_ENV))) + .build(); + ``` + +If you need to customize this `S3Client`, you can leverage the `LargeMessageConfig` singleton. This works with both the annotation and functional API: + +=== "@LargeMessage annotation" + ```java hl_lines="6" + import software.amazon.lambda.powertools.largemessages.LargeMessage; + + public class SnsRecordHandler implements RequestHandler<SNSEvent, String> { + + public SnsRecordHandler() { + LargeMessageConfig.init().withS3Client(/* put your custom S3Client here */); + } + + @Override + public String handleRequest(SNSEvent event, Context context) { + processSNSRecord(event.records.get(0)); + return "Hello World"; + } + + @LargeMessage + private void processSNSRecord(SNSEvent.SNSRecord snsRecord) { + // snsRecord.getSNS().getMessage() will contain the content of the S3 object + } + } + ``` + +=== "Functional API" + ```java hl_lines="1 6" + import software.amazon.lambda.powertools.largemessages.LargeMessages; + + public class SnsRecordHandler implements RequestHandler<SNSEvent, String> { + + public SnsRecordHandler() { + LargeMessageConfig.init().withS3Client(/* put your custom S3Client here */); + } + + @Override + public String handleRequest(SNSEvent event, Context context) { + return LargeMessages.processLargeMessage(event.records.get(0), this::processSNSRecord); + } + + private String processSNSRecord(SNSEvent.SNSRecord snsRecord) { + // snsRecord.getSNS().getMessage() will contain the content of the S3 object + return "Hello World"; + } + } + ``` + +## Migration from the SQS Large Message utility + +- Replace the dependency in maven / gradle: `powertools-sqs` ==> `powertools-large-messages` +- Replace the annotation: `@SqsLargeMessage` ==> `@LargeMessage` (the new module handles both SQS and SNS) +- Move the annotation away from the Lambda `handleRequest` method and put it on a method with `SQSEvent.SQSMessage` + or `SNSEvent.SNSRecord` as first parameter. +- The annotation now handles a single message, contrary to the previous version that was handling the complete batch. + It gives more control, especially when dealing with partial failures with SQS (see the batch module). +- The new module only provides an annotation, an equivalent to the `SqsUtils` class is not available anymore in this new version. + +Finally, if you are still using the `powertools-sqs` library for batch processing, consider moving to `powertools-batch` at the same time to remove the dependency on this library completely; it has been deprecated and will be removed in v2. diff --git a/docs/utilities/parameters.md b/docs/utilities/parameters.md index e2d0cb965..6de47df68 100644 --- a/docs/utilities/parameters.md +++ b/docs/utilities/parameters.md @@ -4,98 +4,189 @@ description: Utility --- -The parameters utility provides a way to retrieve parameter values from -[AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) or -[AWS Secrets Manager](https://aws.amazon.com/secrets-manager/). It also provides a base class to create your parameter provider implementation. - -**Key features** - -* Retrieve one or multiple parameters from the underlying provider +The parameters utilities provide a way to retrieve parameter values from +[AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html), +[AWS Secrets Manager](https://aws.amazon.com/secrets-manager/), [Amazon DynamoDB](https://aws.amazon.com/dynamodb/), +or [AWS AppConfig](https://aws.amazon.com/systems-manager/features/appconfig/). + +## Key features + +* Retrieve one or multiple parameters from an underlying provider in a standard way * Cache parameter values for a given amount of time (defaults to 5 seconds) * Transform parameter values from JSON or base 64 encoded strings +* GraalVM support ## Install +In order to provide lightweight dependencies, each parameters module is available as its own +package: + +* **Secrets Manager** - `powertools-parameters-secrets` +* **SSM Parameter Store** - `powertools-parameters-ssm` +* **Amazon DynamoDB** -`powertools-parameters-dynamodb` +* **AWS AppConfig** - `powertools-parameters-appconfig` -To install this utility, add the following dependency to your project. +You can easily mix and match parameter providers within the same project for different needs. + +Note that you must provide the concrete parameters module you want to use below - see the TODOs! === "Maven" - ```xml - <dependency> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-parameters</artifactId> - <version>{{ powertools.version }}</version> - </dependency> + ```xml hl_lines="4-12 17 24 30-34" + <dependencies> + ... + <dependency> + <groupId>software.amazon.lambda</groupId> + + <!-- TODO! Provide the parameters module you want to use here --> + <artifactId>powertools-parameters-secrets</artifactId> + <artifactId>powertools-parameters-ssm</artifactId> + <artifactId>powertools-parameters-dynamodb</artifactId> + <artifactId>powertools-parameters-appconfig</artifactId> + + <version>{{ powertools.version }}</version> + </dependency> + ... + </dependencies> + ... + <!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project --> + <!-- Note: This AspectJ configuration is not needed when using the provider classes directly (without annotations) --> + <build> + <plugins> + ... + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14</version> + <configuration> + <source>11</source> <!-- or higher --> + <target>11</target> <!-- or higher --> + <complianceLevel>11</complianceLevel> <!-- or higher --> + <aspectLibraries> + <!-- TODO! Provide an aspectLibrary for each of the parameters module(s) you want to use here --> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters-secrets</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <!-- AspectJ compiler version, in sync with runtime --> + <version>1.9.22</version> + </dependency> + </dependencies> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + ... + </plugins> + </build> ``` + === "Gradle" - ```groovy - dependencies { - ... - aspect 'software.amazon.lambda:powertools-parameters:{{ powertools.version }}' - } + ```groovy hl_lines="3 11-13" + plugins { + id 'java' + id 'io.freefair.aspectj.post-compile-weaving' version '8.1.0' // Not needed when using provider classes directly (without annotations) + } + + repositories { + mavenCentral() + } + + dependencies { + // TODO! Provide the parameters module you want to use here + aspect 'software.amazon.lambda:powertools-parameters-secrets:{{ powertools.version }}' // Not needed when using provider classes directly (without annotations) + implementation 'software.amazon.lambda:powertools-parameters-secrets:{{ powertools.version }}' // Use this instead of 'aspect' when using provider classes directly + } + + sourceCompatibility = 11 // or higher + targetCompatibility = 11 // or higher ``` **IAM Permissions** This utility requires additional permissions to work as expected. See the table below: -Provider | Function/Method | IAM Permission -------------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------- -SSM Parameter Store | `SSMProvider.get(String)` `SSMProvider.get(String, Class)` | `ssm:GetParameter` -SSM Parameter Store | `SSMProvider.getMultiple(String)` | `ssm:GetParametersByPath` -Secrets Manager | `SecretsProvider.get(String)` `SecretsProvider.get(String, Class)` | `secretsmanager:GetSecretValue` +| Provider | Function/Method | IAM Permission | +|-----------|-------------------------------------------------------------------------|---------------------------------------------------------------------------| +| SSM | `SSMProvider.get(String)` `SSMProvider.get(String, Class)` | `ssm:GetParameter` | +| SSM | `SSMProvider.getMultiple(String)` | `ssm:GetParametersByPath` | +| SSM | If using `withDecryption(true)` | You must add an additional permission `kms:Decrypt` | +| Secrets | `SecretsProvider.get(String)` `SecretsProvider.get(String, Class)` | `secretsmanager:GetSecretValue` | +| DynamoDB | `DynamoDBProvider.get(String)` `DynamoDBProvider.getMultiple(string)` | `dynamodb:GetItem` `dynamoDB:Query` | +| AppConfig | `AppConfigProvider.get(String)` `AppConfigProvider.getMultiple(string)` | `appconfig:StartConfigurationSession`, `appConfig:GetLatestConfiguration` | -## SSM Parameter Store +## Retrieving Parameters +You can retrieve parameters using either annotations or provider classes directly: -You can retrieve a single parameter using SSMProvider.get() and pass the key of the parameter. -For multiple parameters, you can use SSMProvider.getMultiple() and pass the path to retrieve them all. +- **Annotations** (e.g., `@SecretsParam`, `@SSMParam`) - Simpler syntax with field injection, but requires AspectJ configuration +- **Provider classes** (e.g., `SecretsProvider`, `SSMProvider`) - No AspectJ required, useful when you need to configure the underlying SDK client (e.g., different region or credentials), or prefer avoiding AspectJ setup -Alternatively, you can retrieve an instance of a provider and configure its underlying SDK client, -in order to get data from other regions or use specific credentials. +## Built-in provider classes -=== "SSMProvider" +This section describes the built-in provider classes for each parameter store. For each provider, examples are shown for both the annotation-based approach and the provider class approach. In cases where a provider supports extra features, these will also be described. - ```java hl_lines="6" - import software.amazon.lambda.powertools.parameters.SSMProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; +### Secrets Manager - public class AppWithSSM implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - // Get an instance of the SSM Provider - SSMProvider ssmProvider = ParamManager.getSsmProvider(); - - // Retrieve a single parameter - String value = ssmProvider.get("/my/parameter"); - - // Retrieve multiple parameters from a path prefix - // This returns a Map with the parameter name as key - Map<String, String> values = ssmProvider.getMultiple("/my/path/prefix"); +=== "@SecretsParam annotation" + + ```java hl_lines="8 9" + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.lambda.powertools.parameters.secrets.SecretsParam; + public class ParametersFunction implements RequestHandler<String, String> { + + // Annotation-style injection from secrets manager + @SecretsParam(key = "/powertools-java/userpwd") + String secretParam; + + public string handleRequest(String request, Context context) { + // ... do something with the secretParam here + return "something"; + } } ``` -=== "SSMProvider with an explicit region" +=== "SecretsProvider class" - ```java hl_lines="5 7" - import software.amazon.lambda.powertools.parameters.SSMProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; + ```java hl_lines="12-15 19" + import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; + + import com.amazonaws.services.lambda.runtime.Context; + import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; + import software.amazon.lambda.powertools.parameters.secrets.SecretsProvider; + import com.amazonaws.services.lambda.runtime.RequestHandler; - public class AppWithSSM implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - SsmClient client = SsmClient.builder().region(Region.EU_CENTRAL_1).build(); - // Get an instance of the SSM Provider - SSMProvider ssmProvider = ParamManager.getSsmProvider(client); + public class RequestHandlerWithParams implements RequestHandler<String, String> { - // Retrieve a single parameter - String value = ssmProvider.get("/my/parameter"); + // Get an instance of the SecretsProvider. We can provide a custom client here if we want, + // for instance to use a particular region. + SecretsProvider secretsProvider = SecretsProvider + .builder() + .withClient(SecretsManagerClient.builder().build()) + .build(); - // Retrieve multiple parameters from a path prefix - // This returns a Map with the parameter name as key - Map<String, String> values = ssmProvider.getMultiple("/my/path/prefix"); + public String handleRequest(String input, Context context) { + // Retrieve a single secret + String value = secretsProvider.get("/my/secret"); + // ... do something with the secretParam here + return "something"; + } } ``` -### Additional arguments +### SSM Parameter Store The AWS Systems Manager Parameter Store provider supports two additional arguments for the `get()` and `getMultiple()` methods: @@ -104,17 +195,66 @@ The AWS Systems Manager Parameter Store provider supports two additional argumen | **withDecryption()** | `False` | Will automatically decrypt the parameter. | | **recursive()** | `False` | For `getMultiple()` only, will fetch all parameter values recursively based on a path prefix. | -**Example:** -=== "AppWithSSM.java" +=== "@SSMParam annotation" + + ```java hl_lines="8 9" + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.lambda.powertools.parameters.ssm.SSMParam; + + public class ParametersFunction implements RequestHandler<String, String> { - ```java hl_lines="9 12" - import software.amazon.lambda.powertools.parameters.SSMProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; + // Annotation-style injection from SSM Parameter Store + @SSMParam(key = "/powertools-java/param") + String ssmParam; + + public string handleRequest(String request, Context context) { + return ssmParam; // Request handler simply returns our configuration value + } + } + ``` + +=== "SSMProvider class" + + ```java hl_lines="12-15 19-20 22" + import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.awssdk.services.ssm.SsmClient; + import software.amazon.lambda.powertools.parameters.ssm.SSMProvider; + + public class RequestHandlerWithParams implements RequestHandler<String, String> { + + // Get an instance of the SSMProvider. We can provide a custom client here if we want, + // for instance to use a particular region. + SSMProvider ssmProvider = SSMProvider + .builder() + .withClient(SsmClient.builder().build()) + .build(); + + public String handleRequest(String input, Context context) { + // Retrieve a single param + String value = ssmProvider + .get("/my/secret"); + // We might instead want to retrieve multiple parameters at once, returning a Map of key/value pairs + // .getMultiple("/my/secret/path"); + + // Return the result + return value; + } + } + ``` + +=== "Additional Options" + + ```java hl_lines="5 9 12" + import software.amazon.lambda.powertools.parameters.ssm.SSMProvider; public class AppWithSSM implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { // Get an instance of the SSM Provider - SSMProvider ssmProvider = ParamManager.getSsmProvider(); + SSMProvider ssmProvider = SSMProvider.builder().build(); // Retrieve a single parameter and decrypt it String value = ssmProvider.withDecryption().get("/my/parameter"); @@ -125,94 +265,159 @@ The AWS Systems Manager Parameter Store provider supports two additional argumen } ``` -## Secrets Manager - -For secrets stored in Secrets Manager, use `getSecretsProvider`. +### DynamoDB -Alternatively, you can retrieve an instance of a provider and configure its underlying SDK client, -in order to get data from other regions or use specific credentials. +=== "@DynamoDbParam annotation" + ```java hl_lines="8 9" + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.lambda.powertools.parameters.dynamodb.DynamoDBParam; -=== "SecretsProvider" + public class ParametersFunction implements RequestHandler<String, String> { - ```java hl_lines="9" - import software.amazon.lambda.powertools.parameters.SecretsProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; + // Annotation-style injection from DynamoDB + @DynamoDbParam(table = "my-test-tablename", key = "myKey") + String ddbParam; - public class AppWithSecrets implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - // Get an instance of the Secrets Provider - SecretsProvider secretsProvider = ParamManager.getSecretsProvider(); - - // Retrieve a single secret - String value = secretsProvider.get("/my/secret"); - + public string handleRequest(String request, Context context) { + return ddbParam; // Request handler simply returns our configuration value + } } ``` -=== "SecretsProvider with an explicit region" +=== "DynamoDbProvider class" - ```java hl_lines="5 7" - import software.amazon.lambda.powertools.parameters.SecretsProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; + ```java hl_lines="12-15 19-20 22" + import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + import software.amazon.lambda.powertools.parameters.dynamodb.DynamoDbProvider; - public class AppWithSecrets implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - SecretsManagerClient client = SecretsManagerClient.builder().region(Region.EU_CENTRAL_1).build(); - // Get an instance of the Secrets Provider - SecretsProvider secretsProvider = ParamManager.getSecretsProvider(client); + public class RequestHandlerWithParams implements RequestHandler<String, String> { + + // Get an instance of the DynamoDbProvider. We can provide a custom client here if we want, + // for instance to use a particular region. + DynamoDbProvider ddbProvider = DynamoDbProvider + .builder() + .withClient(DynamoDbClient.builder().build()) + .build(); - // Retrieve a single secret - String value = secretsProvider.get("/my/secret"); + public String handleRequest(String input, Context context) { + // Retrieve a single param + String value = ddbProvider + .get("/my/secret"); + // We might instead want to retrieve multiple values at once, returning a Map of key/value pairs + // .getMultiple("my-partition-key-value"); + // Return the result + return value; + } } ``` -## Advanced configuration +### AppConfig -### Caching +=== "@AppConfigParam annotation" -By default, all parameters and their corresponding values are cached for 5 seconds. + ```java hl_lines="8 9" + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.lambda.powertools.parameters.appconfig.AppConfigParam; -You can customize this default value using `defaultMaxAge`. You can also customize this value for each parameter using -`withMaxAge`. + public class ParametersFunction implements RequestHandler<String, String> { + + // Annotation-style injection from AppConfig + @AppConfigParam(application = "my-app", environment = "my-env", key = "myKey") + String appConfigParam; -=== "Provider with default Max age" + public string handleRequest(String request, Context context) { + return appConfigParam; // Request handler simply returns our configuration value + } + } + ``` - ```java hl_lines="9" - import software.amazon.lambda.powertools.parameters.SecretsProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; +=== "AppConfigProvider class" - public class AppWithSecrets implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - // Get an instance of the Secrets Provider - SecretsProvider secretsProvider = ParamManager.getSecretsProvider() - .defaultMaxAge(10, ChronoUnit.SECONDS); + ```java hl_lines="12-15 19-20" + import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; + import software.amazon.lambda.powertools.parameters.appconfig.AppConfigProvider; - String value = secretsProvider.get("/my/secret"); + public class RequestHandlerWithParams implements RequestHandler<String, String> { - } + // Get an instance of the AppConfigProvider. We can provide a custom client here if we want, + // for instance to use a particular region. + AppConfigProvider appConfigProvider = AppConfigProvider + .builder() + .withClient(AppConfigDataClient.builder().build()) + .build(); + + public String handleRequest(String input, Context context) { + // Retrieve a single param + String value = appConfigProvider + .get("/my/secret"); + + // Return the result + return value; + } + } ``` -=== "Provider with age for each param" +## Advanced configuration - ```java hl_lines="8" - import software.amazon.lambda.powertools.parameters.SecretsProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; +### Caching +Each provider uses the `CacheManager` to cache parameter values. When a value is retrieved using from the provider, a +custom cache duration can be provided using `withMaxAge(duration, unit)`. - public class AppWithSecrets implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - SecretsManagerClient client = SecretsManagerClient.builder().region(Region.EU_CENTRAL_1).build(); - - SecretsProvider secretsProvider = ParamManager.getSecretsProvider(client); +If this is not specified, the default value set on the `CacheManager` itself will be used. This default can be customized +by calling `setDefaultExpirationTime(duration, unit)` on the `CacheManager`. + +=== "Customize Cache" - String value = secretsProvider.withMaxAge(10, ChronoUnit.SECONDS).get("/my/secret"); + ```java hl_lines="9 10 14 19 22-25" + import java.time.Duration; + import software.amazon.lambda.powertools.parameters.appconfig.AppConfigProvider; + import software.amazon.lambda.powertools.parameters.cache.CacheManager; + + public class CustomizeCache { + + public void CustomizeCache() { + + CacheManager cacheManager = new CacheManager(); + cacheManager.setDefaultExpirationTime(Duration.ofSeconds(10)); + + AppConfigProvider paramProvider = AppConfigProvider + .builder() + .withCacheManager(cacheManager) + .withClient(AppConfigDataClient.builder().build()) + .build(); + // Will use the default specified above - 10 seconds + String myParam1 = paramProvider.get("myParam1"); + + // Will override the default above + String myParam2 = paramProvider + .withMaxAge(20, ChronoUnit.SECONDS) + .get("myParam2"); + + return myParam2; + } } ``` + ### Transform values Parameter values can be transformed using ```withTransformation(transformerClass)```. -Base64 and JSON transformations are provided. For more complex transformation, you need to specify how to deserialize- +Base64 and JSON transformations are provided. For more complex transformation, you need to specify how to deserialize. -!!! warning "`SSMProvider.getMultiple()` does not support transformation and will return simple Strings." +!!! warning "`getMultiple()` does not support transformation and will return simple Strings." === "Base64 Transformation" ```java @@ -229,14 +434,16 @@ Base64 and JSON transformations are provided. For more complex transformation, y .get("/my/parameter/json", MyObj.class); ``` -## Write your own Transformer + + +#### Create your own Transformer You can write your own transformer, by implementing the `Transformer` interface and the `applyTransformation()` method. For example, if you wish to deserialize XML into an object. === "XmlTransformer.java" - ```java hl_lines="1" + ```java public class XmlTransformer<T> implements Transformer<T> { private final XmlMapper mapper = new XmlMapper(); @@ -268,171 +475,142 @@ To simplify the use of the library, you can chain all method calls before a get. ```java ssmProvider - .defaultMaxAge(10, SECONDS) // will set 10 seconds as the default cache TTL .withMaxAge(1, MINUTES) // will set the cache TTL for this value at 1 minute .withTransformation(json) // json is a static import from Transformer.json .withDecryption() // enable decryption of the parameter value .get("/my/param", MyObj.class); // finally get the value ``` -## Create your own provider - -You can create your own custom parameter store provider by inheriting the ```BaseProvider``` class and implementing the -```String getValue(String key)``` method to retrieve data from your underlying store. All transformation and caching logic is handled by the get() methods in the base class. +### Create your own Provider +You can create your own custom parameter store provider by implementing a handful of classes: -=== "Example implementation using S3 as a custom parameter" +=== "CustomProvider.java" ```java - public class S3Provider extends BaseProvider { - - private final S3Client client; - private String bucket; - - S3Provider(CacheManager cacheManager) { - this(cacheManager, S3Client.create()); - } - - S3Provider(CacheManager cacheManager, S3Client client) { - super(cacheManager); - this.client = client; + import java.util.Map; + import software.amazon.lambda.powertools.parameters.BaseProvider; + import software.amazon.lambda.powertools.parameters.cache.CacheManager; + import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + + /** + * Our custom parameter provider itself. This does the heavy lifting of retrieving + * parameters from whatever our underlying parameter store might be. + **/ + public class CustomProvider extends BaseProvider { + + public CustomProvider(CacheManager cacheManager, TransformationManager transformationManager) { + super(cacheManager, transformationManager); } - - public S3Provider withBucket(String bucket) { - this.bucket = bucket; - return this; + + public CustomProviderBuilder builder() { + return new CustomProviderBuilder(); } @Override protected String getValue(String key) { - if (bucket == null) { - throw new IllegalStateException("A bucket must be specified, using withBucket() method"); - } - - GetObjectRequest request = GetObjectRequest.builder().bucket(bucket).key(key).build(); - ResponseBytes<GetObjectResponse> response = client.getObject(request, ResponseTransformer.toBytes()); - return response.asUtf8String(); + throw new RuntimeException("TODO - return a single value"); } @Override protected Map<String, String> getMultipleValues(String path) { - if (bucket == null) { - throw new IllegalStateException("A bucket must be specified, using withBucket() method"); - } - - ListObjectsV2Request listRequest = ListObjectsV2Request.builder().bucket(bucket).prefix(path).build(); - List<S3Object> s3Objects = client.listObjectsV2(listRequest).contents(); - - Map<String, String> result = new HashMap<>(); - s3Objects.forEach(s3Object -> { - result.put(s3Object.key(), getValue(s3Object.key())); - }); - - return result; + throw new RuntimeException("TODO - Optional - return multiple values"); } - - @Override - protected void resetToDefaults() { - super.resetToDefaults(); - bucket = null; - } - } ``` -=== "Using custom parameter store" - - ```java hl_lines="3" - S3Provider provider = new S3Provider(ParamManager.getCacheManager()); - - provider.setTransformationManager(ParamManager.getTransformationManager()); - - String value = provider.withBucket("myBucket").get("myKey"); - ``` - -## Annotation - -You can make use of the annotation `@Param` to inject a parameter value in a variable. +=== "CustomProviderBuilder.java" -By default, it will use `SSMProvider` to retrieve the value from AWS System Manager Parameter Store. -You could specify a different provider as long as it extends `BaseProvider` and/or a `Transformer`. + ```java + /** + * Provides a builder-style interface to configure our @{link CustomProvider}. + **/ + public class CustomProviderBuilder { + private CacheManager cacheManager; + private TransformationManager transformationManager; + + /** + * Create a {@link CustomProvider} instance. + * + * @return a {@link CustomProvider} + */ + public CustomProvider build() { + if (cacheManager == null) { + cacheManager = new CacheManager(); + } + return new CustomProvider(cacheManager, transformationManager); + } -=== "Param Annotation" + /** + * Provide a CacheManager to the {@link CustomProvider} + * + * @param cacheManager the manager that will handle the cache of parameters + * @return the builder to chain calls (eg. <pre>builder.withCacheManager().build()</pre>) + */ + public CustomProviderBuilder withCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + return this; + } - ```java hl_lines="3" - public class AppWithAnnotation implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - - @Param(key = "/my/parameter/json") - ObjectToDeserialize value; - + /** + * Provide a transformationManager to the {@link CustomProvider} + * + * @param transformationManager the manager that will handle transformation of parameters + * @return the builder to chain calls (eg. <pre>builder.withTransformationManager().build()</pre>) + */ + public CustomProviderBuilder withTransformationManager(TransformationManager transformationManager) { + this.transformationManager = transformationManager; + return this; + } } ``` -=== "Custom Provider Usage" - - ```java hl_lines="3" - public class AppWithAnnotation implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - - @Param(key = "/my/parameter/json" provider = SecretsProvider.class, transformer = JsonTransformer.class) - ObjectToDeserialize value; - +=== "CustomProviderParam.java" + + ```java + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + import software.amazon.lambda.powertools.parameters.transform.Transformer; + + /** + * Aspect to inject a parameter from our custom provider. Note that if you + * want to implement a provider _without_ an Aspect and field injection, you can + * skip implementing both this and the {@link CustomProviderAspect} class. + **/ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface CustomProviderParam { + // The parameter key + String key(); + + // The transformer to use + Class<? extends Transformer> transformer() default Transformer.class; } ``` - In this case ```SecretsProvider``` will be used to retrieve a raw value that is then trasformed into the target Object by using ```JsonTransformer```. - To show the convenience of the annotation compare the following two code snippets. - - -### Install +=== "CustomProviderAspect.java" -If you want to use the ```@Param``` annotation in your project add configuration to compile-time weave (CTW) the powertools-parameters aspects into your project. - -=== "Maven" + ```java - ```xml - <build> - <plugins> - ... - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>aspectj-maven-plugin</artifactId> - <version>1.14.0</version> - <configuration> - ... - <aspectLibraries> - ... - <aspectLibrary> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-parameters</artifactId> - </aspectLibrary> - </aspectLibraries> - </configuration> - <executions> - <execution> - <goals> - <goal>compile</goal> - </goals> - </execution> - </executions> - </plugin> - ... - </plugins> - </build> - ``` + /** + * Aspect to inject a parameter from our custom provider where the {@link CustomProviderParam} + * annotation is used. + **/ + @Aspect + public class CustomProviderAspect extends BaseParamAspect { -=== "Gradle" - - ```groovy - plugins{ - id 'java' - id 'io.freefair.aspectj.post-compile-weaving' version '6.3.0' - } - - repositories { - mavenCentral() - } + @Pointcut("get(* *) && @annotation(ddbConfigParam)") + public void getParam(CustomProviderParam customConfigParam) { + } + + @Around("getParam(customConfigParam)") + public Object injectParam(final ProceedingJoinPoint joinPoint, final CustomProviderParam customConfigParam) { + BaseProvider provider = CustomProvider.builder().build(); - dependencies { - ... - aspect 'software.amazon.lambda:powertools-parameters:{{ powertools.version }}' + return getAndTransform(customConfigParam.key(), ddbConfigParam.transformer(), provider, + (FieldSignature) joinPoint.getSignature()); + } + } - ``` \ No newline at end of file + ``` diff --git a/docs/utilities/serialization.md b/docs/utilities/serialization.md new file mode 100644 index 000000000..b47bdbd91 --- /dev/null +++ b/docs/utilities/serialization.md @@ -0,0 +1,474 @@ +--- +title: Serialization Utilities +description: Utility +--- + +This module contains a set of utilities you may use in your Lambda functions, to manipulate JSON. + +## Easy deserialization + +### Key features + +* Easily deserialize the main content of an event (for example, the body of an API Gateway event) +* 15+ built-in events (see the [list below](#built-in-events)) + +### Getting started + +=== "Maven" + + ```xml hl_lines="5" + <dependencies> + ... + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-serialization</artifactId> + <version>{{ powertools.version }}</version> + </dependency> + ... + </dependencies> + ``` + +=== "Gradle" + + ``` + implementation 'software.amazon.lambda:powertools-serialization:{{ powertools.version }}' + ``` + +### EventDeserializer + +The `EventDeserializer` can be used to extract the main part of an event (body, message, records) and deserialize it from JSON to your desired type. + +It can handle single elements like the body of an API Gateway event: + +=== "APIGWHandler.java" + + ```java hl_lines="1 6 9" + import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom; + + public class APIGWHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + public APIGatewayProxyResponseEvent handleRequest( + final APIGatewayProxyRequestEvent event, + final Context context) { + + Product product = extractDataFrom(event).as(Product.class); + + } + ``` + +=== "Product.java" + + ```java + public class Product { + private long id; + private String name; + private double price; + + public Product() { + } + + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + } + ``` + +=== "event" + + ```json hl_lines="2" + { + "body": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } + } + ``` + +It can also handle a collection of elements like the records of an SQS event: + +=== "SQSHandler.java" + + ```java hl_lines="1 6 9" + import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom; + + public class SQSHandler implements RequestHandler<SQSEvent, String> { + + public String handleRequest( + final SQSEvent event, + final Context context) { + + List<Product> products = extractDataFrom(event).asListOf(Product.class); + + } + ``` + +=== "event" + + ```json hl_lines="6 23" + { + "Records": [ + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{ \"id\": 1234, \"name\": \"product\", \"price\": 42}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{ \"id\": 12345, \"name\": \"product5\", \"price\": 45}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + } + ] + } + ``` + +!!! Tip + In the background, `EventDeserializer` is using Jackson. The `ObjectMapper` is configured in `JsonConfig`. You can customize the configuration of the mapper if needed: + `JsonConfig.get().getObjectMapper()`. Using this feature, you don't need to add Jackson to your project and create another instance of `ObjectMapper`. + +### Built-in events + +| Event Type | Path to the content | List | +|---------------------------------------------------|-----------------------------------------------------------|------| +| `APIGatewayProxyRequestEvent` | `body` | | +| `APIGatewayV2HTTPEvent` | `body` | | +| `SNSEvent` | `Records[0].Sns.Message` | | +| `SQSEvent` | `Records[*].body` | x | +| `ScheduledEvent` | `detail` | | +| `ApplicationLoadBalancerRequestEvent` | `body` | | +| `CloudWatchLogsEvent` | `powertools_base64_gzip(data)` | | +| `CloudFormationCustomResourceEvent` | `resourceProperties` | | +| `KinesisEvent` | `Records[*].kinesis.powertools_base64(data)` | x | +| `KinesisFirehoseEvent` | `Records[*].powertools_base64(data)` | x | +| `KafkaEvent` | `records[*].values[*].powertools_base64(value)` | x | +| `ActiveMQEvent` | `messages[*].powertools_base64(data)` | x | +| `RabbitMQEvent` | `rmqMessagesByQueue[*].values[*].powertools_base64(data)` | x | +| `KinesisAnalyticsFirehoseInputPreprocessingEvent` | `Records[*].kinesis.powertools_base64(data)` | x | +| `KinesisAnalyticsStreamsInputPreprocessingEvent` | `Records[*].kinesis.powertools_base64(data)` | x | + + +## JMESPath functions + +!!! Tip + [JMESPath](https://jmespath.org/){target="_blank"} is a query language for JSON used by AWS CLI and Powertools for AWS Lambda (Java) to get a specific part of a json. + +### Key features + +* Deserialize JSON from JSON strings, base64, and compressed data +* Use JMESPath to extract and combine data recursively + +### Getting started + +You might have events that contain encoded JSON payloads as string, base64, or even in compressed format. It is a common use case to decode and extract them partially or fully as part of your Lambda function invocation. + +You will generally use this in combination with other Powertools for AWS Lambda (Java) modules ([validation](validation.md) and [idempotency](idempotency.md)) where you might need to extract a portion of your data before using them. + +### Built-in functions + +Powertools for AWS Lambda (Java) provides the following JMESPath Functions to easily deserialize common encoded JSON payloads in Lambda functions: + +#### powertools_json function + +Use `powertools_json` function to decode any JSON string anywhere a JMESPath expression is allowed. + +Below example use this function to load the content from the body of an API Gateway request event as a JSON object and retrieve the id field in it: + +=== "MyHandler.java" + + ```java hl_lines="7" + public class MyHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + public MyHandler() { + Idempotency.config() + .withConfig( + IdempotencyConfig.builder() + .withEventKeyJMESPath("powertools_json(body).id") + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName(System.getenv("TABLE_NAME")) + .build()) + .configure(); + } + + @Idempotent + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent event, final Context context) { + } + ``` + +=== "event" + + ```json hl_lines="2" + { + "body": "{\"message\": \"Lambda rocks\", \"id\": 43876123454654}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "queryStringParameters": { + "foo": "bar" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } + } + ``` + +#### powertools_base64 function + +Use `powertools_base64` function to decode any base64 data. + +Below sample will decode the base64 value within the `data` key, and decode the JSON string into a valid JSON before we can validate it. + +=== "MyEventHandler.java" + + ```java hl_lines="7" + import software.amazon.lambda.powertools.validation.ValidationUtils; + + public class MyEventHandler implements RequestHandler<MyEvent, String> { + + @Override + public String handleRequest(MyEvent myEvent, Context context) { + validate(myEvent, "classpath:/schema.json", "powertools_base64(data)"); + return "OK"; + } + } + ``` +=== "schema.json" +```json +{ +"data" : "ewogICJpZCI6IDQzMjQyLAogICJuYW1lIjogIkZvb0JhciBYWSIsCiAgInByaWNlIjogMjU4Cn0=" +} +``` + +#### powertools_base64_gzip function + +Use `powertools_base64_gzip` function to decompress and decode base64 data. + +Below sample will decompress and decode base64 data. + +=== "MyEventHandler.java" + + ```java hl_lines="7" + import software.amazon.lambda.powertools.validation.ValidationUtils; + + public class MyEventHandler implements RequestHandler<MyEvent, String> { + + @Override + public String handleRequest(MyEvent myEvent, Context context) { + validate(myEvent, "classpath:/schema.json", "powertools_base64_gzip(data)"); + return "OK"; + } + } + ``` + +=== "schema.json" + + ```json + { + "data" : "H4sIAAAAAAAA/6vmUlBQykxRslIwMTYyMdIBcfMSc1OBAkpu+flOiUUKEZFKYOGCosxkkLiRqQVXLQDnWo6bOAAAAA==" + } + ``` + + +### Bring your own JMESPath function + +!!! warning + This should only be used for advanced use cases where you have special formats not covered by the built-in functions. + Please open an issue in Github if you need us to add some common functions. + +Your function must extend `io.burt.jmespath.function.BaseFunction`, take a String as parameter and return a String. +You can read the [doc](https://github.com/burtcorp/jmespath-java#adding-custom-functions) for more information. + +Below is an example that takes some xml and transform it into json. Once your function is created, you need to add it +to powertools.You can then use it to do your validation or in idempotency module. + +=== "XMLFunction.java" + + ```java + public class XMLFunction extends BaseFunction { + public Base64Function() { + super("powertools_xml", ArgumentConstraints.typeOf(JmesPathType.STRING)); + } + + @Override + protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) { + T value = arguments.get(0).value(); + String xmlString = runtime.toString(value); + + String jsonString = // ... transform xmlString to json + + return runtime.createString(jsonString); + } + } + ``` + +=== "Handler with validation API" + + ```java hl_lines="6 13" + ... + import software.amazon.lambda.powertools.validation.ValidationConfig; + import software.amazon.lambda.powertools.validation.ValidationUtils.validate; + + static { + JsonConfig.get().addFunction(new XMLFunction()); + } + + public class MyXMLEventHandler implements RequestHandler<MyEventWithXML, String> { + + @Override + public String handleRequest(MyEventWithXML myEvent, Context context) { + validate(myEvent, "classpath:/schema.json", "powertools_xml(path.to.xml_data)"); + return "OK"; + } + } + ``` + +=== "Handler with validation annotation" + + ```java hl_lines="6 12" + ... + import software.amazon.lambda.powertools.validation.ValidationConfig; + import software.amazon.lambda.powertools.validation.Validation; + + static { + JsonConfig.get().addFunction(new XMLFunction()); + } + + public class MyXMLEventHandler implements RequestHandler<MyEventWithXML, String> { + + @Override + @Validation(inboundSchema="classpath:/schema.json", envelope="powertools_xml(path.to.xml_data)") + public String handleRequest(MyEventWithXML myEvent, Context context) { + return "OK"; + } + } + ``` diff --git a/docs/utilities/sqs_large_message_handling.md b/docs/utilities/sqs_large_message_handling.md deleted file mode 100644 index 95dc34e35..000000000 --- a/docs/utilities/sqs_large_message_handling.md +++ /dev/null @@ -1,218 +0,0 @@ ---- -title: SQS Large Message Handling -description: Utility ---- - -The large message handling utility handles SQS messages which have had their payloads -offloaded to S3 due to them being larger than the SQS maximum. - -The utility automatically retrieves messages which have been offloaded to S3 using the -[amazon-sqs-java-extended-client-lib](https://github.com/awslabs/amazon-sqs-java-extended-client-lib) -client library. Once the message payloads have been processed successful the -utility can delete the message payloads from S3. - -This utility is compatible with versions *[1.1.0+](https://github.com/awslabs/amazon-sqs-java-extended-client-lib)* of amazon-sqs-java-extended-client-lib. - -=== "Maven" - ```xml - <dependency> - <groupId>com.amazonaws</groupId> - <artifactId>amazon-sqs-java-extended-client-lib</artifactId> - <version>1.1.0</version> - </dependency> - ``` -=== "Gradle" - - ```groovy - dependencies { - implementation 'com.amazonaws:amazon-sqs-java-extended-client-lib:1.1.0' - } - ``` - -## Install - -To install this utility, add the following dependency to your project. - -=== "Maven" - ```xml hl_lines="3 4 5 6 7 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36" - <dependencies> - ... - <dependency> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-sqs</artifactId> - <version>{{ powertools.version }}</version> - </dependency> - ... - </dependencies> - <!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project --> - <build> - <plugins> - ... - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>aspectj-maven-plugin</artifactId> - <version>1.14.0</version> - <configuration> - <source>1.8</source> - <target>1.8</target> - <complianceLevel>1.8</complianceLevel> - <aspectLibraries> - <aspectLibrary> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-sqs</artifactId> - </aspectLibrary> - </aspectLibraries> - </configuration> - <executions> - <execution> - <goals> - <goal>compile</goal> - </goals> - </execution> - </executions> - </plugin> - ... - </plugins> - </build> - ``` - -=== "Gradle" - - ```groovy - plugins{ - id 'java' - id 'io.freefair.aspectj.post-compile-weaving' version '6.3.0' - } - - repositories { - mavenCentral() - } - - dependencies { - ... - aspect 'software.amazon.lambda:powertools-sqs:{{ powertools.version }}' - } - ``` - -## Lambda handler - -The annotation `@SqsLargeMessage` should be used with the handleRequest method of a class -which implements `com.amazonaws.services.lambda.runtime.RequestHandler` with -`com.amazonaws.services.lambda.runtime.events.SQSEvent` as the first parameter. - -=== "SqsMessageHandler.java" - - ```java hl_lines="6" - import software.amazon.lambda.powertools.sqs.SqsLargeMessage; - - public class SqsMessageHandler implements RequestHandler<SQSEvent, String> { - - @Override - @SqsLargeMessage - public String handleRequest(SQSEvent sqsEvent, Context context) { - // process messages - - return "ok"; - } - } - ``` - -`@SqsLargeMessage` creates a default S3 Client `AmazonS3 amazonS3 = AmazonS3ClientBuilder.defaultClient()`. - -!!! tip - When the Lambda function is invoked with an event from SQS, each received record - in the SQSEvent is checked to see to validate if it is offloaded to S3. - If it does then `getObject(bucket, key)` will be called, and the payload retrieved. - If there is an error during this process then the function will fail with a `FailedProcessingLargePayloadException` exception. - - If the request handler method returns without error then each payload will be - deleted from S3 using `deleteObject(bucket, key)` - -To disable deletion of payloads setting the following annotation parameter: - -=== "Disable payload deletion" - - ```java hl_lines="3" - import software.amazon.lambda.powertools.sqs.SqsLargeMessage; - - @SqsLargeMessage(deletePayloads=false) - public class SqsMessageHandler implements RequestHandler<SQSEvent, String> { - - } - ``` - -## Utility - -If you want to avoid using annotation and have control over error that can happen during payload enrichment use `SqsUtils.enrichedMessageFromS3()`. -It provides you access with list of `SQSMessage` object enriched from S3 payload. - -Original `SQSEvent` object is never mutated. You can also control if the S3 payload should be deleted after successful -processing. - -=== "Functional API without annotation" - - ```java hl_lines="9 10 11 14 15 16 17 18 19 20 21 22 27 28 29" - import software.amazon.lambda.powertools.sqs.SqsLargeMessage; - import software.amazon.lambda.powertools.sqs.SqsUtils; - - public class SqsMessageHandler implements RequestHandler<SQSEvent, String> { - - @Override - public String handleRequest(SQSEvent sqsEvent, Context context) { - - Map<String, String> sqsMessage = SqsUtils.enrichedMessageFromS3(sqsEvent, sqsMessages -> { - // Some business logic - Map<String, String> someBusinessLogic = new HashMap<>(); - someBusinessLogic.put("Message", sqsMessages.get(0).getBody()); - return someBusinessLogic; - }); - - // Do not delete payload after processing. - Map<String, String> sqsMessage = SqsUtils.enrichedMessageFromS3(sqsEvent, false, sqsMessages -> { - // Some business logic - Map<String, String> someBusinessLogic = new HashMap<>(); - someBusinessLogic.put("Message", sqsMessages.get(0).getBody()); - return someBusinessLogic; - }); - - // Better control over exception during enrichment - try { - // Do not delete payload after processing. - SqsUtils.enrichedMessageFromS3(sqsEvent, false, sqsMessages -> { - // Some business logic - }); - } catch (FailedProcessingLargePayloadException e) { - // handle any exception. - } - - return "ok"; - } - } - ``` - -## Overriding the default S3Client - -If you require customisations to the default S3Client, you can create your own `S3Client` and pass it to be used by utility either for -**[SqsLargeMessage annotation](#lambda-handler)**, or **[SqsUtils Utility API](#utility)**. - -=== "App.java" - - ```java hl_lines="4 5 11" - import software.amazon.lambda.powertools.sqs.SqsLargeMessage; - - static { - SqsUtils.overrideS3Client(S3Client.builder() - .build()); - } - - public class SqsMessageHandler implements RequestHandler<SQSEvent, String> { - - @Override - @SqsLargeMessage - public String handleRequest(SQSEvent sqsEvent, Context context) { - // process messages - - return "ok"; - } - } - ``` \ No newline at end of file diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index 862e668c0..8e0d2c631 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -8,36 +8,36 @@ This utility provides JSON Schema validation for payloads held within events and **Key features** * Validate incoming events and responses -* Built-in validation for most common events (API Gateway, SNS, SQS, ...) +* Built-in validation for most common events (API Gateway, SNS, SQS, ...) and support for partial batch failures (SQS, Kinesis) * JMESPath support validate only a sub part of the event ## Install -To install this utility, add the following dependency to your project. - === "Maven" - ```xml hl_lines="3 4 5 6 7 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36" + ```xml hl_lines="3-7 16 18 24-27" <dependencies> - ... - <dependency> - <groupId>com.amazonaws</groupId> - <artifactId>powertools-validation</artifactId> - <version>{{ powertools.version }}</version> - </dependency> - ... + ... + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-validation</artifactId> + <version>{{ powertools.version }}</version> + </dependency> + ... </dependencies> + ... <!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project --> + <!-- Note: This AspectJ configuration is not needed when using the functional approach with ValidationUtils.validate() --> <build> <plugins> ... <plugin> - <groupId>org.codehaus.mojo</groupId> + <groupId>dev.aspectj</groupId> <artifactId>aspectj-maven-plugin</artifactId> - <version>1.14.0</version> + <version>1.14</version> <configuration> - <source>1.8</source> - <target>1.8</target> - <complianceLevel>1.8</complianceLevel> + <source>11</source> <!-- or higher --> + <target>11</target> <!-- or higher --> + <complianceLevel>11</complianceLevel> <!-- or higher --> <aspectLibraries> <aspectLibrary> <groupId>software.amazon.lambda</groupId> @@ -45,6 +45,14 @@ To install this utility, add the following dependency to your project. </aspectLibrary> </aspectLibraries> </configuration> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <!-- AspectJ compiler version, in sync with runtime --> + <version>1.9.22</version> + </dependency> + </dependencies> <executions> <execution> <goals> @@ -60,34 +68,49 @@ To install this utility, add the following dependency to your project. === "Gradle" - ```groovy - plugins{ - id 'java' - id 'io.freefair.aspectj.post-compile-weaving' version '6.3.0' - } - - repositories { - mavenCentral() - } - - dependencies { - aspect 'software.amazon.lambda:powertools-validation:{{ powertools.version }}' - } + ```groovy hl_lines="3 11 12" + plugins { + id 'java' + id 'io.freefair.aspectj.post-compile-weaving' version '8.1.0' // Not needed when using the functional approach with ValidationUtils.validate() + } + + repositories { + mavenCentral() + } + + dependencies { + aspect 'software.amazon.lambda:powertools-validation:{{ powertools.version }}' // Not needed when using the functional approach with ValidationUtils.validate() + implementation 'software.amazon.lambda:powertools-validation:{{ powertools.version }}' // Use this instead of 'aspect' when using the functional approach + } + + sourceCompatibility = 11 // or higher + targetCompatibility = 11 // or higher ``` ## Validating events -You can validate inbound and outbound events using `@Validation` annotation. +You can validate inbound and outbound events using either the `@Validation` annotation or the functional approach with `ValidationUtils.validate()` methods: + +- **@Validation annotation** - Simpler syntax with automatic validation, but requires AspectJ configuration +- **ValidationUtils.validate()** - No AspectJ required, provides more control over the validation process such as handling validation errors -You can also use the `Validator#validate()` methods, if you want more control over the validation process such as handling a validation error. +We support JSON schema version 4, 6, 7, 2019-09 and 2020-12 using the [NetworkNT JSON Schema Validator](https://github.com/networknt/json-schema-validator) ([Compatibility with JSON Schema versions](https://github.com/networknt/json-schema-validator/blob/master/doc/compatibility.md)). -We support JSON schema version 4, 6, 7 and 201909 (from [jmespath-jackson library](https://github.com/burtcorp/jmespath-java)). +The validator is configured to enable format assertions by default even for 2019-09 and 2020-12. ### Validation annotation -`@Validation` annotation is used to validate either inbound events or functions' response. +The `@Validation` annotation is used to validate either inbound events or functions' response. -It will fail fast with `ValidationException` if an event or response doesn't conform with given JSON Schema. +It will fail fast if an event or response doesn't conform with given JSON Schema. For most type of events a `ValidationException` will be thrown. + +For API gateway events associated with REST APIs and HTTP APIs - `APIGatewayProxyRequestEvent` and `APIGatewayV2HTTPEvent` - the `@Validation` +annotation will build and return a custom 400 / "Bad Request" response, with a body containing the validation errors. This saves you from having +to catch the validation exception and map it back to a meaningful user error yourself. + +For SQS and Kinesis events - `SQSEvent` and `KinesisEvent`- the `@Validation` annotation will add the invalid messages +to the batch item failures list in the response, respectively `SQSBatchResponse` and `StreamsEventResponse` +and removed from the event so that you do not process them within the handler. While it is easier to specify a json schema file in the classpath (using the notation `"classpath:/path/to/schema.json"`), you can also provide a JSON String containing the schema. @@ -109,11 +132,11 @@ While it is easier to specify a json schema file in the classpath (using the not **NOTE**: It's not a requirement to validate both inbound and outbound schemas - You can either use one, or both. -### Validate function +### Functional approach with ValidationUtils -Validate standalone function is used within the Lambda handler, or any other methods that perform data validation. +The `ValidationUtils.validate()` method provides a functional approach that can be used within the Lambda handler or any other methods that perform data validation. This approach does not require AspectJ configuration. -You can also gracefully handle schema validation errors by catching `ValidationException`. +With this approach, you can gracefully handle schema validation errors by catching `ValidationException`. === "MyFunctionHandler.java" @@ -143,33 +166,33 @@ You can also gracefully handle schema validation errors by catching `ValidationE For the following events and responses, the Validator will automatically perform validation on the content. -** Events ** - - Type of event | Class | Path to content | - ------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------- - API Gateway REST | APIGatewayProxyRequestEvent | `body` - API Gateway HTTP | APIGatewayV2HTTPEvent | `body` - Application Load Balancer | ApplicationLoadBalancerRequestEvent | `body` - Cloudformation Custom Resource | CloudFormationCustomResourceEvent | `resourceProperties` - CloudWatch Logs | CloudWatchLogsEvent | `awslogs.powertools_base64_gzip(data)` - EventBridge / Cloudwatch | ScheduledEvent | `detail` - Kafka | KafkaEvent | `records[*][*].value` - Kinesis | KinesisEvent | `Records[*].kinesis.powertools_base64(data)` - Kinesis Firehose | KinesisFirehoseEvent | `Records[*].powertools_base64(data)` - Kinesis Analytics from Firehose | KinesisAnalyticsFirehoseInputPreprocessingEvent | `Records[*].powertools_base64(data)` - Kinesis Analytics from Streams | KinesisAnalyticsStreamsInputPreprocessingEvent | `Records[*].powertools_base64(data)` - SNS | SNSEvent | `Records[*].Sns.Message` - SQS | SQSEvent | `Records[*].body` - -** Responses ** - - Type of response | Class | Path to content (envelope) - ------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------- - API Gateway REST | APIGatewayProxyResponseEvent} | `body` - API Gateway HTTP | APIGatewayV2HTTPResponse} | `body` - API Gateway WebSocket | APIGatewayV2WebSocketResponse} | `body` - Load Balancer | ApplicationLoadBalancerResponseEvent} | `body` - Kinesis Analytics | KinesisAnalyticsInputPreprocessingResponse} | `Records[*].powertools_base64(data)`` +**Events** + +| Type of event | Class | Path to content | +|---------------------------------|-------------------------------------------------|----------------------------------------------| +| API Gateway REST | APIGatewayProxyRequestEvent | `body` | +| API Gateway HTTP | APIGatewayV2HTTPEvent | `body` | +| Application Load Balancer | ApplicationLoadBalancerRequestEvent | `body` | +| Cloudformation Custom Resource | CloudFormationCustomResourceEvent | `resourceProperties` | +| CloudWatch Logs | CloudWatchLogsEvent | `awslogs.powertools_base64_gzip(data)` | +| EventBridge / Cloudwatch | ScheduledEvent | `detail` | +| Kafka | KafkaEvent | `records[*][*].value` | +| Kinesis | KinesisEvent | `Records[*].kinesis.powertools_base64(data)` | +| Kinesis Firehose | KinesisFirehoseEvent | `Records[*].powertools_base64(data)` | +| Kinesis Analytics from Firehose | KinesisAnalyticsFirehoseInputPreprocessingEvent | `Records[*].powertools_base64(data)` | +| Kinesis Analytics from Streams | KinesisAnalyticsStreamsInputPreprocessingEvent | `Records[*].powertools_base64(data)` | +| SNS | SNSEvent | `Records[*].Sns.Message` | +| SQS | SQSEvent | `Records[*].body` | + +**Responses** + +| Type of response | Class | Path to content (envelope) | +|-----------------------|---------------------------------------------|---------------------------------------| +| API Gateway REST | APIGatewayProxyResponseEvent} | `body` | +| API Gateway HTTP | APIGatewayV2HTTPResponse} | `body` | +| API Gateway WebSocket | APIGatewayV2WebSocketResponse} | `body` | +| Load Balancer | ApplicationLoadBalancerResponseEvent} | `body` | +| Kinesis Analytics | KinesisAnalyticsInputPreprocessingResponse} | `Records[*].powertools_base64(data)` | ## Custom events and responses @@ -221,134 +244,46 @@ This is quite powerful because you can use JMESPath Query language to extract re to [pipe expressions](https://jmespath.org/tutorial.html#pipe-expressions) and [function](https://jmespath.org/tutorial.html#functions) expressions, where you'd extract what you need before validating the actual payload. -## JMESPath functions - -JMESPath functions ensure to make an operation on a specific part of the json.validate - -Powertools provides two built-in functions: - -### powertools_base64 function - -Use `powertools_base64` function to decode any base64 data. - -Below sample will decode the base64 value within the data key, and decode the JSON string into a valid JSON before we can validate it. - -=== "MyEventHandler.java" - - ```java hl_lines="7" - import software.amazon.lambda.powertools.validation.ValidationUtils; - - public class MyEventHandler implements RequestHandler<MyEvent, String> { - - @Override - public String handleRequest(MyEvent myEvent, Context context) { - validate(myEvent, "classpath:/schema.json", "powertools_base64(data)"); - return "OK"; - } - } - ``` -=== "schema.json" - ```json - { - "data" : "ewogICJpZCI6IDQzMjQyLAogICJuYW1lIjogIkZvb0JhciBYWSIsCiAgInByaWNlIjogMjU4Cn0=" - } - ``` - -### powertools_base64_gzip function - -Use `powertools_base64_gzip` function to decompress and decode base64 data. - -Below sample will decompress and decode base64 data. - -=== "MyEventHandler.java" - - ```java hl_lines="7" - import software.amazon.lambda.powertools.validation.ValidationUtils; - - public class MyEventHandler implements RequestHandler<MyEvent, String> { - - @Override - public String handleRequest(MyEvent myEvent, Context context) { - validate(myEvent, "classpath:/schema.json", "powertools_base64_gzip(data)"); - return "OK"; - } - } - ``` - -=== "schema.json" - - ```json - { - "data" : "H4sIAAAAAAAA/6vmUlBQykxRslIwMTYyMdIBcfMSc1OBAkpu+flOiUUKEZFKYOGCosxkkLiRqQVXLQDnWo6bOAAAAA==" - } - ``` - -!!! note - You don't need any function to transform a JSON String into a JSON object, powertools-validation will do it for you. - In the 2 previous example, data contains JSON. Just provide the function to transform the base64 / gzipped / ... string into a clear JSON string. - -### Bring your own JMESPath function - -!!! warning - This should only be used for advanced use cases where you have special formats not covered by the built-in functions. - New functions will be added to the 2 built-in ones. -Your function must extend `io.burt.jmespath.function.BaseFunction`, take a String as parameter and return a String. -You can read the [doc](https://github.com/burtcorp/jmespath-java#adding-custom-functions) for more information. - -Below is an example that takes some xml and transform it into json. Once your function is created, you need to add it -to powertools.You can then use it to do your validation or using annotation. +## Change the schema version +By default, powertools-validation is configured to use [V7](https://json-schema.org/draft-07/json-schema-release-notes.html) as the default dialect if [`$schema`](https://json-schema.org/understanding-json-schema/reference/schema#schema) is not explicitly specified within the schema. If [`$schema`](https://json-schema.org/understanding-json-schema/reference/schema#schema) is explicitly specified within the schema, the validator will use the specified dialect. -=== "XMLFunction.java" - - ```java - public class XMLFunction extends BaseFunction { - public Base64Function() { - super("powertools_xml", ArgumentConstraints.typeOf(JmesPathType.STRING)); - } - - @Override - protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) { - T value = arguments.get(0).value(); - String xmlString = runtime.toString(value); - - String jsonString = // ... transform xmlString to json - - return runtime.createString(jsonString); - } - } - ``` +You can use the `ValidationConfig` to change that behaviour. -=== "Handler with validation API" +=== "Handler with custom schema version" - ```java hl_lines="6 13" + ```java hl_lines="6" ... import software.amazon.lambda.powertools.validation.ValidationConfig; - import software.amazon.lambda.powertools.validation.ValidationUtils.validate; + import software.amazon.lambda.powertools.validation.Validation; static { - ValidationConfig.get().addFunction(new XMLFunction()); + ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V4); } public class MyXMLEventHandler implements RequestHandler<MyEventWithXML, String> { @Override + @Validation(inboundSchema="classpath:/schema.json", envelope="powertools_xml(path.to.xml_data)") public String handleRequest(MyEventWithXML myEvent, Context context) { - validate(myEvent, "classpath:/schema.json", "powertools_xml(path.to.xml_data)"); return "OK"; } } ``` -=== "Handler with validation annotation" +## Advanced ObjectMapper settings +If you need to configure the Jackson ObjectMapper, you can use the `ValidationConfig`: + +=== "Handler with custom ObjectMapper" - ```java hl_lines="6 12" + ```java hl_lines="6 7" ... import software.amazon.lambda.powertools.validation.ValidationConfig; import software.amazon.lambda.powertools.validation.Validation; static { - ValidationConfig.get().addFunction(new XMLFunction()); + ObjectMapper objectMapper= ValidationConfig.get().getObjectMapper(); + // update (de)serializationConfig or other properties } public class MyXMLEventHandler implements RequestHandler<MyEventWithXML, String> { @@ -361,52 +296,52 @@ to powertools.You can then use it to do your validation or using annotation. } ``` -## Change the schema version -By default, powertools-validation is configured with [V7](https://json-schema.org/draft-07/json-schema-release-notes.html). -You can use the `ValidationConfig` to change that behaviour. +## Advanced -=== "Handler with custom schema version" +### Lambda SnapStart priming - ```java hl_lines="6" - ... - import software.amazon.lambda.powertools.validation.ValidationConfig; +The Validation utility integrates with AWS Lambda SnapStart to improve restore durations. To make sure the SnapStart priming logic of this utility runs correctly, you need an explicit reference to `ValidationConfig` in your code to allow the library to register before SnapStart takes a memory snapshot. Learn more about what priming is in this [blog post](https://aws.amazon.com/blogs/compute/optimizing-cold-start-performance-of-aws-lambda-using-advanced-priming-strategies-with-snapstart/){target="_blank"}. + +If you don't set a custom `ValidationConfig` in your code yet, make sure to reference `ValidationConfig` in your Lambda handler initialization code. This can be done by adding one of the following lines to your handler class: + +=== "Constructor" + + ```java hl_lines="7" import software.amazon.lambda.powertools.validation.Validation; + import software.amazon.lambda.powertools.validation.ValidationConfig; - static { - ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V4); - } + public class MyFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + public MyFunctionHandler() { + ValidationConfig.get(); // Ensure ValidationConfig is loaded for SnapStart + } - public class MyXMLEventHandler implements RequestHandler<MyEventWithXML, String> { - @Override - @Validation(inboundSchema="classpath:/schema.json", envelope="powertools_xml(path.to.xml_data)") - public String handleRequest(MyEventWithXML myEvent, Context context) { - return "OK"; - } + @Validation(inboundSchema = "classpath:/schema_in.json", outboundSchema = "classpath:/schema_out.json") + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + // ... + return something; + } } ``` -## Advanced ObjectMapper settings -If you need to configure the Jackson ObjectMapper, you can use the `ValidationConfig`: +=== "Static Initializer" -=== "Handler with custom ObjectMapper" - - ```java hl_lines="6 7" - ... - import software.amazon.lambda.powertools.validation.ValidationConfig; + ```java hl_lines="7" import software.amazon.lambda.powertools.validation.Validation; + import software.amazon.lambda.powertools.validation.ValidationConfig; - static { - ObjectMapper objectMapper= ValidationConfig.get().getObjectMapper(); - // update (de)serializationConfig or other properties - } + public class MyFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + static { + ValidationConfig.get(); // Ensure ValidationConfig is loaded for SnapStart + } - public class MyXMLEventHandler implements RequestHandler<MyEventWithXML, String> { - @Override - @Validation(inboundSchema="classpath:/schema.json", envelope="powertools_xml(path.to.xml_data)") - public String handleRequest(MyEventWithXML myEvent, Context context) { - return "OK"; - } + @Validation(inboundSchema = "classpath:/schema_in.json", outboundSchema = "classpath:/schema_out.json") + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + // ... + return something; + } } - ``` \ No newline at end of file + ``` diff --git a/example/HelloWorldFunction/build.gradle b/example/HelloWorldFunction/build.gradle deleted file mode 100644 index c7c774a33..000000000 --- a/example/HelloWorldFunction/build.gradle +++ /dev/null @@ -1,36 +0,0 @@ -plugins{ - id 'java' - id 'aspectj.AspectjGradlePlugin' version '0.0.7' -} - -repositories { - mavenCentral() -} - -dependencies { - implementation 'software.amazon.lambda:powertools-tracing:1.10.2' - aspectpath 'software.amazon.lambda:powertools-tracing:1.10.2' - - implementation 'software.amazon.lambda:powertools-logging:1.10.2' - aspectpath 'software.amazon.lambda:powertools-logging:1.10.2' - - implementation 'software.amazon.lambda:powertools-metrics:1.10.2' - aspectpath 'software.amazon.lambda:powertools-metrics:1.10.2' - - implementation 'software.amazon.lambda:powertools-sqs:1.10.2' - aspectpath 'software.amazon.lambda:powertools-sqs:1.10.2' - - implementation 'software.amazon.lambda:powertools-parameters:1.10.2' - aspectpath 'software.amazon.lambda:powertools-parameters:1.10.2' - - implementation 'software.amazon.lambda:powertools-validation:1.10.2' - aspectpath 'software.amazon.lambda:powertools-validation:1.10.2' - - implementation 'com.amazonaws:aws-lambda-java-core:1.2.1' - implementation 'com.amazonaws:aws-lambda-java-events:3.1.0' - - implementation 'org.apache.logging.log4j:log4j-api:2.16.0' - implementation 'org.apache.logging.log4j:log4j-core:2.16.0' - - testImplementation 'junit:junit:4.12' -} \ No newline at end of file diff --git a/example/HelloWorldFunction/gradle.properties b/example/HelloWorldFunction/gradle.properties deleted file mode 100644 index c84ec451a..000000000 --- a/example/HelloWorldFunction/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -aspectjVersion=1.9.6 \ No newline at end of file diff --git a/example/HelloWorldFunction/gradle/wrapper/gradle-wrapper.jar b/example/HelloWorldFunction/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 62d4c0535..000000000 Binary files a/example/HelloWorldFunction/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/example/HelloWorldFunction/gradlew b/example/HelloWorldFunction/gradlew deleted file mode 100755 index 3a163de71..000000000 --- a/example/HelloWorldFunction/gradlew +++ /dev/null @@ -1,185 +0,0 @@ -#!/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" "$@" \ No newline at end of file diff --git a/example/HelloWorldFunction/settings.gradle b/example/HelloWorldFunction/settings.gradle deleted file mode 100644 index 94df0a3e5..000000000 --- a/example/HelloWorldFunction/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'HelloWorldFunction' diff --git a/example/HelloWorldFunction/src/main/java/helloworld/AppParams.java b/example/HelloWorldFunction/src/main/java/helloworld/AppParams.java deleted file mode 100644 index 5a2ea5d0f..000000000 --- a/example/HelloWorldFunction/src/main/java/helloworld/AppParams.java +++ /dev/null @@ -1,81 +0,0 @@ -package helloworld; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import software.amazon.lambda.powertools.parameters.ParamManager; -import software.amazon.lambda.powertools.parameters.SSMProvider; -import software.amazon.lambda.powertools.parameters.SecretsProvider; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -import static java.time.temporal.ChronoUnit.SECONDS; -import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; -import static software.amazon.lambda.powertools.parameters.transform.Transformer.json; - -public class AppParams implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - private final static Logger log = LogManager.getLogger(); - - SSMProvider ssmProvider = ParamManager.getSsmProvider(); - SecretsProvider secretsProvider = ParamManager.getSecretsProvider(); - - String simplevalue = ssmProvider.defaultMaxAge(30, SECONDS).get("/powertools-java/sample/simplekey"); - String listvalue = ssmProvider.withMaxAge(60, SECONDS).get("/powertools-java/sample/keylist"); - MyObject jsonobj = ssmProvider.withTransformation(json).get("/powertools-java/sample/keyjson", MyObject.class); - Map<String, String> allvalues = ssmProvider.getMultiple("/powertools-java/sample"); - String b64value = ssmProvider.withTransformation(base64).get("/powertools-java/sample/keybase64"); - - Map<String, String> secretjson = secretsProvider.withTransformation(json).get("/powertools-java/userpwd", Map.class); - MyObject secretjsonobj = secretsProvider.withMaxAge(42, SECONDS).withTransformation(json).get("/powertools-java/secretcode", MyObject.class); - - @Override - public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { - - log.info("\n=============== SSM Parameter Store ==============="); - log.info("simplevalue={}, listvalue={}, b64value={}\n", simplevalue, listvalue, b64value); - log.info("jsonobj={}\n", jsonobj); - - log.info("allvalues (multiple):"); - allvalues.forEach((key, value) -> log.info("- {}={}\n", key, value)); - - log.info("\n=============== Secrets Manager ==============="); - log.info("secretjson:"); - secretjson.forEach((key, value) -> log.info("- {}={}\n", key, value)); - log.info("secretjsonobj={}\n", secretjsonobj); - - Map<String, String> headers = new HashMap<>(); - headers.put("Content-Type", "application/json"); - headers.put("X-Custom-Header", "application/json"); - - APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() - .withHeaders(headers); - try { - final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); - String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); - - return response - .withStatusCode(200) - .withBody(output); - } catch (IOException e) { - return response - .withBody("{}") - .withStatusCode(500); - } - } - - private String getPageContents(String address) throws IOException{ - URL url = new URL(address); - try(BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) { - return br.lines().collect(Collectors.joining(System.lineSeparator())); - } - } -} diff --git a/example/HelloWorldFunction/src/main/java/helloworld/AppSqsEvent.java b/example/HelloWorldFunction/src/main/java/helloworld/AppSqsEvent.java deleted file mode 100644 index 31ba1f760..000000000 --- a/example/HelloWorldFunction/src/main/java/helloworld/AppSqsEvent.java +++ /dev/null @@ -1,35 +0,0 @@ -package helloworld; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.sqs.SqsBatch; -import software.amazon.lambda.powertools.sqs.SqsMessageHandler; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; - -public class AppSqsEvent implements RequestHandler<SQSEvent, String> { - private static final Logger log = LogManager.getLogger(AppSqsEvent.class); - - @SqsBatch(SampleMessageHandler.class) - @Logging(logEvent = true) - @Override - public String handleRequest(SQSEvent input, Context context) { - return "{\"statusCode\": 200}"; - } - - public static class SampleMessageHandler implements SqsMessageHandler<Object> { - - @Override - public String process(SQSMessage message) { - if("19dd0b57-b21e-4ac1-bd88-01bbb068cb99".equals(message.getMessageId())) { - throw new RuntimeException(message.getMessageId()); - } - log.info("Processing message with details {}", message); - return message.getMessageId(); - } - } -} diff --git a/example/HelloWorldFunction/src/main/java/helloworld/AppSqsEventUtil.java b/example/HelloWorldFunction/src/main/java/helloworld/AppSqsEventUtil.java deleted file mode 100644 index 3ececdfe1..000000000 --- a/example/HelloWorldFunction/src/main/java/helloworld/AppSqsEventUtil.java +++ /dev/null @@ -1,39 +0,0 @@ -package helloworld; - -import java.util.List; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import software.amazon.lambda.powertools.sqs.SqsUtils; -import software.amazon.lambda.powertools.sqs.SQSBatchProcessingException; - -import static java.util.Collections.emptyList; - -public class AppSqsEventUtil implements RequestHandler<SQSEvent, List<String>> { - private static final Logger log = LogManager.getLogger(AppSqsEventUtil.class); - - @Override - public List<String> handleRequest(SQSEvent input, Context context) { - try { - - return SqsUtils.batchProcessor(input, (message) -> { - if ("19dd0b57-b21e-4ac1-bd88-01bbb068cb99".equals(message.getMessageId())) { - throw new RuntimeException(message.getMessageId()); - } - - log.info("Processing message with details {}", message); - return message.getMessageId(); - }); - - } catch (SQSBatchProcessingException e) { - log.info("Exception details {}", e.getMessage(), e); - log.info("Success message Returns{}", e.successMessageReturnValues()); - log.info("Failed messages {}", e.getFailures()); - log.info("Failed messages Reasons {}", e.getExceptions()); - return emptyList(); - } - } -} diff --git a/example/HelloWorldFunction/src/main/java/helloworld/AppStream.java b/example/HelloWorldFunction/src/main/java/helloworld/AppStream.java deleted file mode 100644 index aed048eef..000000000 --- a/example/HelloWorldFunction/src/main/java/helloworld/AppStream.java +++ /dev/null @@ -1,25 +0,0 @@ -package helloworld; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Map; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; -import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.metrics.Metrics; - -public class AppStream implements RequestStreamHandler { - private static final ObjectMapper mapper = new ObjectMapper(); - - @Override - @Logging(logEvent = true) - @Metrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) - public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { - Map map = mapper.readValue(input, Map.class); - - System.out.println(map.size()); - } -} diff --git a/example/HelloWorldFunction/src/main/java/helloworld/MyObject.java b/example/HelloWorldFunction/src/main/java/helloworld/MyObject.java deleted file mode 100644 index 3c416971e..000000000 --- a/example/HelloWorldFunction/src/main/java/helloworld/MyObject.java +++ /dev/null @@ -1,34 +0,0 @@ -package helloworld; - -public class MyObject { - - private long id; - private String code; - - public MyObject() { - } - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - - @Override - public String toString() { - return "MyObject{" + - "id=" + id + - ", code='" + code + '\'' + - '}'; - } -} diff --git a/example/README.md b/example/README.md deleted file mode 100644 index 56af61c73..000000000 --- a/example/README.md +++ /dev/null @@ -1,121 +0,0 @@ -# example - -This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. - -- HelloWorldFunction/src/main - Code for the application's Lambda function. -- events - Invocation events that you can use to invoke the function. -- HelloWorldFunction/src/test - Unit tests for the application code. -- template.yaml - A template that defines the application's AWS resources. - -The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. - -If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. -The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. - -* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) -* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) -* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) -* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) - -## Deploy the sample application - -The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. - -To use the SAM CLI, you need the following tools. - -* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) -* Java8 - [Install the Java SE Development Kit 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) -* Maven - [Install Maven](https://maven.apache.org/install.html) or -* Gradle - [Install Gradle](https://gradle.org/install/) -* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) - -To build and deploy your application for the first time, run the following in your shell: - -```bash -sam build -sam deploy --guided -``` - -The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: - -* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. -* **AWS Region**: The AWS region you want to deploy your app to. -* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. -* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modified IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. -* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. - -You can find your API Gateway Endpoint URL in the output values displayed after deployment. - -## Use the SAM CLI to build and test locally - -Build your application with the `sam build` command. - -```bash -example$ sam build -``` - -The SAM CLI installs dependencies defined in `HelloWorldFunction/build.gradle`, creates a deployment package, and saves it in the `.aws-sam/build` folder. - -Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. - -Run functions locally and invoke them with the `sam local invoke` command. - -```bash -example$ sam local invoke HelloWorldFunction --event events/event.json -``` - -The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. - -```bash -example$ sam local start-api -example$ curl http://localhost:3000/ -``` - -The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. - -```yaml - Events: - HelloWorld: - Type: Api - Properties: - Path: /hello - Method: get -``` - -## Add a resource to your application -The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. - -## Fetch, tail, and filter Lambda function logs - -To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. - -`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. - -```bash -example$ sam logs -n HelloWorldFunction --stack-name sam-app --tail -``` - -You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). - -## Unit tests - -Tests are defined in the `HelloWorldFunction/src/test` folder in this project. - -```bash -example$ cd HelloWorldFunction -HelloWorldFunction$ gradle test -``` - -## Cleanup - -To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: - -```bash -aws cloudformation delete-stack --stack-name example -``` - -## Resources - -See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. - -Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/example/events/eventSqs.json b/example/events/eventSqs.json deleted file mode 100644 index 37a29c4dd..000000000 --- a/example/events/eventSqs.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "Records": [ - { - "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb99", - "receiptHandle": "MessageReceiptHandle", - "body": "Hello from SQS!", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1523232000000", - "SenderId": "123456789012", - "ApproximateFirstReceiveTimestamp": "1523232000001" - }, - "messageAttributes": {}, - "md5OfBody": "7b270e59b47ff90a553787216d55d999", - "eventSource": "aws:sqs", - "eventSourceARN": "arn:aws:sqs:eu-west-1:123456789:powertools-example-TestSqsQueue-1JW5W8N9", - "awsRegion": "eu-west-1" - }, - { - "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", - "receiptHandle": "MessageReceiptHandle", - "body": "Hello from SQS!", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1523232000000", - "SenderId": "123456789012", - "ApproximateFirstReceiveTimestamp": "1523232000001" - }, - "messageAttributes": {}, - "md5OfBody": "7b270e59b47ff90a553787216d55d91d", - "eventSource": "aws:sqs", - "eventSourceARN": "arn:aws:sqs:eu-west-1:123456789:powertools-example-TestSqsQueue-1JW5W8N9", - "awsRegion": "eu-west-1" - } - ] -} \ No newline at end of file diff --git a/example/template.yaml b/example/template.yaml deleted file mode 100644 index 435f187cf..000000000 --- a/example/template.yaml +++ /dev/null @@ -1,214 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: > - sam-app - - Sample SAM Template for sam-app - -# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst -Globals: - Function: - Timeout: 20 - Runtime: java11 - -Resources: - HelloWorldFunction: - Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction - Properties: - CodeUri: HelloWorldFunction - Handler: helloworld.App::handleRequest - MemorySize: 512 - Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object - Variables: - POWERTOOLS_SERVICE_NAME: "Payment Service" - POWERTOOLS_LOG_LEVEL: INFO - Tracing: Active - Events: - HelloWorld: - Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api - Properties: - Path: /hello - Method: get - - HelloWorldValidationFunction: - Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction - Properties: - CodeUri: HelloWorldFunction - Handler: helloworld.AppValidation::handleRequest - MemorySize: 512 - Tracing: Active - Events: - HelloWorld: - Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api - Properties: - Path: /hello - Method: post - - HelloWorldStreamFunction: - Type: AWS::Serverless::Function - Properties: - CodeUri: HelloWorldFunction - Handler: helloworld.AppStream::handleRequest - MemorySize: 512 - Tracing: Active - Environment: - Variables: - POWERTOOLS_LOGGER_SAMPLE_RATE: 0.7 - Events: - HelloWorld: - Type: Api - Properties: - Path: /hellostream - Method: get - - HelloWorldParamsFunction: - Type: AWS::Serverless::Function - Properties: - CodeUri: HelloWorldFunction - Handler: helloworld.AppParams::handleRequest - MemorySize: 512 - Tracing: Active - Policies: - - AWSSecretsManagerGetSecretValuePolicy: - SecretArn: !Ref UserPwd - - AWSSecretsManagerGetSecretValuePolicy: - SecretArn: !Ref SecretConfig - - Statement: - - Sid: SSMGetParameterPolicy - Effect: Allow - Action: - - ssm:GetParameter - - ssm:GetParameters - - ssm:GetParametersByPath - Resource: '*' - Events: - HelloWorld: - Type: Api - Properties: - Path: /helloparams - Method: get - - UserPwd: - Type: AWS::SecretsManager::Secret - Properties: - Name: /powertools-java/userpwd - Description: Generated secret for lambda-powertools-java powertools-parameters - module - GenerateSecretString: - SecretStringTemplate: '{"username": "test-user"}' - GenerateStringKey: password - PasswordLength: 15 - ExcludeCharacters: '"@/\' - SecretConfig: - Type: AWS::SecretsManager::Secret - Properties: - Name: /powertools-java/secretcode - Description: Json secret for lambda-powertools-java powertools-parameters module - SecretString: '{"id":23443,"code":"hk38543oj24kn796kp67bkb234gkj679l68"}' - BasicParameter: - Type: AWS::SSM::Parameter - Properties: - Name: /powertools-java/sample/simplekey - Type: String - Value: simplevalue - Description: Simple SSM Parameter for lambda-powertools-java powertools-parameters - module - ParameterList: - Type: AWS::SSM::Parameter - Properties: - Name: /powertools-java/sample/keylist - Type: StringList - Value: value1,value2,value3 - Description: SSM Parameter List for lambda-powertools-java powertools-parameters - module - JsonParameter: - Type: AWS::SSM::Parameter - Properties: - Name: /powertools-java/sample/keyjson - Type: String - Value: '{"id":23443,"code":"hk38543oj24kn796kp67bkb234gkj679l68"}' - Description: Json SSM Parameter for lambda-powertools-java powertools-parameters - module - Base64Parameter: - Type: AWS::SSM::Parameter - Properties: - Name: /powertools-java/sample/keybase64 - Type: String - Value: aGVsbG8gd29ybGQ= - Description: Base64 SSM Parameter for lambda-powertools-java powertools-parameters module - - TestSqsQueue: - Type: AWS::SQS::Queue - - HelloWorldSqsEventFunction: - Type: AWS::Serverless::Function - Properties: - CodeUri: HelloWorldFunction - Handler: helloworld.AppSqsEvent::handleRequest - MemorySize: 512 - Tracing: Active - Policies: - - Statement: - - Sid: AdditionalPermisssionForPowertoolsSQSUtils - Effect: Allow - Action: - - sqs:GetQueueUrl - - sqs:DeleteMessageBatch - Resource: !GetAtt TestSqsQueue.Arn - Events: - TestSQSEvent: - Type: SQS - Properties: - Queue: !GetAtt TestSqsQueue.Arn - BatchSize: 10 - - TestAnotherSqsQueue: - Type: AWS::SQS::Queue - - HelloWorldSqsEventUtilFunction: - Type: AWS::Serverless::Function - Properties: - CodeUri: HelloWorldFunction - Handler: helloworld.AppSqsEventUtil::handleRequest - MemorySize: 512 - Tracing: Active - Policies: - - Statement: - - Sid: AdditionalPermisssionForPowertoolsSQSUtils - Effect: Allow - Action: - - sqs:GetQueueUrl - - sqs:DeleteMessageBatch - Resource: !GetAtt TestAnotherSqsQueue.Arn - Events: - TestSQSEvent: - Type: SQS - Properties: - Queue: !GetAtt TestAnotherSqsQueue.Arn - BatchSize: 10 - -Outputs: - # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function - # Find out more about other implicit resources you can reference within SAM - # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api - HelloWorldApi: - Description: "API Gateway endpoint URL for Prod stage for Hello World function" - Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" - HelloWorldFunction: - Description: "Hello World Lambda Function ARN" - Value: !GetAtt HelloWorldFunction.Arn - - HelloWorldStreamApi: - Description: "API Gateway endpoint URL for Prod stage for Hello World stream function" - Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hellostream/" - HelloWorldStreamFunction: - Description: "Hello World Stream Lambda Function ARN" - Value: !GetAtt HelloWorldStreamFunction.Arn - - HelloWorldParamsApi: - Description: "API Gateway endpoint URL for Prod stage for Hello World params function" - Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/helloparams/" - HelloWorldParamsFunction: - Description: "Hello World Params Lambda Function ARN" - Value: !GetAtt HelloWorldParamsFunction.Arn - diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 000000000..892320d3b --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,4 @@ +dependency-reduced-pom.xml +.aws-sam +cdk.out +.m2 diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..727bc652e --- /dev/null +++ b/examples/README.md @@ -0,0 +1,82 @@ +# Powertools for AWS Lambda (Java) Examples + +This directory holds example projects demoing different components of the Powertools for AWS Lambda (Java). + +Each example can be copied from its subdirectory and used independently of the rest of this repository. + +## Examples + +* [powertools-examples-core-utilities](powertools-examples-core-utilities) - Demonstrates the core logging, tracing, and metrics modules with different build tools and languages + * [CDK](./powertools-examples-core-utilities/cdk) + * [SAM](./powertools-examples-core-utilities/sam) + * [SAM GraalVM](./powertools-examples-core-utilities/sam-graalvm) + * [Serverless](./powertools-examples-core-utilities/serverless) + * [Terraform](./powertools-examples-core-utilities/terraform) + * [Gradle](./powertools-examples-core-utilities/gradle) + * [Kotlin](./powertools-examples-core-utilities/kotlin) +* [powertools-examples-idempotency](powertools-examples-idempotency) - An idempotent HTTP API + * [SAM](./powertools-examples-idempotency/sam) + * [SAM GraalVM](./powertools-examples-idempotency/sam-graalvm) +* [powertools-examples-parameters](powertools-examples-parameters) - Uses the parameters module to provide runtime parameters to a function + * [SAM](./powertools-examples-parameters/sam) + * [SAM GraalVM](./powertools-examples-parameters/sam-graalvm) +* [powertools-examples-serialization](powertools-examples-serialization) - Uses the serialization module to serialize and deserialize API Gateway & SQS payloads + * [SAM](./powertools-examples-serialization/sam) + * [SAM GraalVM](./powertools-examples-serialization/sam-graalvm) +* [powertools-examples-validation](powertools-examples-validation) - Uses the validation module to validate user requests received via API Gateway +* [powertools-examples-cloudformation](powertools-examples-cloudformation) - Deploys a Cloudformation custom resource +* [powertools-examples-batch](powertools-examples-batch) - Examples for each of the different batch processing deployments +* [powertools-examples-kafka](powertools-examples-kafka) - Examples for Kafka event processing + +## Working with AWS Serverless Application Model (SAM) Examples +Many of the examples use [AWS Serverless Application Model](https://aws.amazon.com/serverless/sam/) (SAM). To get started with them, you can use the SAM Command Line Interface (SAM CLI) to build it and deploy an example to AWS. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* Java11 - [Install the Java 11](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html) +* Maven - [Install Maven](https://maven.apache.org/install.html) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To learn more about SAM, [check out the developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli.html). You can use the CLI to [test events locally](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-local-invoke.html), and [run the application locally](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-local-start-api.html), amongst other things. + +To build and deploy an example application for the first time, run the following in your shell: + +```bash +# Switch to the directory containing an example for the powertools-idempotency module +$ cd powertools-examples-idempotency/sam + +# Build and deploy the example +$ sam build +$ sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modified IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +If you're not using SAM, you can look for examples for other tools under [powertools-examples-core-utilities](./powertools-examples-core-utilities) + +### External examples + +You can find more examples in the https://github.com/aws/aws-sam-cli-app-templates project: + +* [Java 11 + Maven](https://github.com/aws/aws-sam-cli-app-templates/tree/master/java11/hello-pt-maven) +* [Java 17 + Maven](https://github.com/aws/aws-sam-cli-app-templates/tree/master/java17/hello-pt-maven) +* [Java 17 + Gradle](https://github.com/aws/aws-sam-cli-app-templates/tree/master/java17/hello-pt-gradle) + + +### SAM - Other Tools + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. + +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) diff --git a/examples/pom.xml b/examples/pom.xml new file mode 100644 index 000000000..5d191063f --- /dev/null +++ b/examples/pom.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-examples</artifactId> + <version>2.9.0</version> + <packaging>pom</packaging> + + <name>Powertools for AWS Lambda (Java) - Examples</name> + <description> + A suite of examples accompanying for Powertools for AWS Lambda (Java). + </description> + + <modules> + <module>powertools-examples-core-utilities/sam</module> + <module>powertools-examples-core-utilities/sam-graalvm</module> + <module>powertools-examples-core-utilities/cdk/app</module> + <module>powertools-examples-core-utilities/cdk/infra</module> + <module>powertools-examples-core-utilities/serverless</module> + <module>powertools-examples-core-utilities/terraform</module> + <module>powertools-examples-idempotency/sam</module> + <module>powertools-examples-idempotency/sam-graalvm</module> + <module>powertools-examples-parameters/sam</module> + <module>powertools-examples-parameters/sam-graalvm</module> + <module>powertools-examples-serialization/sam</module> + <module>powertools-examples-serialization/sam-graalvm</module> + <module>powertools-examples-kafka</module> + <module>powertools-examples-batch</module> + <module>powertools-examples-validation</module> + <module>powertools-examples-cloudformation</module> + </modules> + + <build> + <plugins> + <!-- Don't deploy the examples --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/examples/powertools-examples-batch/README.md b/examples/powertools-examples-batch/README.md new file mode 100644 index 000000000..d65fb584a --- /dev/null +++ b/examples/powertools-examples-batch/README.md @@ -0,0 +1,35 @@ + # Powertools for AWS Lambda (Java) - Batch Example + +This project contains examples of Lambda function using the batch processing module of Powertools for AWS Lambda (Java). +For more information on this module, please refer to the +[documentation](https://docs.powertools.aws.dev/lambda-java/utilities/batch/). + +Three different examples and SAM deployments are included, covering each of the batch sources: + +* [SQS](src/main/java/org/demo/batch/sqs) - SQS batch processing +* [Kinesis Streams](src/main/java/org/demo/batch/kinesis) - Kinesis Streams batch processing +* [DynamoDB Streams](src/main/java/org/demo/batch/dynamo) - DynamoDB Streams batch processing + +## Deploy the sample application + +This sample is based on Serverless Application Model (SAM). To deploy it, check out the instructions for getting +started with SAM in [the examples directory](../README.md) + +This sample contains three different deployments, depending on which batch processor you'd like to use, you can +change to the subdirectory containing the example SAM template, and deploy. For instance, for the SQS batch +deployment: + +```bash +cd deploy/sqs +sam build +sam deploy --guided +``` + +## Test the application + +Each of the examples uses a Lambda scheduled every 5 minutes to push a batch, and a separate lambda to read it. To +see this in action, we can simply tail the logs of our stack: + +```bash +sam logs --tail $STACK_NAME +``` \ No newline at end of file diff --git a/examples/powertools-examples-batch/deploy/ddb-streams/template.yaml b/examples/powertools-examples-batch/deploy/ddb-streams/template.yaml new file mode 100644 index 000000000..91f8799c4 --- /dev/null +++ b/examples/powertools-examples-batch/deploy/ddb-streams/template.yaml @@ -0,0 +1,74 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + DynamoDB Streams batch processing demo + +Globals: + Function: + Timeout: 20 + Runtime: java11 + MemorySize: 512 + Tracing: Active + Architectures: + - x86_64 + Environment: + Variables: + POWERTOOLS_LOG_LEVEL: INFO + POWERTOOLS_LOGGER_SAMPLE_RATE: 1.0 + POWERTOOLS_LOGGER_LOG_EVENT: true + +Resources: + DynamoDBTable: + Type: AWS::DynamoDB::Table + Properties: + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + ProvisionedThroughput: + ReadCapacityUnits: 5 + WriteCapacityUnits: 5 + StreamSpecification: + StreamViewType: NEW_IMAGE + + + DemoDynamoDBWriter: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../.. + Handler: org.demo.batch.dynamo.DynamoDBWriter::handleRequest + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: ddbstreams-demo + TABLE_NAME: !Ref DynamoDBTable + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref DynamoDBTable + Events: + CWSchedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Name: !Join [ "-", [ "ddb-writer-schedule", !Select [ 0, !Split [ -, !Select [ 2, !Split [ /, !Ref AWS::StackId ] ] ] ] ] ] + Description: Write records to DynamoDB via a Lambda function + Enabled: true + + DemoDynamoDBStreamsConsumerFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../.. + Handler: org.demo.batch.dynamo.DynamoDBStreamBatchHandler::handleRequest + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: ddbstreams-batch-demo + Policies: AWSLambdaDynamoDBExecutionRole + Events: + Stream: + Type: DynamoDB + Properties: + Stream: !GetAtt DynamoDBTable.StreamArn + BatchSize: 100 + StartingPosition: TRIM_HORIZON + diff --git a/examples/powertools-examples-batch/deploy/kinesis/template.yml b/examples/powertools-examples-batch/deploy/kinesis/template.yml new file mode 100644 index 000000000..dcece61b8 --- /dev/null +++ b/examples/powertools-examples-batch/deploy/kinesis/template.yml @@ -0,0 +1,83 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + Kinesis batch processing demo + +Globals: + Function: + Timeout: 20 + Runtime: java11 + MemorySize: 512 + Tracing: Active + Environment: + Variables: + POWERTOOLS_LOG_LEVEL: INFO + POWERTOOLS_LOGGER_SAMPLE_RATE: 1.0 + POWERTOOLS_LOGGER_LOG_EVENT: true + +Resources: + + DemoKinesisStream: + Type: AWS::Kinesis::Stream + Properties: + ShardCount: 1 + + StreamConsumer: + Type: "AWS::Kinesis::StreamConsumer" + Properties: + StreamARN: !GetAtt DemoKinesisStream.Arn + ConsumerName: KinesisBatchHandlerConsumer + + DemoKinesisSenderFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../.. + Handler: org.demo.batch.kinesis.KinesisBatchSender::handleRequest + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: kinesis-batch-demo + STREAM_NAME: !Ref DemoKinesisStream + Policies: + - Statement: + - Sid: WriteToKinesis + Effect: Allow + Action: + - kinesis:PutRecords + - kinesis:DescribeStream + Resource: !GetAtt DemoKinesisStream.Arn + Events: + CWSchedule: + Type: Schedule + Properties: + Schedule: 'rate(5 minutes)' + Name: !Join [ "-", [ "message-producer-schedule", !Select [ 0, !Split [ -, !Select [ 2, !Split [ /, !Ref AWS::StackId ] ] ] ] ] ] + Description: Produce message to Kinesis via a Lambda function + Enabled: true + + DemoKinesisConsumerFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../.. + Handler: org.demo.batch.kinesis.KinesisBatchHandler::handleRequest + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: kinesis-demo + Events: + Kinesis: + Type: Kinesis + Properties: + Stream: !GetAtt StreamConsumer.ConsumerARN + StartingPosition: LATEST + BatchSize: 2 + +Outputs: + DemoKinesisQueue: + Description: "ARN for Kinesis Stream" + Value: !GetAtt DemoKinesisStream.Arn + DemoKinesisSenderFunction: + Description: "Kinesis Batch Sender - Lambda Function ARN" + Value: !GetAtt DemoKinesisSenderFunction.Arn + DemoSQSConsumerFunction: + Description: "SQS Batch Handler - Lambda Function ARN" + Value: !GetAtt DemoKinesisConsumerFunction.Arn + diff --git a/examples/powertools-examples-batch/deploy/sqs/template.yml b/examples/powertools-examples-batch/deploy/sqs/template.yml new file mode 100644 index 000000000..1232e4d51 --- /dev/null +++ b/examples/powertools-examples-batch/deploy/sqs/template.yml @@ -0,0 +1,200 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + sqs batch processing demo + +Globals: + Function: + Timeout: 20 + Runtime: java11 + MemorySize: 5400 + Environment: + Variables: + POWERTOOLS_LOG_LEVEL: INFO + POWERTOOLS_LOGGER_LOG_EVENT: true + +Resources: + CustomerKey: + Type: AWS::KMS::Key + Properties: + Description: KMS key for encrypted queues + Enabled: true + KeyPolicy: + Version: '2012-10-17' + Statement: + - Sid: Enable IAM User Permissions + Effect: Allow + Principal: + AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root' + Action: 'kms:*' + Resource: '*' + - Sid: Allow use of the key + Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: + - kms:Decrypt + - kms:GenerateDataKey + Resource: '*' + + CustomerKeyAlias: + Type: AWS::KMS::Alias + Properties: + AliasName: alias/powertools-batch-sqs-demo + TargetKeyId: !Ref CustomerKey + + Bucket: + Type: AWS::S3::Bucket + + DemoDlqSqsQueue: + Type: AWS::SQS::Queue + Properties: + KmsMasterKeyId: !Ref CustomerKey + + DemoSqsQueue: + Type: AWS::SQS::Queue + Properties: + RedrivePolicy: + deadLetterTargetArn: + Fn::GetAtt: + - "DemoDlqSqsQueue" + - "Arn" + maxReceiveCount: 2 + KmsMasterKeyId: !Ref CustomerKey + + DemoSQSSenderFunction: + Type: AWS::Serverless::Function + Properties: + Tracing: Active + CodeUri: ../.. + Handler: org.demo.batch.sqs.SqsBatchSender::handleRequest + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: sqs-batch-demo + QUEUE_URL: !Ref DemoSqsQueue + Policies: + - Statement: + - Sid: SQSSendMessageBatch + Effect: Allow + Action: + - sqs:SendMessageBatch + - sqs:SendMessage + Resource: !GetAtt DemoSqsQueue.Arn + - Sid: SQSKMSKey + Effect: Allow + Action: + - kms:GenerateDataKey + - kms:Decrypt + Resource: !GetAtt CustomerKey.Arn + Events: + CWSchedule: + Type: Schedule + Properties: + Schedule: 'rate(5 minutes)' + Name: !Join [ "-", [ "message-producer-schedule", !Select [ 0, !Split [ -, !Select [ 2, !Split [ /, !Ref AWS::StackId ] ] ] ] ] ] + Description: Produce message to SQS via a Lambda function + Enabled: true + + DemoSQSConsumerFunction: + Type: AWS::Serverless::Function + Properties: + Tracing: Active + CodeUri: ../.. + Handler: org.demo.batch.sqs.SqsBatchHandler::handleRequest + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: sqs-demo + BUCKET: !Ref Bucket + Policies: + - Statement: + - Sid: SQSDeleteGetAttribute + Effect: Allow + Action: + - sqs:DeleteMessageBatch + - sqs:GetQueueAttributes + Resource: !GetAtt DemoSqsQueue.Arn + - Sid: SQSSendMessageBatch + Effect: Allow + Action: + - sqs:SendMessageBatch + - sqs:SendMessage + Resource: !GetAtt DemoDlqSqsQueue.Arn + - Sid: SQSKMSKey + Effect: Allow + Action: + - kms:GenerateDataKey + - kms:Decrypt + Resource: !GetAtt CustomerKey.Arn + - Sid: WriteToS3 + Effect: Allow + Action: + - s3:PutObject + Resource: !Sub ${Bucket.Arn}/* + +# Events: +# MySQSEvent: +# Type: SQS +# Properties: +# Queue: !GetAtt DemoSqsQueue.Arn +# BatchSize: 100 +# MaximumBatchingWindowInSeconds: 60 + + DemoSQSParallelConsumerFunction: + Type: AWS::Serverless::Function + Properties: + Tracing: Active + CodeUri: ../.. + Handler: org.demo.batch.sqs.SqsParallelBatchHandler::handleRequest + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: sqs-demo + BUCKET: !Ref Bucket + Policies: + - Statement: + - Sid: SQSDeleteGetAttribute + Effect: Allow + Action: + - sqs:DeleteMessageBatch + - sqs:GetQueueAttributes + Resource: !GetAtt DemoSqsQueue.Arn + - Sid: SQSSendMessageBatch + Effect: Allow + Action: + - sqs:SendMessageBatch + - sqs:SendMessage + Resource: !GetAtt DemoDlqSqsQueue.Arn + - Sid: SQSKMSKey + Effect: Allow + Action: + - kms:GenerateDataKey + - kms:Decrypt + Resource: !GetAtt CustomerKey.Arn + - Sid: WriteToS3 + Effect: Allow + Action: + - s3:PutObject + Resource: !Sub ${Bucket.Arn}/* + Events: + MySQSEvent: + Type: SQS + Properties: + Queue: !GetAtt DemoSqsQueue.Arn + BatchSize: 100 + MaximumBatchingWindowInSeconds: 60 + +Outputs: + DemoSqsQueue: + Description: "ARN for main SQS queue" + Value: !GetAtt DemoSqsQueue.Arn + DemoDlqSqsQueue: + Description: "ARN for DLQ" + Value: !GetAtt DemoDlqSqsQueue.Arn + DemoSQSSenderFunction: + Description: "SQS Batch Sender - Lambda Function ARN" + Value: !GetAtt DemoSQSSenderFunction.Arn + DemoSQSConsumerFunction: + Description: "SQS Batch Handler - Lambda Function ARN" + Value: !GetAtt DemoSQSConsumerFunction.Arn + DemoSQSConsumerFunctionRole: + Description: "Implicit IAM Role created for SQS Lambda Function ARN" + Value: !GetAtt DemoSQSConsumerFunctionRole.Arn diff --git a/examples/powertools-examples-batch/pom.xml b/examples/powertools-examples-batch/pom.xml new file mode 100644 index 000000000..0091fb5ca --- /dev/null +++ b/examples/powertools-examples-batch/pom.xml @@ -0,0 +1,157 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-batch</artifactId> + <packaging>jar</packaging> + <name>Powertools for AWS Lambda (Java) - Examples - Batch</name> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <aspectj.version>1.9.20.1</aspectj.version> + <sdk.version>2.39.3</sdk.version> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-batch</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + <version>${sdk.version}</version> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <version>${sdk.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sqs</artifactId> + <version>${sdk.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>url-connection-client</artifactId> + <version>${sdk.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>dynamodb-enhanced</artifactId> + <version>${sdk.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>kinesis</artifactId> + <version>${sdk.version}</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>3.6.1</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <transformers> + <transformer implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer"/> + </transformers> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId> + <version>0.2.0</version> + </dependency> + </dependencies> + </plugin> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> +</project> \ No newline at end of file diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBStreamBatchHandler.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBStreamBatchHandler.java new file mode 100644 index 000000000..4f27929f0 --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBStreamBatchHandler.java @@ -0,0 +1,32 @@ +package org.demo.batch.dynamo; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.DynamodbEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; + +public class DynamoDBStreamBatchHandler implements RequestHandler<DynamodbEvent, StreamsEventResponse> { + + private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDBStreamBatchHandler.class); + private final BatchMessageHandler<DynamodbEvent, StreamsEventResponse> handler; + + public DynamoDBStreamBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withDynamoDbBatchHandler() + .buildWithRawMessageHandler(this::processMessage); + } + + @Override + public StreamsEventResponse handleRequest(DynamodbEvent ddbEvent, Context context) { + return handler.processBatch(ddbEvent, context); + } + + private void processMessage(DynamodbEvent.DynamodbStreamRecord dynamodbStreamRecord) { + LOGGER.info("Processing DynamoDB Stream Record" + dynamodbStreamRecord); + } + +} diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBStreamBatchHandlerParallel.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBStreamBatchHandlerParallel.java new file mode 100644 index 000000000..2e784b795 --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBStreamBatchHandlerParallel.java @@ -0,0 +1,37 @@ +package org.demo.batch.dynamo; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.DynamodbEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class DynamoDBStreamBatchHandlerParallel implements RequestHandler<DynamodbEvent, StreamsEventResponse> { + + private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDBStreamBatchHandlerParallel.class); + private final BatchMessageHandler<DynamodbEvent, StreamsEventResponse> handler; + private final ExecutorService executor; + + public DynamoDBStreamBatchHandlerParallel() { + handler = new BatchMessageHandlerBuilder() + .withDynamoDbBatchHandler() + .buildWithRawMessageHandler(this::processMessage); + executor = Executors.newFixedThreadPool(2); + } + + @Override + public StreamsEventResponse handleRequest(DynamodbEvent ddbEvent, Context context) { + return handler.processBatchInParallel(ddbEvent, context, executor); + } + + private void processMessage(DynamodbEvent.DynamodbStreamRecord dynamodbStreamRecord) { + LOGGER.info("Processing DynamoDB Stream Record" + dynamodbStreamRecord); + } + +} diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBWriter.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBWriter.java new file mode 100644 index 000000000..fc1b0747b --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBWriter.java @@ -0,0 +1,108 @@ +package org.demo.batch.dynamo; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; +import java.security.SecureRandom; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.demo.batch.model.DdbProduct; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.model.BatchWriteItemEnhancedRequest; +import software.amazon.awssdk.enhanced.dynamodb.model.BatchWriteResult; +import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + +public class DynamoDBWriter implements RequestHandler<ScheduledEvent, String> { + + private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDBWriter.class); + + private final DynamoDbEnhancedClient enhancedClient; + + private final SecureRandom random; + + public DynamoDBWriter() { + random = new SecureRandom(); + DynamoDbClient dynamoDbClient = DynamoDbClient.builder() + .httpClientBuilder(UrlConnectionHttpClient.builder()) + .build(); + + enhancedClient = DynamoDbEnhancedClient.builder() + .dynamoDbClient(dynamoDbClient) + .build(); + } + + @Override + public String handleRequest(ScheduledEvent scheduledEvent, Context context) { + String tableName = System.getenv("TABLE_NAME"); + + LOGGER.info("handleRequest"); + + List<DdbProduct> products = createProducts(tableName); + List<DdbProduct> updatedProducts = updateProducts(tableName, products); + deleteProducts(tableName, updatedProducts); + + return "Success"; + } + + private void deleteProducts(String tableName, List<DdbProduct> updatedProducts) { + WriteBatch.Builder<DdbProduct> productDeleteBuilder = WriteBatch.builder(DdbProduct.class) + .mappedTableResource(enhancedClient.table(tableName, TableSchema.fromBean(DdbProduct.class))); + + updatedProducts.forEach(productDeleteBuilder::addDeleteItem); + + BatchWriteResult batchDeleteResult = enhancedClient + .batchWriteItem(BatchWriteItemEnhancedRequest.builder().writeBatches( + productDeleteBuilder.build()) + .build()); + LOGGER.info("Deleted batch of objects from DynamoDB: {}", batchDeleteResult); + } + + private List<DdbProduct> updateProducts(String tableName, List<DdbProduct> products) { + WriteBatch.Builder<DdbProduct> productUpdateBuilder = WriteBatch.builder(DdbProduct.class) + .mappedTableResource(enhancedClient.table(tableName, TableSchema.fromBean(DdbProduct.class))); + + List<DdbProduct> updatedProducts = products.stream().map(product -> { + // Update the price of the product and add it to the batch + LOGGER.info("Updating product: {}", product); + float price = random.nextFloat(); + DdbProduct updatedProduct = new DdbProduct(product.getId(), "updated-product-" + product.getId(), price); + productUpdateBuilder.addPutItem(updatedProduct); + return updatedProduct; + }).collect(Collectors.toList()); + + BatchWriteResult batchUpdateResult = enhancedClient + .batchWriteItem(BatchWriteItemEnhancedRequest.builder().writeBatches( + productUpdateBuilder.build()) + .build()); + LOGGER.info("Updated batch of objects to DynamoDB: {}", batchUpdateResult); + return updatedProducts; + } + + public List<DdbProduct> createProducts(String tableName) { + WriteBatch.Builder<DdbProduct> productBuilder = WriteBatch.builder(DdbProduct.class) + .mappedTableResource(enhancedClient.table(tableName, TableSchema.fromBean(DdbProduct.class))); + + List<DdbProduct> ddbProductStream = IntStream.range(0, 5).mapToObj(i -> { + String id = UUID.randomUUID().toString(); + float price = random.nextFloat(); + // Create a new product and add it to the batch + final DdbProduct product = new DdbProduct(id, "product-" + id, price); + productBuilder.addPutItem(product); + return product; + }).collect(Collectors.toList()); + + BatchWriteResult batchWriteResult = enhancedClient + .batchWriteItem(BatchWriteItemEnhancedRequest.builder().writeBatches( + productBuilder.build()) + .build()); + LOGGER.info("Wrote batch of objects to DynamoDB: {}", batchWriteResult); + return ddbProductStream; + } +} diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchHandler.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchHandler.java new file mode 100644 index 000000000..b7b4f462e --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchHandler.java @@ -0,0 +1,33 @@ +package org.demo.batch.kinesis; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.demo.batch.model.Product; +import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; + +public class KinesisBatchHandler implements RequestHandler<KinesisEvent, StreamsEventResponse> { + + private static final Logger LOGGER = LoggerFactory.getLogger(KinesisBatchHandler.class); + private final BatchMessageHandler<KinesisEvent, StreamsEventResponse> handler; + + public KinesisBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withKinesisBatchHandler() + .buildWithMessageHandler(this::processMessage, Product.class); + } + + @Override + public StreamsEventResponse handleRequest(KinesisEvent kinesisEvent, Context context) { + return handler.processBatch(kinesisEvent, context); + } + + private void processMessage(Product p) { + LOGGER.info("Processing product " + p); + } + +} diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchHandlerParallel.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchHandlerParallel.java new file mode 100644 index 000000000..d7e99e85a --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchHandlerParallel.java @@ -0,0 +1,39 @@ +package org.demo.batch.kinesis; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; +import org.demo.batch.model.Product; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class KinesisBatchHandlerParallel implements RequestHandler<KinesisEvent, StreamsEventResponse> { + + private static final Logger LOGGER = LoggerFactory.getLogger(KinesisBatchHandlerParallel.class); + private final BatchMessageHandler<KinesisEvent, StreamsEventResponse> handler; + private final ExecutorService executor; + + + public KinesisBatchHandlerParallel() { + handler = new BatchMessageHandlerBuilder() + .withKinesisBatchHandler() + .buildWithMessageHandler(this::processMessage, Product.class); + executor = Executors.newFixedThreadPool(2); + } + + @Override + public StreamsEventResponse handleRequest(KinesisEvent kinesisEvent, Context context) { + return handler.processBatchInParallel(kinesisEvent, context, executor); + } + + private void processMessage(Product p) { + LOGGER.info("Processing product " + p); + } + +} diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchSender.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchSender.java new file mode 100644 index 000000000..dadead1a2 --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchSender.java @@ -0,0 +1,78 @@ +package org.demo.batch.kinesis; + +import static java.util.stream.Collectors.toList; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.security.SecureRandom; +import java.util.List; +import java.util.stream.IntStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.demo.batch.model.Product; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.services.kinesis.KinesisClient; +import software.amazon.awssdk.services.kinesis.model.PutRecordsRequest; +import software.amazon.awssdk.services.kinesis.model.PutRecordsRequestEntry; +import software.amazon.awssdk.services.kinesis.model.PutRecordsResponse; + + +/** + * A Lambda handler used to send message batches to Kinesis Streams. This is only here + * to produce an end-to-end demo, so that the {{@link org.demo.batch.kinesis.KinesisBatchHandler}} + * has some data to consume. + */ +public class KinesisBatchSender implements RequestHandler<ScheduledEvent, String> { + + private static final Logger LOGGER = LoggerFactory.getLogger(KinesisBatchSender.class); + + private final KinesisClient kinesisClient; + private final SecureRandom random; + private final ObjectMapper objectMapper; + + public KinesisBatchSender() { + kinesisClient = KinesisClient.builder() + .httpClient(UrlConnectionHttpClient.create()) + .build(); + random = new SecureRandom(); + objectMapper = new ObjectMapper(); + } + + @Override + public String handleRequest(ScheduledEvent scheduledEvent, Context context) { + String streamName = System.getenv("STREAM_NAME"); + + LOGGER.info("handleRequest"); + + // Push 5 messages on each invoke. + List<PutRecordsRequestEntry> records = IntStream.range(0, 5) + .mapToObj(value -> { + long id = random.nextLong(); + float price = random.nextFloat(); + Product product = new Product(id, "product-" + id, price); + try { + SdkBytes data = SdkBytes.fromUtf8String(objectMapper.writeValueAsString(product)); + return PutRecordsRequestEntry.builder() + .partitionKey(String.format("%d", id)) + .data(data) + .build(); + } catch (JsonProcessingException e) { + LOGGER.error("Failed serializing body", e); + throw new RuntimeException(e); + } + }).collect(toList()); + + PutRecordsResponse putRecordsResponse = kinesisClient.putRecords(PutRecordsRequest.builder() + .streamName(streamName) + .records(records) + .build()); + + LOGGER.info("Sent Message {}", putRecordsResponse); + + return "Success"; + } +} diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/model/DdbProduct.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/model/DdbProduct.java new file mode 100644 index 000000000..9d69eac5d --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/model/DdbProduct.java @@ -0,0 +1,88 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.batch.model; + +import java.util.Objects; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; + +@DynamoDbBean +public class DdbProduct { + private String id; + + private String name; + + private double price; + + public DdbProduct() { + } + + public DdbProduct(String id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DdbProduct that = (DdbProduct) o; + return Double.compare(that.price, price) == 0 && Objects.equals(id, that.id) && Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, price); + } + + @Override + public String toString() { + return "Product{" + + "id=" + id + + ", name='" + name + '\'' + + ", price=" + price + + '}'; + } +} diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/model/Product.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/model/Product.java new file mode 100644 index 000000000..64da1804e --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/model/Product.java @@ -0,0 +1,84 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.batch.model; + +import java.util.Objects; + +public class Product { + private long id; + + private String name; + + private double price; + + public Product() { + } + + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Product product = (Product) o; + return id == product.id && Double.compare(product.price, price) == 0 && Objects.equals(name, product.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, price); + } + + @Override + public String toString() { + return "Product{" + + "id=" + id + + ", name='" + name + '\'' + + ", price=" + price + + '}'; + } +} diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/AbstractSqsBatchHandler.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/AbstractSqsBatchHandler.java new file mode 100644 index 000000000..1b3029d40 --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/AbstractSqsBatchHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 2024 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.batch.sqs; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Random; + +import org.demo.batch.model.Product; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.lambda.powertools.tracing.Tracing; +import software.amazon.lambda.powertools.tracing.TracingUtils; + +public class AbstractSqsBatchHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractSqsBatchHandler.class); + private final ObjectMapper mapper = new ObjectMapper(); + private final String bucket = System.getenv("BUCKET"); + private final S3Client s3 = S3Client.builder().httpClient(UrlConnectionHttpClient.create()).build(); + private final Random r = new Random(); + + /** + * Simulate some processing (I/O + S3 put request) + * @param p deserialized product + */ + @Tracing + protected void processMessage(Product p) { + TracingUtils.putAnnotation("productId", p.getId()); + TracingUtils.putAnnotation("Thread", Thread.currentThread().getName()); + MDC.put("product", String.valueOf(p.getId())); + LOGGER.info("Processing product {}", p); + + char c = (char) (r.nextInt(26) + 'a'); + char[] chars = new char[1024 * 1000]; + Arrays.fill(chars, c); + p.setName(new String(chars)); + try { + File file = new File("/tmp/" + p.getId() + ".json"); + mapper.writeValue(file, p); + s3.putObject( + PutObjectRequest.builder().bucket(bucket).key(p.getId() + ".json").build(), + RequestBody.fromFile(file)); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + MDC.remove("product"); + } + } +} diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchHandler.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchHandler.java new file mode 100644 index 000000000..bc0f57cb8 --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchHandler.java @@ -0,0 +1,32 @@ +package org.demo.batch.sqs; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import org.demo.batch.model.Product; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.tracing.Tracing; + +public class SqsBatchHandler extends AbstractSqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + private static final Logger LOGGER = LoggerFactory.getLogger(SqsBatchHandler.class); + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; + + public SqsBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithMessageHandler(this::processMessage, Product.class); + } + + @Logging + @Tracing + @Override + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + LOGGER.info("Processing batch of {} messages", sqsEvent.getRecords().size()); + return handler.processBatch(sqsEvent, context); + } +} diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchHandlerParallel.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchHandlerParallel.java new file mode 100644 index 000000000..21294dd55 --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchHandlerParallel.java @@ -0,0 +1,37 @@ +package org.demo.batch.sqs; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import org.demo.batch.model.Product; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.tracing.Tracing; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class SqsBatchHandlerParallel extends AbstractSqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + private static final Logger LOGGER = LoggerFactory.getLogger(SqsBatchHandlerParallel.class); + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; + private final ExecutorService executor; + + public SqsBatchHandlerParallel() { + handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithMessageHandler(this::processMessage, Product.class); + executor = Executors.newFixedThreadPool(2); + } + + @Logging + @Tracing + @Override + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + LOGGER.info("Processing batch of {} messages", sqsEvent.getRecords().size()); + return handler.processBatchInParallel(sqsEvent, context, executor); + } +} diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchSender.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchSender.java new file mode 100644 index 000000000..58b24d735 --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchSender.java @@ -0,0 +1,72 @@ +package org.demo.batch.sqs; + +import static java.util.stream.Collectors.toList; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.security.SecureRandom; +import java.util.List; +import java.util.stream.IntStream; +import org.demo.batch.model.Product; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.services.sqs.SqsClient; +import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequest; +import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequestEntry; + + +/** + * A Lambda handler used to send message batches to SQS. This is only here + * to produce an end-to-end demo, so that the {{@link org.demo.batch.sqs.SqsBatchHandler}} + * has some data to consume. + */ +public class SqsBatchSender implements RequestHandler<ScheduledEvent, String> { + + private static final Logger LOGGER = LoggerFactory.getLogger(SqsBatchSender.class); + + private final SqsClient sqsClient; + private final SecureRandom random; + private final ObjectMapper objectMapper; + + public SqsBatchSender() { + sqsClient = SqsClient.builder() + .httpClient(UrlConnectionHttpClient.create()) + .build(); + random = new SecureRandom(); + objectMapper = new ObjectMapper(); + } + + @Override + public String handleRequest(ScheduledEvent scheduledEvent, Context context) { + String queueUrl = System.getenv("QUEUE_URL"); + + List<SendMessageBatchRequestEntry> batchRequestEntries = IntStream.range(0, 50) + .mapToObj(value -> { + long id = Math.abs(random.nextLong()); + float price = Math.abs(random.nextFloat() * 3465); + Product product = new Product(id, "product-" + id, price); + try { + return SendMessageBatchRequestEntry.builder() + .id(scheduledEvent.getId() + value) + .messageBody(objectMapper.writeValueAsString(product)) + .build(); + } catch (JsonProcessingException e) { + LOGGER.error("Failed serializing body", e); + throw new RuntimeException(e); + } + }).collect(toList()); + + for (int i = 0; i < 50; i += 10) { + sqsClient.sendMessageBatch(SendMessageBatchRequest.builder() + .queueUrl(queueUrl) + .entries(batchRequestEntries.subList(i, i + 10)) + .build()); + } + + return "Success"; + } +} diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsParallelBatchHandler.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsParallelBatchHandler.java new file mode 100644 index 000000000..0151c0a32 --- /dev/null +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsParallelBatchHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2024 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.batch.sqs; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import org.demo.batch.model.Product; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.tracing.Tracing; + +public class SqsParallelBatchHandler extends AbstractSqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + private static final Logger LOGGER = LoggerFactory.getLogger(SqsParallelBatchHandler.class); + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; + + public SqsParallelBatchHandler() { + handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithMessageHandler(this::processMessage, Product.class); + } + + @Logging + @Tracing + @Override + public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + LOGGER.info("Processing batch of {} messages", sqsEvent.getRecords().size()); + MDC.put("requestId", context.getAwsRequestId()); // should be propagated to other threads + return handler.processBatchInParallel(sqsEvent, context); + } +} diff --git a/examples/powertools-examples-batch/src/main/resources/LogLayout.json b/examples/powertools-examples-batch/src/main/resources/LogLayout.json new file mode 100644 index 000000000..60f102e09 --- /dev/null +++ b/examples/powertools-examples-batch/src/main/resources/LogLayout.json @@ -0,0 +1,75 @@ +{ + "level": { + "$resolver": "level", + "field": "name" + }, + "message": { + "$resolver": "message" + }, + "error": { + "message": { + "$resolver": "exception", + "field": "message" + }, + "name": { + "$resolver": "exception", + "field": "className" + }, + "stack": { + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": true + } + } + }, + "cold_start": { + "$resolver": "powertools", + "field": "cold_start" + }, + "thread": { + "$resolver": "thread", + "field": "name" + }, + "function_arn": { + "$resolver": "powertools", + "field": "function_arn" + }, + "function_memory_size": { + "$resolver": "powertools", + "field": "function_memory_size" + }, + "function_name": { + "$resolver": "powertools", + "field": "function_name" + }, + "function_request_id": { + "$resolver": "powertools", + "field": "function_request_id" + }, + "function_version": { + "$resolver": "powertools", + "field": "function_version" + }, + "sampling_rate": { + "$resolver": "powertools", + "field": "sampling_rate" + }, + "service": { + "$resolver": "powertools", + "field": "service" + }, + "timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + } + }, + "xray_trace_id": { + "$resolver": "powertools", + "field": "xray_trace_id" + }, + "": { + "$resolver": "powertools" + } +} \ No newline at end of file diff --git a/examples/powertools-examples-batch/src/main/resources/log4j2.xml b/examples/powertools-examples-batch/src/main/resources/log4j2.xml new file mode 100644 index 000000000..c48ca3ef4 --- /dev/null +++ b/examples/powertools-examples-batch/src/main/resources/log4j2.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <!-- custom layout to show thread name in logs --> + <JsonTemplateLayout eventTemplateUri="classpath:LogLayout.json"/> + </Console> + </Appenders> + <Loggers> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + <Root level="info"> + <AppenderRef ref="JsonAppender"/> + </Root> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/Makefile b/examples/powertools-examples-cloudformation/Makefile new file mode 100644 index 000000000..b916d823c --- /dev/null +++ b/examples/powertools-examples-cloudformation/Makefile @@ -0,0 +1,5 @@ +build-HelloWorldFunction: + chmod +x target/hello-world + cp target/hello-world $(ARTIFACTS_DIR) # (ARTIFACTS_DIR --> https://github.com/aws/aws-lambda-builders/blob/develop/aws_lambda_builders/workflows/custom_make/DESIGN.md#implementation) + chmod +x src/main/config/bootstrap + cp src/main/config/bootstrap $(ARTIFACTS_DIR) diff --git a/examples/powertools-examples-cloudformation/README.md b/examples/powertools-examples-cloudformation/README.md new file mode 100644 index 000000000..27e564bf3 --- /dev/null +++ b/examples/powertools-examples-cloudformation/README.md @@ -0,0 +1,36 @@ +# Powertools for AWS Lambda (Java) - Cloudformation Custom Resource Example + +This project contains an example of Lambda function using the CloudFormation module of Powertools for AWS Lambda in Java. For more information on this module, please refer to the [documentation](https://awslabs.github.io/aws-lambda-powertools-java/utilities/custom_resources/). + +In this example you pass in a bucket name as a parameter and upon CloudFormation events a call is made to a lambda. That lambda attempts to create the bucket on CREATE events, create a new bucket if the name changes with an UPDATE event and delete the bucket upon DELETE events. + +## Deploy the sample application + +This sample can be used either with the Serverless Application Model (SAM) or with CDK. + +### Deploy with SAM CLI +To deploy it using the SAM CLI, check out the instructions for getting started in [the examples directory](../README.md) +Run the following in your shell: + +```bash +cd infra/sam +sam build +sam deploy --guided --parameter-overrides BucketNameParam=my-unique-bucket-2.9.0718 +``` + +### Deploy with CDK +To use CDK you need the following tools. + +* CDK - [Install CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) +* Java 11 - [Install Java 11](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html) +* Maven - [Install Maven](https://maven.apache.org/install.html) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy this application for the first time, run the following in your shell: + +```bash +cd infra/cdk +mvn package +cdk synth +cdk deploy -c BucketNameParam=my-unique-bucket-2.9.0718 +``` \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/infra/cdk/.gitignore b/examples/powertools-examples-cloudformation/infra/cdk/.gitignore new file mode 100644 index 000000000..1db21f162 --- /dev/null +++ b/examples/powertools-examples-cloudformation/infra/cdk/.gitignore @@ -0,0 +1,13 @@ +.classpath.txt +target +.classpath +.project +.idea +.settings +.vscode +*.iml + +# CDK asset staging directory +.cdk.staging +cdk.out + diff --git a/examples/powertools-examples-cloudformation/infra/cdk/cdk.json b/examples/powertools-examples-cloudformation/infra/cdk/cdk.json new file mode 100644 index 000000000..fe011b328 --- /dev/null +++ b/examples/powertools-examples-cloudformation/infra/cdk/cdk.json @@ -0,0 +1,37 @@ +{ + "app": "mvn -e -q compile exec:java", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "target", + "pom.xml", + "src/test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true + } +} diff --git a/examples/powertools-examples-cloudformation/infra/cdk/pom.xml b/examples/powertools-examples-cloudformation/infra/cdk/pom.xml new file mode 100644 index 000000000..30172fa3f --- /dev/null +++ b/examples/powertools-examples-cloudformation/infra/cdk/pom.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + + <groupId>com.myorg</groupId> + <artifactId>powertools-examples-cloudformation-cdk</artifactId> + <version>0.1</version> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <cdk.version>2.59.0</cdk.version> + <constructs.version>[10.0.0,11.0.0)</constructs.version> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.1</version> + <configuration> + <source>8</source> + <target>8</target> + </configuration> + </plugin> + + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>3.0.0</version> + <configuration> + <mainClass>com.myorg.PowertoolsExamplesCloudformationCdkApp</mainClass> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <!-- AWS Cloud Development Kit --> + <dependency> + <groupId>software.amazon.awscdk</groupId> + <artifactId>aws-cdk-lib</artifactId> + <version>${cdk.version}</version> + </dependency> + + <dependency> + <groupId>software.constructs</groupId> + <artifactId>constructs</artifactId> + <version>${constructs.version}</version> + </dependency> + </dependencies> +</project> diff --git a/examples/powertools-examples-cloudformation/infra/cdk/src/main/java/com/myorg/PowertoolsExamplesCloudformationCdkApp.java b/examples/powertools-examples-cloudformation/infra/cdk/src/main/java/com/myorg/PowertoolsExamplesCloudformationCdkApp.java new file mode 100644 index 000000000..f4a4d06d7 --- /dev/null +++ b/examples/powertools-examples-cloudformation/infra/cdk/src/main/java/com/myorg/PowertoolsExamplesCloudformationCdkApp.java @@ -0,0 +1,17 @@ +package com.myorg; + +import software.amazon.awscdk.App; +import software.amazon.awscdk.StackProps; + +public class PowertoolsExamplesCloudformationCdkApp { + public static void main(final String[] args) { + App app = new App(); + + new PowertoolsExamplesCloudformationCdkStack(app, "PowertoolsExamplesCloudformationCdkStack", + StackProps.builder() + .build()); + + app.synth(); + } +} + diff --git a/examples/powertools-examples-cloudformation/infra/cdk/src/main/java/com/myorg/PowertoolsExamplesCloudformationCdkStack.java b/examples/powertools-examples-cloudformation/infra/cdk/src/main/java/com/myorg/PowertoolsExamplesCloudformationCdkStack.java new file mode 100644 index 000000000..e880a3534 --- /dev/null +++ b/examples/powertools-examples-cloudformation/infra/cdk/src/main/java/com/myorg/PowertoolsExamplesCloudformationCdkStack.java @@ -0,0 +1,89 @@ +package com.myorg; + +import software.amazon.awscdk.Stack; +import software.amazon.awscdk.*; +import software.amazon.awscdk.services.iam.Effect; +import software.amazon.awscdk.services.iam.PolicyStatement; +import software.amazon.awscdk.services.iam.PolicyStatementProps; +import software.amazon.awscdk.services.lambda.Code; +import software.amazon.awscdk.services.lambda.Function; +import software.amazon.awscdk.services.lambda.FunctionProps; +import software.amazon.awscdk.services.lambda.Runtime; +import software.amazon.awscdk.services.s3.assets.AssetOptions; +import software.constructs.Construct; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +import static java.util.Collections.singletonList; +import static software.amazon.awscdk.BundlingOutput.NOT_ARCHIVED; + +public class PowertoolsExamplesCloudformationCdkStack extends Stack { + + public static final String SAMPLE_BUCKET_NAME = "sample-bucket-name-20230315-abc123"; + + public PowertoolsExamplesCloudformationCdkStack(final Construct scope, final String id) { + this(scope, id, null); + } + + public PowertoolsExamplesCloudformationCdkStack(final Construct scope, final String id, final StackProps props) { + super(scope, id, props); + + + List<String> functionPackagingInstructions = Arrays.asList( + "/bin/sh", + "-c", + "mvn clean install" + + "&& mkdir /asset-output/lib" + + "&& cp target/powertools-examples-cloudformation-*.jar /asset-output/lib" + ); + BundlingOptions bundlingOptions = BundlingOptions.builder() + .command(functionPackagingInstructions) + .image(Runtime.JAVA_11.getBundlingImage()) + .volumes(singletonList( + // Mount local .m2 repo to avoid download all the dependencies again inside the container + DockerVolume.builder() + .hostPath(System.getProperty("user.home") + "/.m2/") + .containerPath("/root/.m2/") + .build() + )) + .user("root") + .outputType(NOT_ARCHIVED) + .build(); + + Function helloWorldFunction = new Function(this, "HelloWorldFunction", FunctionProps.builder() + .runtime(Runtime.JAVA_11) + .code(Code.fromAsset("../../", AssetOptions.builder().bundling(bundlingOptions) + .build())) + .handler("helloworld.App::handleRequest") + .memorySize(512) + .timeout(Duration.seconds(20)) + .environment(Collections + .singletonMap("JAVA_TOOL_OPTIONS", "-XX:+TieredCompilation -XX:TieredStopAtLevel=1")) + .build()); + helloWorldFunction.addToRolePolicy(new PolicyStatement(PolicyStatementProps.builder() + .effect(Effect.ALLOW) + .actions(Arrays.asList("s3:GetLifecycleConfiguration", + "s3:PutLifecycleConfiguration", + "s3:CreateBucket", + "s3:ListBucket", + "s3:DeleteBucket")) + .resources(singletonList("*")).build())); + + String bucketName = (String) this.getNode().tryGetContext("BucketNameParam"); + + Map<String, Serializable> crProperties = new HashMap<>(); + crProperties.put("BucketName", bucketName); + CustomResource.Builder + .create(this, "HelloWorldCustomResource") + .serviceToken(helloWorldFunction.getFunctionArn()) + .properties(crProperties) + .build(); + + } +} diff --git a/examples/powertools-examples-cloudformation/infra/sam-graalvm/Dockerfile b/examples/powertools-examples-cloudformation/infra/sam-graalvm/Dockerfile new file mode 100644 index 000000000..dac9390e5 --- /dev/null +++ b/examples/powertools-examples-cloudformation/infra/sam-graalvm/Dockerfile @@ -0,0 +1,14 @@ +# Use the official AWS SAM base image for Java 21 +FROM public.ecr.aws/sam/build-java21@sha256:a5554d68374e19450c6c88448516ac95a9acedc779f318040f5c230134b4e461 + +# Install GraalVM dependencies +RUN curl -4 -L curl https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz | tar -xvz +RUN mv graalvm-jdk-21.* /usr/lib/graalvm + +# Make native image and mvn available on CLI +RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image +RUN ln -s /usr/lib/maven/bin/mvn /usr/bin/mvn + +# Set GraalVM as default +ENV JAVA_HOME=/usr/lib/graalvm +ENV PATH=/usr/lib/graalvm/bin:$PATH diff --git a/examples/powertools-examples-cloudformation/infra/sam-graalvm/README.md b/examples/powertools-examples-cloudformation/infra/sam-graalvm/README.md new file mode 100644 index 000000000..3aca1408a --- /dev/null +++ b/examples/powertools-examples-cloudformation/infra/sam-graalvm/README.md @@ -0,0 +1,52 @@ +# Powertools for AWS Lambda (Java) - CloudFormation Custom Resource Example with SAM on GraalVM + +This project contains an example of a Lambda function using the CloudFormation module of Powertools for AWS Lambda (Java). For more information on this module, please refer to the [documentation](https://docs.powertools.aws.dev/lambda-java/utilities/custom_resources/). + +In this example you pass in a bucket name as a parameter and upon CloudFormation events a call is made to a lambda. That lambda attempts to create the bucket on CREATE events, create a new bucket if the name changes with an UPDATE event and delete the bucket upon DELETE events. + +Have a look at [App.java](../../src/main/java/helloworld/App.java) for the full details. + +## Build the sample application + +> [!NOTE] +> Building AWS Lambda packages on macOS (ARM64/Intel) for deployment on AWS Lambda (Linux x86_64 or ARM64) will result in incompatible binary dependencies that cause import errors at runtime. + +Choose the appropriate build method based on your operating system: + +### Build locally using Docker + +Recommended for macOS and Windows users: Cross-compile using Docker to match target platform of Lambda: + +```shell +docker build --platform linux/amd64 . -t powertools-examples-cloudformation-sam-graalvm +docker run --platform linux/amd64 -it -v `pwd`/../..:`pwd`/../.. -w `pwd`/../.. -v ~/.m2:/root/.m2 powertools-examples-cloudformation-sam-graalvm mvn clean -Pnative-image package -DskipTests +sam build --use-container --build-image powertools-examples-cloudformation-sam-graalvm +``` + +**Note**: The Docker run command mounts your local Maven cache (`~/.m2`) and builds the native binary with SNAPSHOT support, then SAM packages the pre-built binary. + +### Build on native OS + +For Linux users with GraalVM installed: + +```shell +export JAVA_HOME=<path to GraalVM> +cd ../.. +mvn clean -Pnative-image package -DskipTests +cd infra/sam-graalvm +sam build +``` + +## Deploy the sample application + +```shell +sam deploy --guided --parameter-overrides BucketNameParam=my-unique-bucket-2.9.0718 +``` + +This sample is based on Serverless Application Model (SAM). To deploy it, check out the instructions for getting started with SAM in [the examples directory](../../../README.md) + +## Test the application + +The CloudFormation custom resource will be triggered automatically during stack deployment. You can monitor the Lambda function execution in CloudWatch Logs to see the custom resource handling CREATE, UPDATE, and DELETE events for the S3 bucket. + +Check out [App.java](../../src/main/java/helloworld/App.java) to see how it works! \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/infra/sam-graalvm/template.yaml b/examples/powertools-examples-cloudformation/infra/sam-graalvm/template.yaml new file mode 100644 index 000000000..4249aaed1 --- /dev/null +++ b/examples/powertools-examples-cloudformation/infra/sam-graalvm/template.yaml @@ -0,0 +1,49 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + powertools-examples-cloudformation-graalvm + + Sample SAM Template for powertools-examples-cloudformation with GraalVM native image + +Globals: + Function: + Timeout: 20 + +Parameters: + BucketNameParam: + Type: String + +Resources: + HelloWorldCustomResource: + Type: AWS::CloudFormation::CustomResource + Properties: + ServiceToken: !GetAtt HelloWorldFunction.Arn + BucketName: !Ref BucketNameParam + + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../../ + Handler: helloworld.App::handleRequest + Runtime: provided.al2023 + Architectures: + - x86_64 + MemorySize: 512 + Policies: + - Statement: + - Sid: bucketaccess1 + Effect: Allow + Action: + - s3:GetLifecycleConfiguration + - s3:PutLifecycleConfiguration + - s3:CreateBucket + - s3:ListBucket + - s3:DeleteBucket + Resource: '*' + Metadata: + BuildMethod: makefile + +Outputs: + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/infra/sam/events/create_event.json b/examples/powertools-examples-cloudformation/infra/sam/events/create_event.json new file mode 100644 index 000000000..26ef0a03b --- /dev/null +++ b/examples/powertools-examples-cloudformation/infra/sam/events/create_event.json @@ -0,0 +1,12 @@ +{ + "RequestType": "Create", + "ResponseURL": "http://pre-signed-S3-url-for-response", + "StackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/MyStack/guid", + "RequestId": "unique id for this create request", + "ResourceType": "Custom::TestResource", + "ResourceProperties": { + "BucketName": "test-bucket-20230307-1", + "RetentionDays" : 10, + "StackName": "MyStack" + } +} diff --git a/examples/powertools-examples-cloudformation/infra/sam/events/delete_event.json b/examples/powertools-examples-cloudformation/infra/sam/events/delete_event.json new file mode 100644 index 000000000..d18fdd3e4 --- /dev/null +++ b/examples/powertools-examples-cloudformation/infra/sam/events/delete_event.json @@ -0,0 +1,14 @@ +{ + "RequestType": "Delete", + "ResponseURL": "http://pre-signed-S3-url-for-response", + "StackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/MyStack/guid", + "RequestId": "unique id for this create request", + "ResourceType": "Custom::TestResource", + "LogicalResourceId": "MyTestResource", + "PhysicalResourceId": "test-bucket-20230307-1", + "ResourceProperties": { + "BucketName": "test-bucket-20230307-1", + "RetentionDays" : 10, + "StackName": "MyStack" + } +} diff --git a/examples/powertools-examples-cloudformation/infra/sam/events/update_event.json b/examples/powertools-examples-cloudformation/infra/sam/events/update_event.json new file mode 100644 index 000000000..5a5ae2e3f --- /dev/null +++ b/examples/powertools-examples-cloudformation/infra/sam/events/update_event.json @@ -0,0 +1,14 @@ +{ + "RequestType": "Update", + "ResponseURL": "http://pre-signed-S3-url-for-response", + "StackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/MyStack/guid", + "RequestId": "unique id for this create request", + "ResourceType": "Custom::TestResource", + "LogicalResourceId": "MyTestResource", + "PhysicalResourceId": "test-bucket-20230307-1", + "ResourceProperties": { + "BucketName": "test-bucket-20230307-1", + "RetentionDays" : 100, + "StackName": "MyStack" + } +} diff --git a/examples/powertools-examples-cloudformation/infra/sam/template.yaml b/examples/powertools-examples-cloudformation/infra/sam/template.yaml new file mode 100644 index 000000000..a7ce4adf1 --- /dev/null +++ b/examples/powertools-examples-cloudformation/infra/sam/template.yaml @@ -0,0 +1,50 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + powertools-examples-cloudformation + + Sample SAM Template for powertools-examples-cloudformation + +Globals: + Function: + Timeout: 20 + +Parameters: + BucketNameParam: + Type: String + +Resources: + HelloWorldCustomResource: + Type: AWS::CloudFormation::CustomResource + Properties: + ServiceToken: !GetAtt HelloWorldFunction.Arn + BucketName: !Ref BucketNameParam + + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../../ + Handler: helloworld.App::handleRequest + Runtime: java11 + Architectures: + - x86_64 + MemorySize: 512 + Policies: + - Statement: + - Sid: bucketaccess1 + Effect: Allow + Action: + - s3:GetLifecycleConfiguration + - s3:PutLifecycleConfiguration + - s3:CreateBucket + - s3:ListBucket + - s3:DeleteBucket + Resource: '*' + Environment: + Variables: + JAVA_TOOL_OPTIONS: -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + +Outputs: + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn diff --git a/examples/powertools-examples-cloudformation/pom.xml b/examples/powertools-examples-cloudformation/pom.xml new file mode 100644 index 000000000..212c0966b --- /dev/null +++ b/examples/powertools-examples-cloudformation/pom.xml @@ -0,0 +1,186 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-cloudformation</artifactId> + <packaging>jar</packaging> + + <name>Powertools for AWS Lambda (Java) - Examples - CloudFormation</name> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <lambda.core.version>1.4.0</lambda.core.version> + <lambda.events.version>3.16.1</lambda.events.version> + <aws.sdk.version>2.40.9</aws.sdk.version> + <aspectj.version>1.9.20.1</aspectj.version> + + </properties> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>bom</artifactId> + <version>${aws.sdk.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>${lambda.core.version}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <version>${lambda.events.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-cloudformation</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <exclusions> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>netty-nio-client</artifactId> + </exclusion> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>apache-client</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>apache-client</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + <version>2.8.7</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>3.6.1</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <transformers> + <transformer + implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer" /> + </transformers> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId> + <version>0.2.0</version> + </dependency> + </dependencies> + </plugin> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>build-native</id> + <goals> + <goal>build</goal> + </goals> + <phase>package</phase> + </execution> + </executions> + <configuration> + <imageName>hello-world</imageName> + <mainClass>com.amazonaws.services.lambda.runtime.api.client.AWSLambda</mainClass> + <buildArgs> + <arg>--enable-url-protocols=http</arg> + <arg>--add-opens java.base/java.util=ALL-UNNAMED</arg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/examples/powertools-examples-cloudformation/src/main/config/bootstrap b/examples/powertools-examples-cloudformation/src/main/config/bootstrap new file mode 100755 index 000000000..8e7928cd3 --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/config/bootstrap @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +./hello-world $_HANDLER \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/src/main/java/helloworld/App.java b/examples/powertools-examples-cloudformation/src/main/java/helloworld/App.java new file mode 100644 index 000000000..c35ad8fb9 --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/java/helloworld/App.java @@ -0,0 +1,172 @@ +package helloworld; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import java.util.Objects; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.waiters.WaiterResponse; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.CreateBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteBucketRequest; +import software.amazon.awssdk.services.s3.model.HeadBucketRequest; +import software.amazon.awssdk.services.s3.model.HeadBucketResponse; +import software.amazon.awssdk.services.s3.model.NoSuchBucketException; +import software.amazon.awssdk.services.s3.waiters.S3Waiter; +import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler; +import software.amazon.lambda.powertools.cloudformation.Response; + +/** + * Handler for requests to Lambda function. + */ + +public class App extends AbstractCustomResourceHandler { + private static final Logger log = LoggerFactory.getLogger(App.class); + private final S3Client s3Client; + + public App() { + super(); + s3Client = S3Client.builder().httpClientBuilder(ApacheHttpClient.builder()).build(); + } + + /** + * This method is invoked when CloudFormation Creates the Custom Resource. + * In this example, the method creates an Amazon S3 Bucket with the provided `BucketName` + * + * @param cloudFormationCustomResourceEvent Create Event from CloudFormation + * @param context Lambda Context + * @return Response to send to CloudFormation + */ + @Override + protected Response create(CloudFormationCustomResourceEvent cloudFormationCustomResourceEvent, Context context) { + // Validate the CloudFormation Custom Resource event + Objects.requireNonNull(cloudFormationCustomResourceEvent, "cloudFormationCustomResourceEvent cannot be null."); + Objects.requireNonNull(cloudFormationCustomResourceEvent.getResourceProperties().get("BucketName"), + "BucketName cannot be null."); + + log.info(cloudFormationCustomResourceEvent.toString()); + String bucketName = (String) cloudFormationCustomResourceEvent.getResourceProperties().get("BucketName"); + log.info("Bucket Name {}", bucketName); + try { + // Create the S3 bucket with the given bucketName + createBucket(bucketName); + // Return a successful response with the bucketName as the physicalResourceId + return Response.success(bucketName); + } catch (AwsServiceException | SdkClientException e) { + // In case of error, return a failed response, with the bucketName as the physicalResourceId + log.error("Unable to create bucket", e); + return Response.failed(bucketName); + } + } + + /** + * This method is invoked when CloudFormation Updates the Custom Resource. + * In this example, the method creates an Amazon S3 Bucket with the provided `BucketName`, if the `BucketName` differs from the previous `BucketName` (for initial creation) + * + * @param cloudFormationCustomResourceEvent Update Event from CloudFormation + * @param context Lambda Context + * @return Response to send to CloudFormation + */ + @Override + protected Response update(CloudFormationCustomResourceEvent cloudFormationCustomResourceEvent, Context context) { + // Validate the CloudFormation Custom Resource event + Objects.requireNonNull(cloudFormationCustomResourceEvent, "cloudFormationCustomResourceEvent cannot be null."); + Objects.requireNonNull(cloudFormationCustomResourceEvent.getResourceProperties().get("BucketName"), + "BucketName cannot be null."); + + log.info(cloudFormationCustomResourceEvent.toString()); + // Get the physicalResourceId. physicalResourceId is the value returned to CloudFormation in the Create request, and passed in on subsequent requests (e.g. UPDATE or DELETE) + String physicalResourceId = cloudFormationCustomResourceEvent.getPhysicalResourceId(); + log.info("Physical Resource ID {}", physicalResourceId); + + // Get the BucketName from the CloudFormation Event + String newBucketName = (String) cloudFormationCustomResourceEvent.getResourceProperties().get("BucketName"); + + // Check if the physicalResourceId equals the new BucketName + if (!physicalResourceId.equals(newBucketName)) { + // The bucket name has changed - create a new bucket + try { + // Create a new bucket with the newBucketName + createBucket(newBucketName); + // Return a successful response with the newBucketName + return Response.success(newBucketName); + } catch (AwsServiceException | SdkClientException e) { + log.error("Unable to create bucket", e); + return Response.failed(newBucketName); + } + } else { + // Bucket name has not changed, and no changes are needed. + // Return a successful response with the previous physicalResourceId + return Response.success(physicalResourceId); + } + } + + /** + * This method is invoked when CloudFormation Deletes the Custom Resource. + * NOTE: CloudFormation will DELETE a resource, if during the UPDATE a new physicalResourceId is returned. + * Refer to the <a href="https://docs.powertools.aws.dev/lambda/java/utilities/custom_resources/#understanding-the-cloudformation-custom-resource-lifecycle">Powertools Java Documentation</a> for more details. + * + * @param cloudFormationCustomResourceEvent Delete Event from CloudFormation + * @param context Lambda Context + * @return Response to send to CloudFormation + */ + @Override + protected Response delete(CloudFormationCustomResourceEvent cloudFormationCustomResourceEvent, Context context) { + // Validate the CloudFormation Custom Resource event + Objects.requireNonNull(cloudFormationCustomResourceEvent, "cloudFormationCustomResourceEvent cannot be null."); + Objects.requireNonNull(cloudFormationCustomResourceEvent.getPhysicalResourceId(), + "PhysicalResourceId cannot be null."); + + log.info(cloudFormationCustomResourceEvent.toString()); + // Get the physicalResourceId. physicalResourceId is the value provided to CloudFormation in the Create request. + String bucketName = cloudFormationCustomResourceEvent.getPhysicalResourceId(); + log.info("Bucket Name {}", bucketName); + + // Check if a bucket with bucketName exists + if (bucketExists(bucketName)) { + try { + // If it exists, delete the bucket + s3Client.deleteBucket(DeleteBucketRequest.builder().bucket(bucketName).build()); + log.info("Bucket Deleted {}", bucketName); + // Return a successful response with bucketName as the physicalResourceId + return Response.success(bucketName); + } catch (AwsServiceException | SdkClientException e) { + // Return a failed response in case of errors during the bucket deletion + log.error("Unable to delete bucket", e); + return Response.failed(bucketName); + } + } else { + // If the bucket does not exist, return a successful response with the bucketName as the physicalResourceId + log.info("Bucket already deleted - no action"); + return Response.success(bucketName); + } + + } + + private boolean bucketExists(String bucketName) { + try { + HeadBucketResponse headBucketResponse = + s3Client.headBucket(HeadBucketRequest.builder().bucket(bucketName).build()); + if (headBucketResponse.sdkHttpResponse().isSuccessful()) { + return true; + } + } catch (NoSuchBucketException e) { + log.info("Bucket does not exist"); + return false; + } + return false; + } + + private void createBucket(String bucketName) { + S3Waiter waiter = s3Client.waiter(); + CreateBucketRequest createBucketRequest = CreateBucketRequest.builder().bucket(bucketName).build(); + s3Client.createBucket(createBucketRequest); + WaiterResponse<HeadBucketResponse> waiterResponse = + waiter.waitUntilBucketExists(HeadBucketRequest.builder().bucket(bucketName).build()); + waiterResponse.matched().response().ifPresent(res -> log.info(res.toString())); + log.info("Bucket Created {}", bucketName); + } +} \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..e97baa294 --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,44 @@ +[ + { + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields":[{"name":"logger"}] + }, + { + "name":"java.lang.Void", + "methods":[{"name":"<init>","parameterTypes":[] }] + }, + { + "name":"java.util.Collections$UnmodifiableMap", + "fields":[{"name":"m"}] + }, + { + "name":"jdk.internal.module.IllegalAccessLogger", + "fields":[{"name":"logger"}] + }, + { + "name":"sun.misc.Unsafe", + "fields":[{"name":"theUnsafe"}] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/native-image.properties b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/native-image.properties new file mode 100644 index 000000000..db5ebaa55 --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/native-image.properties @@ -0,0 +1 @@ +Args = --enable-url-protocols=http,https \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/reflect-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/reflect-config.json new file mode 100644 index 000000000..06ea9ce2f --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/reflect-config.json @@ -0,0 +1,11 @@ +[ + { + "name": "helloworld.App", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/resource-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/software.amazon.awssdk/s3/reflect-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/software.amazon.awssdk/s3/reflect-config.json new file mode 100644 index 000000000..d685b7e20 --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/software.amazon.awssdk/s3/reflect-config.json @@ -0,0 +1,27 @@ +[ + { + "name": "software.amazon.awssdk.services.s3.model.CreateBucketRequest", + "allPublicMethods": true, + "allPublicConstructors": true + }, + { + "name": "software.amazon.awssdk.services.s3.model.DeleteBucketRequest", + "allPublicMethods": true, + "allPublicConstructors": true + }, + { + "name": "software.amazon.awssdk.services.s3.model.HeadBucketRequest", + "allPublicMethods": true, + "allPublicConstructors": true + }, + { + "name": "software.amazon.awssdk.services.s3.model.HeadBucketResponse", + "allPublicMethods": true, + "allPublicConstructors": true + }, + { + "name": "software.amazon.awssdk.services.s3.model.NoSuchBucketException", + "allPublicMethods": true, + "allPublicConstructors": true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-cloudformation/src/main/resources/log4j2.xml b/examples/powertools-examples-cloudformation/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8e8162128 --- /dev/null +++ b/examples/powertools-examples-cloudformation/src/main/resources/log4j2.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="Lambda"> + <PatternLayout> + <pattern>%d{dd MMM yyyy HH:mm:ss,SSS} [%p] <%X{AWSRequestId}> (%t) %c:%L: %m%n</pattern> + </PatternLayout> + </Console> + </Appenders> + <Loggers> + <Root level="info"> + <AppenderRef ref="Lambda"/> + </Root> + <Logger name="JsonLogger" level="INFO" additivity="false"/> + <Logger name="software.amazon.lambda.powertools" level="DEBUG"/> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/.gitignore b/examples/powertools-examples-core-utilities/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/examples/powertools-examples-core-utilities/README.md b/examples/powertools-examples-core-utilities/README.md new file mode 100644 index 000000000..c9ca60f57 --- /dev/null +++ b/examples/powertools-examples-core-utilities/README.md @@ -0,0 +1,55 @@ +# Powertools for AWS Lambda (Java) - Core Utilities Example + +This project demonstrates the Lambda for Powertools Java module - including +[logging](https://docs.powertools.aws.dev/lambda/java/core/logging/), +[tracing](https://docs.powertools.aws.dev/lambda/java/core/tracing/), and +[metrics](https://docs.powertools.aws.dev/lambda/java/core/metrics/). + +The example application is the same, and you can now also use Kotlin! + +## Java +* [AWS SAM](sam/) +* [AWS CDK](cdk/) +* [Serverless framework](serverless/) +* [Terraform](terraform/) + +We also provide an example showing the integration of SAM, Powertools, and Gradle: + +* [AWS SAM with a Gradle build](gradle/) + +- App.java - Code for the application's Lambda function. +- AppTests.java - Unit tests for the application code. +- events - Invocation events that you can use to invoke the function. + +Configuration files and deployment process for each tool are described in corresponding README files. + +## Kotlin + +- [Gradle](kotlin/) + +Example application consists of the following files: + +- App.kt - Code for the application's Lambda function. +- AppTests.kt - Unit tests for the application code. +- events - Invocation events that you can use to invoke the function. + +Configuration files and deployment process for each tool are described in corresponding README files. + +## Test the application + +Once the app is deployed, you can invoke the endpoint like this: + +```bash + curl https://[REST-API-ID].execute-api.[REGION].amazonaws.com/Prod/hello/ +``` + +The response itself isn't particularly interesting - you will get back some information about your IP address. If +you go to the Lambda Console and locate the lambda you have deployed, then click the "Monitoring" tab you will +be able to find: + +- **View X-Ray traces** - Display the traces captured by the traces module. These include subsegments for the + different function calls within the example +- **View Cloudwatch logs** - Display the structured logging output of the example + +Likewise, from the CloudWatch dashboard, under **Metrics**, **all metrics**, you will find the namespaces `Another` +and `ServerlessAirline`. The values in each of these are published by the code in the respective application's Lambda function. diff --git a/examples/powertools-examples-core-utilities/cdk/README.md b/examples/powertools-examples-core-utilities/cdk/README.md new file mode 100644 index 000000000..fbc558943 --- /dev/null +++ b/examples/powertools-examples-core-utilities/cdk/README.md @@ -0,0 +1,36 @@ +# Powertools for AWS Lambda (Java) - Core Utilities Example with CDK + +This project demonstrates the Lambda for Powertools Java module deployed using [Cloud Development Kit](https://aws.amazon.com/cdk/). + +For general information on the deployed example itself, you can refer to the parent [README](../README.md) + +## Configuration +CDK uses the following project structure: +- [app](app) - stores the source code of your application, which is similar between all examples +- [infra](infra) - stores the definition of your infrastructure + - [cdk.json](infra/cdk.json) - tells the CDK Toolkit how to execute your app + - [CdkApp](./infra/src/main/java/cdk/CdkApp.java) - bootstraps your stack, taking AWS `account` and `region` as input + - [CdkStack](./infra/src/main/java/cdk/CdkStack.java) - defines the Lambda function to be deployed as well as API Gateway for it. + +It is a [Maven](https://maven.apache.org/) based project, so you can open this project with any Maven compatible Java IDE to build and run tests. + + +## Deploy the sample application + +The minimum to deploy the app should be +```bash +cdk deploy +``` + +If you're running CDK for the first time, you'll need to first run the bootstrap command: +```bash +cdk bootstrap +``` + +## Useful commands + +* `mvn package` compile and run tests +* `cdk synth` emits the synthesized CloudFormation template +* `cdk deploy` deploy this stack to your default AWS account/region +* `cdk diff` compare deployed stack with current state +* `cdk docs` open CDK documentation \ No newline at end of file diff --git a/example/events/event.json b/examples/powertools-examples-core-utilities/cdk/app/events/event.json similarity index 100% rename from example/events/event.json rename to examples/powertools-examples-core-utilities/cdk/app/events/event.json diff --git a/example/HelloWorldFunction/pom.xml b/examples/powertools-examples-core-utilities/cdk/app/pom.xml similarity index 59% rename from example/HelloWorldFunction/pom.xml rename to examples/powertools-examples-core-utilities/cdk/app/pom.xml index 5c6000a5f..c02b73026 100644 --- a/example/HelloWorldFunction/pom.xml +++ b/examples/powertools-examples-core-utilities/cdk/app/pom.xml @@ -1,57 +1,47 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> - <groupId>helloworld</groupId> - <artifactId>HelloWorld</artifactId> - <version>1.0</version> + + <name>Powertools for AWS Lambda (Java) - Examples - Core Utilities (logging, tracing, metrics) with CDK</name> + <groupId>software.amazon.lambda.examples</groupId> + <!-- TODO TODO TODO this should build from SNAPSHOT, but it doesn't, because the snapshots + don't appear in the docker environment CDK builds it in in our CDK tests. How to procede? V2 blocker --> + <version>2.9.0</version> + <artifactId>powertools-examples-core-utilities-cdk</artifactId> <packaging>jar</packaging> - <name>A sample Hello World created for SAM CLI.</name> + <properties> + <log4j.version>2.25.3</log4j.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> - <log4j.version>2.17.1</log4j.version> + <aspectj.version>1.9.20.1</aspectj.version> </properties> <dependencies> <dependency> <groupId>software.amazon.lambda</groupId> <artifactId>powertools-tracing</artifactId> - <version>1.10.2</version> + <version>${project.version}</version> </dependency> <dependency> <groupId>software.amazon.lambda</groupId> <artifactId>powertools-logging</artifactId> - <version>1.10.2</version> + <version>${project.version}</version> </dependency> <dependency> <groupId>software.amazon.lambda</groupId> <artifactId>powertools-metrics</artifactId> - <version>1.10.2</version> - </dependency> - <dependency> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-parameters</artifactId> - <version>1.10.2</version> - </dependency> - <dependency> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-validation</artifactId> - <version>1.10.2</version> - </dependency> - <dependency> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-sqs</artifactId> - <version>1.10.2</version> + <version>${project.version}</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-core</artifactId> - <version>1.2.1</version> + <version>1.4.0</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-events</artifactId> - <version>3.1.0</version> + <version>3.16.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> @@ -64,25 +54,19 @@ <version>${log4j.version}</version> </dependency> <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-databind</artifactId> - <version>2.11.2</version> - </dependency> - - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>4.13.1</version> - <scope>test</scope> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> </dependency> </dependencies> <build> + <finalName>helloworld-lambda</finalName> <plugins> <plugin> - <groupId>org.codehaus.mojo</groupId> + <groupId>dev.aspectj</groupId> <artifactId>aspectj-maven-plugin</artifactId> - <version>1.14.0</version> + <version>1.14.1</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> @@ -100,14 +84,6 @@ <groupId>software.amazon.lambda</groupId> <artifactId>powertools-metrics</artifactId> </aspectLibrary> - <aspectLibrary> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-sqs</artifactId> - </aspectLibrary> - <aspectLibrary> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-validation</artifactId> - </aspectLibrary> </aspectLibraries> </configuration> <executions> @@ -117,21 +93,49 @@ </goals> </execution> </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> - <version>3.2.4</version> - <configuration> - </configuration> + <version>3.6.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <transformers> + <transformer + implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer"/> + </transformers> + </configuration> </execution> </executions> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId> + <version>0.2.0</version> + </dependency> + </dependencies> + </plugin> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> </plugin> </plugins> </build> diff --git a/example/HelloWorldFunction/src/main/java/helloworld/App.java b/examples/powertools-examples-core-utilities/cdk/app/src/main/java/helloworld/App.java similarity index 60% rename from example/HelloWorldFunction/src/main/java/helloworld/App.java rename to examples/powertools-examples-core-utilities/cdk/app/src/main/java/helloworld/App.java index a371e6c11..bb21f84d3 100644 --- a/example/HelloWorldFunction/src/main/java/helloworld/App.java +++ b/examples/powertools-examples-core-utilities/cdk/app/src/main/java/helloworld/App.java @@ -1,5 +1,25 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package helloworld; +import static software.amazon.lambda.powertools.tracing.TracingUtils.putMetadata; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -7,52 +27,43 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; -import com.amazonaws.xray.AWSXRay; -import com.amazonaws.xray.entities.Entity; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import software.amazon.cloudwatchlogs.emf.model.DimensionSet; -import software.amazon.cloudwatchlogs.emf.model.Unit; -import software.amazon.lambda.powertools.logging.LoggingUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; import software.amazon.lambda.powertools.tracing.CaptureMode; -import software.amazon.lambda.powertools.tracing.TracingUtils; import software.amazon.lambda.powertools.tracing.Tracing; - -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; -import static software.amazon.lambda.powertools.metrics.MetricsUtils.withSingleMetric; -import static software.amazon.lambda.powertools.tracing.TracingUtils.putMetadata; -import static software.amazon.lambda.powertools.tracing.TracingUtils.withEntitySubsegment; +import software.amazon.lambda.powertools.tracing.TracingUtils; /** * Handler for requests to Lambda function. */ public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { - private final static Logger log = LogManager.getLogger(); + private static final Logger log = LoggerFactory.getLogger(App.class); + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); @Logging(logEvent = true, samplingRate = 0.7) @Tracing(captureMode = CaptureMode.RESPONSE_AND_ERROR) - @Metrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/json"); headers.put("X-Custom-Header", "application/json"); - metricsLogger().putMetric("CustomMetric1", 1, Unit.COUNT); + metrics.addMetric("CustomMetric1", 1, MetricUnit.COUNT); - withSingleMetric("CustomMetrics2", 1, Unit.COUNT, "Another", (metric) -> { - metric.setDimensions(DimensionSet.of("AnotherService", "CustomService")); - metric.setDimensions(DimensionSet.of("AnotherService1", "CustomService1")); - }); + DimensionSet dimensionSet = DimensionSet.of( + "AnotherService", "CustomService", + "AnotherService1", "CustomService1"); + metrics.flushSingleMetric("CustomMetric2", 1, MetricUnit.COUNT, "Another", dimensionSet); - LoggingUtils.appendKey("test", "willBeLogged"); + MDC.put("test", "willBeLogged"); APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() .withHeaders(headers); @@ -68,37 +79,17 @@ public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEv log.info(output); }); - threadOption1(); - - threadOption2(); - log.info("After output"); return response .withStatusCode(200) .withBody(output); - } catch (IOException | InterruptedException e) { + } catch (IOException e) { return response .withBody("{}") .withStatusCode(500); } } - private void threadOption1() throws InterruptedException { - final Entity traceEntity = AWSXRay.getTraceEntity(); - assert traceEntity != null; - traceEntity.run(new Thread(this::log)); - } - - private void threadOption2() throws InterruptedException { - Entity traceEntity = AWSXRay.getTraceEntity(); - Thread anotherThread = new Thread(() -> withEntitySubsegment("inlineLog", traceEntity, subsegment -> { - String var = "somethingToProcess"; - log.info("inside threaded logging inline {}", var); - })); - anotherThread.start(); - anotherThread.join(); - } - @Tracing private void log() { log.info("inside threaded logging for function"); diff --git a/examples/powertools-examples-core-utilities/cdk/app/src/main/java/helloworld/AppStream.java b/examples/powertools-examples-core-utilities/cdk/app/src/main/java/helloworld/AppStream.java new file mode 100644 index 000000000..8bc57b201 --- /dev/null +++ b/examples/powertools-examples-core-utilities/cdk/app/src/main/java/helloworld/AppStream.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; + +import java.io.InputStreamReader; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +public class AppStream implements RequestStreamHandler { + private static final ObjectMapper mapper = new ObjectMapper(); + private final static Logger log = LogManager.getLogger(AppStream.class); + + @Override + @Logging(logEvent = true) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + // RequestStreamHandler can be used instead of RequestHandler for cases when you'd like to deserialize request body or serialize response body yourself, instead of allowing that to happen automatically + // Note that you still need to return a proper JSON for API Gateway to handle + // See Lambda Response format for examples: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + public void handleRequest(InputStream input, OutputStream output, Context context) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); + PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)))) { + + log.info("Received: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mapper.readTree(reader))); + + writer.write("{\"body\": \"" + System.currentTimeMillis() + "\"} "); + } catch (IOException e) { + log.error("Something has gone wrong: ", e); + } + } +} + diff --git a/powertools-logging/src/test/resources/log4j2.xml b/examples/powertools-examples-core-utilities/cdk/app/src/main/resources/log4j2.xml similarity index 84% rename from powertools-logging/src/test/resources/log4j2.xml rename to examples/powertools-examples-core-utilities/cdk/app/src/main/resources/log4j2.xml index 22a44ee8b..e1fd14cea 100644 --- a/powertools-logging/src/test/resources/log4j2.xml +++ b/examples/powertools-examples-core-utilities/cdk/app/src/main/resources/log4j2.xml @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <Configuration> <Appenders> - <File name="JsonAppender" fileName="target/logfile.json"> + <Console name="JsonAppender" target="SYSTEM_OUT"> <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> - </File> + </Console> </Appenders> <Loggers> <Logger name="JsonLogger" level="INFO" additivity="false"> diff --git a/examples/powertools-examples-core-utilities/cdk/infra/cdk.json b/examples/powertools-examples-core-utilities/cdk/infra/cdk.json new file mode 100644 index 000000000..e24ee7b04 --- /dev/null +++ b/examples/powertools-examples-core-utilities/cdk/infra/cdk.json @@ -0,0 +1,36 @@ +{ + "app": "mvn -e -q compile exec:java", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "target", + "pom.xml", + "src/test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true + } +} diff --git a/examples/powertools-examples-core-utilities/cdk/infra/pom.xml b/examples/powertools-examples-core-utilities/cdk/infra/pom.xml new file mode 100644 index 000000000..e3ceb7e65 --- /dev/null +++ b/examples/powertools-examples-core-utilities/cdk/infra/pom.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + <groupId>software.amazon.lambda.examples</groupId> + <artifactId>cdk</artifactId> + <version>2.9.0</version> + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <cdk.version>2.224.0</cdk.version> + <constructs.version>[10.0.0,11.0.0)</constructs.version> + <junit.version>5.14.1</junit.version> + </properties> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.14.1</version> + <configuration> + <source>11</source> + <target>11</target> + </configuration> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>3.6.2</version> + <configuration> + <mainClass>cdk.CdkApp</mainClass> + </configuration> + </plugin> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + <dependencies> + <!-- AWS Cloud Development Kit --> + <dependency> + <groupId>software.amazon.awscdk</groupId> + <artifactId>aws-cdk-lib</artifactId> + <version>${cdk.version}</version> + </dependency> + <dependency> + <groupId>software.constructs</groupId> + <artifactId>constructs</artifactId> + <version>${constructs.version}</version> + </dependency> + + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/examples/powertools-examples-core-utilities/cdk/infra/src/main/java/cdk/CdkApp.java b/examples/powertools-examples-core-utilities/cdk/infra/src/main/java/cdk/CdkApp.java new file mode 100644 index 000000000..d564eb9a0 --- /dev/null +++ b/examples/powertools-examples-core-utilities/cdk/infra/src/main/java/cdk/CdkApp.java @@ -0,0 +1,53 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package cdk; + +import software.amazon.awscdk.App; +import software.amazon.awscdk.StackProps; + +public class CdkApp { + public static void main(final String[] args) { + App app = new App(); + + new CdkStack(app, "CdkStack", StackProps.builder() + // If you don't specify 'env', this stack will be environment-agnostic. + // Account/Region-dependent features and context lookups will not work, + // but a single synthesized template can be deployed anywhere. + + // Uncomment the next block to specialize this stack for the AWS Account + // and Region that are implied by the current CLI configuration. + /* + .env(Environment.builder() + .account(System.getenv("CDK_DEFAULT_ACCOUNT")) + .region(System.getenv("CDK_DEFAULT_REGION")) + .build()) + */ + + + // Uncomment the next block if you know exactly what Account and Region you + // want to deploy the stack to. + /* + .env(Environment.builder() + .account("1234567890") + .region("region") + .build()) + */ + + // For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html + .build()); + + app.synth(); + } +} diff --git a/examples/powertools-examples-core-utilities/cdk/infra/src/main/java/cdk/CdkStack.java b/examples/powertools-examples-core-utilities/cdk/infra/src/main/java/cdk/CdkStack.java new file mode 100644 index 000000000..8e6b44112 --- /dev/null +++ b/examples/powertools-examples-core-utilities/cdk/infra/src/main/java/cdk/CdkStack.java @@ -0,0 +1,144 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package cdk; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awscdk.BundlingOptions; +import software.amazon.awscdk.CfnOutput; +import software.amazon.awscdk.Duration; +import software.amazon.awscdk.Stack; +import software.amazon.awscdk.StackProps; +import software.amazon.awscdk.services.apigateway.LambdaIntegration; +import software.amazon.awscdk.services.apigateway.RestApi; +import software.amazon.awscdk.services.lambda.Code; +import software.amazon.awscdk.services.lambda.Function; +import software.amazon.awscdk.services.lambda.Runtime; +import software.amazon.awscdk.services.lambda.Tracing; +import software.amazon.awscdk.services.s3.assets.AssetOptions; +import software.constructs.Construct; + +/** + * Defines a stack that consists of a single Java Lambda function and an API Gateway + */ +public class CdkStack extends Stack { + private static final String SHELL_COMMAND = "/bin/sh"; + private static final String MAVEN_PACKAGE = "mvn package"; + private static final String COPY_OUTPUT = "cp /asset-input/target/helloworld-lambda.jar /asset-output/"; + + public CdkStack(final Construct scope, final String id) { + this(scope, id, null); + } + + public CdkStack(final Construct scope, final String id, final StackProps props) { + super(scope, id, props); + + Function helloWorldFunction = createHelloWorldFunction(); + Function helloWorldStreamFunction = createHelloWorldStreamFunction(); + RestApi restApi = createHelloWorldApi(); + + restApi.getRoot().resourceForPath("/hello") + .addMethod("GET", LambdaIntegration.Builder.create(helloWorldFunction) + .build()); + + restApi.getRoot().resourceForPath("/hellostream") + .addMethod("GET", LambdaIntegration.Builder.create(helloWorldStreamFunction) + .build()); + + outputApiUrl(restApi); + } + + private static List<String> createFunctionPackageInstructions() { + // CDK will use this command to package your Java Lambda + return Arrays.asList( + SHELL_COMMAND, + "-c", + MAVEN_PACKAGE + " && " + + COPY_OUTPUT + ); + } + + /** + * Adds API URL to the outputs + * + * @param restApi + */ + private void outputApiUrl(RestApi restApi) { + CfnOutput.Builder.create(this, "HelloWorldApiUrl") + .description("API Gateway endpoint URL for Prod stage for Hello World function") + .value(restApi.getUrl() + "hello").build(); + } + + // Method to create the Lambda function + private Function createHelloWorldFunction() { + List<String> functionPackageInstructions = createFunctionPackageInstructions(); + + Map<String, String> environment = new HashMap<>(); + environment.put("POWERTOOLS_LOG_LEVEL", "INFO"); + environment.put("POWERTOOLS_LOGGER_SAMPLE_RATE", "0.1"); + environment.put("POWERTOOLS_LOGGER_LOG_EVENT", "true"); + environment.put("POWERTOOLS_METRICS_NAMESPACE", "Coreutilities"); + + return Function.Builder.create(this, "HelloWorldFunction") + .runtime(Runtime.JAVA_11) + .memorySize(512) + .timeout(Duration.seconds(20)) + .tracing(Tracing.ACTIVE) + .code(Code.fromAsset("../app/", AssetOptions.builder() + .bundling(BundlingOptions.builder() + .image(Runtime.JAVA_11.getBundlingImage()) + .command(functionPackageInstructions) + .build()) + .build())) + .handler("helloworld.App") + .environment(environment) + .build(); + } + + private Function createHelloWorldStreamFunction() { + List<String> functionPackageInstructions = createFunctionPackageInstructions(); + + Map<String, String> environment = new HashMap<>(); + environment.put("POWERTOOLS_LOG_LEVEL", "INFO"); + environment.put("POWERTOOLS_LOGGER_SAMPLE_RATE", "0.7"); + environment.put("POWERTOOLS_LOGGER_LOG_EVENT", "true"); + environment.put("POWERTOOLS_METRICS_NAMESPACE", "Coreutilities"); + environment.put("POWERTOOLS_SERVICE_NAME", "hello"); + + return Function.Builder.create(this, "HelloWorldStreamFunction") + .runtime(Runtime.JAVA_11) + .memorySize(512) + .timeout(Duration.seconds(20)) + .tracing(Tracing.ACTIVE) + .code(Code.fromAsset("../app/", AssetOptions.builder() + .bundling(BundlingOptions.builder() + .image(Runtime.JAVA_11.getBundlingImage()) + .command(functionPackageInstructions) + .build()) + .build())) + .handler("helloworld.AppStream") + .environment(environment) + .build(); + } + + // Method to create the REST API + private RestApi createHelloWorldApi() { + return RestApi.Builder.create(this, "HelloWorldApi") + .description("API Gateway endpoint URL for Prod stage for Hello World function") + .build(); + } +} diff --git a/examples/powertools-examples-core-utilities/cdk/infra/src/test/java/cdk/CdkStackTest.java b/examples/powertools-examples-core-utilities/cdk/infra/src/test/java/cdk/CdkStackTest.java new file mode 100644 index 000000000..e69de29bb diff --git a/examples/powertools-examples-core-utilities/gradle/README.md b/examples/powertools-examples-core-utilities/gradle/README.md new file mode 100644 index 000000000..adc7fe635 --- /dev/null +++ b/examples/powertools-examples-core-utilities/gradle/README.md @@ -0,0 +1,38 @@ +# Powertools for AWS Lambda (Java) - Core Utilities Example with Gradle + +This project demonstrates the Lambda for Powertools Java module deployed using [Serverless Application Model](https://aws.amazon.com/serverless/sam/) with +[Gradle](https://gradle.org/) running the build. This example is configured for Java 11 only; in order to use a newer version, check out the Gradle +configuration guide [in the main project README](../../../README.md). + +You can also use `sam init` to create a new Gradle-powered Powertools application - choose to use the **AWS Quick Start Templates**, +and then **Hello World Example with Powertools for AWS Lambda**, **Java 17** runtime, and finally **gradle**. + + +For general information on the deployed example itself, you can refer to the parent [README](../README.md) + +## Configuration +SAM uses [template.yaml](template.yaml) to define the application's AWS resources. +This file defines the Lambda function to be deployed as well as API Gateway for it. + +The build of the project is managed by Gradle, and configured in [build.gradle](build.gradle). + +## Deploy the sample application +To get started, you can use the Gradle wrapper to bootstrap Gradle and run the build: + +```bash +./gradlew build +``` + +Once this is done to deploy the example, check out the instructions for getting started with SAM in +[the examples directory](../../README.md) + +## Additional notes + +You can watch the trace information or log information using the SAM CLI: +```bash +# Tail the logs +sam logs --tail $MY_STACK + +# Tail the traces +sam traces --tail +``` \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/gradle/build.gradle b/examples/powertools-examples-core-utilities/gradle/build.gradle new file mode 100644 index 000000000..b01fdcfaa --- /dev/null +++ b/examples/powertools-examples-core-utilities/gradle/build.gradle @@ -0,0 +1,36 @@ + +plugins { + id 'java' + id "io.freefair.aspectj.post-compile-weaving" version "8.2.2" +} + +wrapper { + gradleVersion = "8.5" +} + +compileJava { + sourceCompatibility = "11" + targetCompatibility = "11" + + ajc { + enabled = true + } +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation 'com.amazonaws:aws-lambda-java-core:1.2.2' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.13.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' + implementation 'com.amazonaws:aws-lambda-java-events:3.16.0' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2' + implementation 'org.aspectj:aspectjrt:1.9.20.1' + aspect 'software.amazon.lambda:powertools-tracing:2.9.0' + aspect 'software.amazon.lambda:powertools-logging-log4j:2.9.0' + aspect 'software.amazon.lambda:powertools-metrics:2.9.0' +} + diff --git a/examples/powertools-examples-core-utilities/gradle/events/event.json b/examples/powertools-examples-core-utilities/gradle/events/event.json new file mode 100644 index 000000000..070ad8e01 --- /dev/null +++ b/examples/powertools-examples-core-utilities/gradle/events/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/examples/powertools-examples-core-utilities/gradle/gradle/wrapper/.gitignore b/examples/powertools-examples-core-utilities/gradle/gradle/wrapper/.gitignore new file mode 100644 index 000000000..59c09e205 --- /dev/null +++ b/examples/powertools-examples-core-utilities/gradle/gradle/wrapper/.gitignore @@ -0,0 +1,2 @@ +!gradle-wrapper.jar + diff --git a/examples/powertools-examples-core-utilities/gradle/gradle/wrapper/gradle-wrapper.jar b/examples/powertools-examples-core-utilities/gradle/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..033e24c4c Binary files /dev/null and b/examples/powertools-examples-core-utilities/gradle/gradle/wrapper/gradle-wrapper.jar differ diff --git a/example/HelloWorldFunction/gradle/wrapper/gradle-wrapper.properties b/examples/powertools-examples-core-utilities/gradle/gradle/wrapper/gradle-wrapper.properties similarity index 74% rename from example/HelloWorldFunction/gradle/wrapper/gradle-wrapper.properties rename to examples/powertools-examples-core-utilities/gradle/gradle/wrapper/gradle-wrapper.properties index 2a563242c..1af9e0930 100644 --- a/example/HelloWorldFunction/gradle/wrapper/gradle-wrapper.properties +++ b/examples/powertools-examples-core-utilities/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/examples/powertools-examples-core-utilities/gradle/gradlew b/examples/powertools-examples-core-utilities/gradle/gradlew new file mode 100755 index 000000000..fcb6fca14 --- /dev/null +++ b/examples/powertools-examples-core-utilities/gradle/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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 POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/example/HelloWorldFunction/gradlew.bat b/examples/powertools-examples-core-utilities/gradle/gradlew.bat similarity index 81% rename from example/HelloWorldFunction/gradlew.bat rename to examples/powertools-examples-core-utilities/gradle/gradlew.bat index 9b30e1557..93e3f59f1 100644 --- a/example/HelloWorldFunction/gradlew.bat +++ b/examples/powertools-examples-core-utilities/gradle/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +65,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,19 +72,21 @@ 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 %CMD_LINE_ARGS% +"%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 +if %ERRORLEVEL% equ 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 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal -:omega \ No newline at end of file +:omega diff --git a/examples/powertools-examples-core-utilities/gradle/src/main/java/helloworld/App.java b/examples/powertools-examples-core-utilities/gradle/src/main/java/helloworld/App.java new file mode 100644 index 000000000..39ed6f8d8 --- /dev/null +++ b/examples/powertools-examples-core-utilities/gradle/src/main/java/helloworld/App.java @@ -0,0 +1,109 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.entry; +import static software.amazon.lambda.powertools.tracing.TracingUtils.putMetadata; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import software.amazon.lambda.powertools.tracing.CaptureMode; +import software.amazon.lambda.powertools.tracing.Tracing; +import software.amazon.lambda.powertools.tracing.TracingUtils; + +/** + * Handler for requests to Lambda function. + */ +public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger log = LoggerFactory.getLogger(App.class); + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Logging(logEvent = true, samplingRate = 0.7) + @Tracing(captureMode = CaptureMode.RESPONSE_AND_ERROR) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + Map<String, String> headers = new HashMap<>(); + + headers.put("Content-Type", "application/json"); + headers.put("X-Custom-Header", "application/json"); + + metrics.addMetric("CustomMetric1", 1, MetricUnit.COUNT); + + DimensionSet dimensionSet = DimensionSet.of( + "AnotherService", "CustomService", + "AnotherService1", "CustomService1" + ); + metrics.flushSingleMetric("CustomMetric2", 1, MetricUnit.COUNT, "Another", dimensionSet); + + MDC.put("test", "willBeLogged"); + + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() + .withHeaders(headers); + try { + final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); + log.info("", entry("ip", pageContents)); + TracingUtils.putAnnotation("Test", "New"); + String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); + + TracingUtils.withSubsegment("loggingResponse", subsegment -> + { + String sampled = "log something out"; + log.info(sampled); + log.info(output); + }); + + log.info("After output"); + return response + .withStatusCode(200) + .withBody(output); + } catch (RuntimeException | IOException e) { + return response + .withBody("{}") + .withStatusCode(500); + } + } + + @Tracing + private void log() { + log.info("inside threaded logging for function"); + } + + @Tracing(namespace = "getPageContents", captureMode = CaptureMode.DISABLED) + private String getPageContents(String address) throws IOException { + URL url = new URL(address); + putMetadata("getPageContents", address); + try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } + } +} diff --git a/examples/powertools-examples-core-utilities/gradle/src/main/java/helloworld/AppStream.java b/examples/powertools-examples-core-utilities/gradle/src/main/java/helloworld/AppStream.java new file mode 100644 index 000000000..8bc57b201 --- /dev/null +++ b/examples/powertools-examples-core-utilities/gradle/src/main/java/helloworld/AppStream.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; + +import java.io.InputStreamReader; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +public class AppStream implements RequestStreamHandler { + private static final ObjectMapper mapper = new ObjectMapper(); + private final static Logger log = LogManager.getLogger(AppStream.class); + + @Override + @Logging(logEvent = true) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + // RequestStreamHandler can be used instead of RequestHandler for cases when you'd like to deserialize request body or serialize response body yourself, instead of allowing that to happen automatically + // Note that you still need to return a proper JSON for API Gateway to handle + // See Lambda Response format for examples: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + public void handleRequest(InputStream input, OutputStream output, Context context) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); + PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)))) { + + log.info("Received: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mapper.readTree(reader))); + + writer.write("{\"body\": \"" + System.currentTimeMillis() + "\"} "); + } catch (IOException e) { + log.error("Something has gone wrong: ", e); + } + } +} + diff --git a/examples/powertools-examples-core-utilities/gradle/template.yaml b/examples/powertools-examples-core-utilities/gradle/template.yaml new file mode 100644 index 000000000..1a1572fca --- /dev/null +++ b/examples/powertools-examples-core-utilities/gradle/template.yaml @@ -0,0 +1,71 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + gradle + + Sample SAM Template for gradle + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 20 + Runtime: java11 + MemorySize: 512 + Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html + Environment: + Variables: + # Powertools for AWS Lambda (Java) env vars: https://docs.powertools.aws.dev/lambda/java/#environment-variables + POWERTOOLS_LOG_LEVEL: INFO + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_LOGGER_LOG_EVENT: true + POWERTOOLS_METRICS_NAMESPACE: Coreutilities + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: . + Handler: helloworld.App::handleRequest + Runtime: java11 + MemorySize: 512 + Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + Variables: + POWERTOOLS_SERVICE_NAME: hello + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + + HelloWorldStreamFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: helloworld.AppStream::handleRequest + Runtime: java11 + MemorySize: 512 + Tracing: Active + Environment: + Variables: + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.7 + Events: + HelloWorld: + Type: Api + Properties: + Path: /hellostream + Method: get + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/examples/powertools-examples-core-utilities/kotlin/.gitignore b/examples/powertools-examples-core-utilities/kotlin/.gitignore new file mode 100644 index 000000000..e660fd93d --- /dev/null +++ b/examples/powertools-examples-core-utilities/kotlin/.gitignore @@ -0,0 +1 @@ +bin/ diff --git a/examples/powertools-examples-core-utilities/kotlin/README.md b/examples/powertools-examples-core-utilities/kotlin/README.md new file mode 100644 index 000000000..422e12742 --- /dev/null +++ b/examples/powertools-examples-core-utilities/kotlin/README.md @@ -0,0 +1,38 @@ +# Powertools for AWS Lambda (Kotlin) - Core Utilities Example + +This project demonstrates the Lambda for Powertools Kotlin module deployed using [Serverless Application Model](https://aws.amazon.com/serverless/sam/) with +[Gradle](https://gradle.org/) running the build. This example is configured for Java 11 only; in order to use a newer version, check out the Gradle +configuration guide [in the main project README](../../../README.md). + +You can also use `sam init` to create a new Gradle-powered Powertools application - choose to use the **AWS Quick Start Templates**, +and then **Hello World Example with Powertools for AWS Lambda**, **Java 17** runtime, and finally **gradle**. + +For general information on the deployed example itself, you can refer to the parent [README](../README.md) + +## Configuration +SAM uses [template.yaml](template.yaml) to define the application's AWS resources. +This file defines the Lambda function to be deployed as well as API Gateway for it. + +The build of the project is managed by Gradle, and configured in [build.gradle.kts](build.gradle.kts) +. + +## Deploy the sample application +To get started, you can use the included template with SAM to run the build and deploy to your AWS environment: + +```bash +sam build && sam deploy --guided +``` + +Once this is done to deploy the example, check out the instructions for getting started with SAM in +[the examples directory](../../README.md) + +## Additional notes + +You can watch the trace information or log information using the SAM CLI: +```bash +# Tail the logs +sam logs --tail $MY_STACK + +# Tail the traces +sam traces --tail +``` \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts new file mode 100644 index 000000000..3dae5015e --- /dev/null +++ b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + id("io.freefair.aspectj.post-compile-weaving") version "8.2.2" + kotlin("jvm") version "1.9.10" +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation("com.amazonaws:aws-lambda-java-core:1.2.3") + implementation("com.fasterxml.jackson.core:jackson-annotations:2.15.1") + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") + implementation("com.amazonaws:aws-lambda-java-events:3.16.0") + implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2") + implementation("org.aspectj:aspectjrt:1.9.20.1") + aspect("software.amazon.lambda:powertools-tracing:2.9.0") + aspect("software.amazon.lambda:powertools-logging-log4j:2.9.0") + aspect("software.amazon.lambda:powertools-metrics:2.9.0") + implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.24") +} + +kotlin { + jvmToolchain(11) +} diff --git a/examples/powertools-examples-core-utilities/kotlin/events/event.json b/examples/powertools-examples-core-utilities/kotlin/events/event.json new file mode 100644 index 000000000..070ad8e01 --- /dev/null +++ b/examples/powertools-examples-core-utilities/kotlin/events/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/examples/powertools-examples-core-utilities/kotlin/gradle/wrapper/.gitignore b/examples/powertools-examples-core-utilities/kotlin/gradle/wrapper/.gitignore new file mode 100644 index 000000000..59c09e205 --- /dev/null +++ b/examples/powertools-examples-core-utilities/kotlin/gradle/wrapper/.gitignore @@ -0,0 +1,2 @@ +!gradle-wrapper.jar + diff --git a/examples/powertools-examples-core-utilities/kotlin/gradle/wrapper/gradle-wrapper.jar b/examples/powertools-examples-core-utilities/kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..033e24c4c Binary files /dev/null and b/examples/powertools-examples-core-utilities/kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/powertools-examples-core-utilities/kotlin/gradle/wrapper/gradle-wrapper.properties b/examples/powertools-examples-core-utilities/kotlin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..1af9e0930 --- /dev/null +++ b/examples/powertools-examples-core-utilities/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/powertools-examples-core-utilities/kotlin/gradlew b/examples/powertools-examples-core-utilities/kotlin/gradlew new file mode 100755 index 000000000..fcb6fca14 --- /dev/null +++ b/examples/powertools-examples-core-utilities/kotlin/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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 POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/powertools-examples-core-utilities/kotlin/gradlew.bat b/examples/powertools-examples-core-utilities/kotlin/gradlew.bat new file mode 100644 index 000000000..93e3f59f1 --- /dev/null +++ b/examples/powertools-examples-core-utilities/kotlin/gradlew.bat @@ -0,0 +1,92 @@ +@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=. +@rem This is normally unused +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% equ 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% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/App.kt b/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/App.kt new file mode 100644 index 000000000..b05e0d055 --- /dev/null +++ b/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/App.kt @@ -0,0 +1,99 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package helloworld + +import com.amazonaws.services.lambda.runtime.Context +import com.amazonaws.services.lambda.runtime.RequestHandler +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent +import com.amazonaws.xray.entities.Subsegment +import org.slf4j.LoggerFactory +import org.slf4j.MDC +import software.amazon.lambda.powertools.logging.Logging +import software.amazon.lambda.powertools.logging.argument.StructuredArguments.entry +import software.amazon.lambda.powertools.metrics.Metrics +import software.amazon.lambda.powertools.metrics.Metrics +import software.amazon.lambda.powertools.metrics.MetricsFactory +import software.amazon.lambda.powertools.metrics.model.DimensionSet +import software.amazon.lambda.powertools.metrics.model.MetricUnit +import software.amazon.lambda.powertools.tracing.CaptureMode +import software.amazon.lambda.powertools.tracing.Tracing +import software.amazon.lambda.powertools.tracing.TracingUtils +import java.io.IOException +import java.io.InputStreamReader +import java.net.URL + +/** + * Handler for requests to Lambda function. + */ + +class App : RequestHandler<APIGatewayProxyRequestEvent?, APIGatewayProxyResponseEvent> { + private val log = LoggerFactory.getLogger(this::class.java) + private val metrics: Metrics = MetricsFactory.getMetricsInstance() + + @Logging(logEvent = true, samplingRate = 0.7) + @Tracing(captureMode = CaptureMode.RESPONSE_AND_ERROR) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + override fun handleRequest(input: APIGatewayProxyRequestEvent?, context: Context?): APIGatewayProxyResponseEvent { + val headers = mapOf("Content-Type" to "application/json", "X-Custom-Header" to "application/json") + + metrics.addMetric("CustomMetric1", 1.0, MetricUnit.COUNT) + + val dimensionSet = DimensionSet.of( + "AnotherService", "CustomService", + "AnotherService1", "CustomService1" + ) + metrics.flushSingleMetric("CustomMetric2", 1.0, MetricUnit.COUNT, "Another", dimensionSet) + + MDC.put("test", "willBeLogged") + val response = APIGatewayProxyResponseEvent().withHeaders(headers) + return try { + val pageContents = getPageContents("https://checkip.amazonaws.com") + log.info("", entry("ip", pageContents)); + TracingUtils.putAnnotation("Test", "New") + val output = """ + { + "message": "hello world", + "location": "$pageContents" + } + """.trimIndent() + TracingUtils.withSubsegment("loggingResponse") { _: Subsegment? -> + val sampled = "log something out" + log.info(sampled) + log.info(output) + } + log.info("After output") + response.withStatusCode(200).withBody(output) + } catch (e: RuntimeException) { + response.withBody("{}").withStatusCode(500) + } catch (e: IOException) { + response.withBody("{}").withStatusCode(500) + } + } + + @Tracing + private fun log() { + log.info("inside threaded logging for function") + } + + @Tracing(namespace = "getPageContents", captureMode = CaptureMode.DISABLED) + @Throws(IOException::class) + private fun getPageContents(address: String): String { + val url = URL(address) + TracingUtils.putMetadata("getPageContents", address) + return InputStreamReader(url.openStream()).use { reader -> + reader.readText().trim() + } + } +} diff --git a/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/AppStream.kt b/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/AppStream.kt new file mode 100644 index 000000000..88f578e3f --- /dev/null +++ b/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/AppStream.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package helloworld + +import com.amazonaws.services.lambda.runtime.Context +import com.amazonaws.services.lambda.runtime.RequestStreamHandler +import com.fasterxml.jackson.databind.ObjectMapper +import software.amazon.lambda.powertools.logging.Logging +import software.amazon.lambda.powertools.metrics.Metrics +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +class AppStream : RequestStreamHandler { + @Logging(logEvent = true) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + @Throws(IOException::class) + override fun handleRequest(input: InputStream, output: OutputStream, context: Context) { + val map: Map<*, *> = mapper.readValue(input, MutableMap::class.java) + println(map.size) + } + + companion object { + private val mapper = ObjectMapper() + } +} diff --git a/examples/powertools-examples-core-utilities/kotlin/template.yaml b/examples/powertools-examples-core-utilities/kotlin/template.yaml new file mode 100644 index 000000000..1a1572fca --- /dev/null +++ b/examples/powertools-examples-core-utilities/kotlin/template.yaml @@ -0,0 +1,71 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + gradle + + Sample SAM Template for gradle + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 20 + Runtime: java11 + MemorySize: 512 + Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html + Environment: + Variables: + # Powertools for AWS Lambda (Java) env vars: https://docs.powertools.aws.dev/lambda/java/#environment-variables + POWERTOOLS_LOG_LEVEL: INFO + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_LOGGER_LOG_EVENT: true + POWERTOOLS_METRICS_NAMESPACE: Coreutilities + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: . + Handler: helloworld.App::handleRequest + Runtime: java11 + MemorySize: 512 + Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + Variables: + POWERTOOLS_SERVICE_NAME: hello + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + + HelloWorldStreamFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: helloworld.AppStream::handleRequest + Runtime: java11 + MemorySize: 512 + Tracing: Active + Environment: + Variables: + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.7 + Events: + HelloWorld: + Type: Api + Properties: + Path: /hellostream + Method: get + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/examples/powertools-examples-core-utilities/sam-bazel/.bazelrc b/examples/powertools-examples-core-utilities/sam-bazel/.bazelrc new file mode 100644 index 000000000..6cdf014a1 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-bazel/.bazelrc @@ -0,0 +1,3 @@ +# Java compilation settings +build --java_language_version=11 +build --java_runtime_version=11 diff --git a/examples/powertools-examples-core-utilities/sam-bazel/.gitignore b/examples/powertools-examples-core-utilities/sam-bazel/.gitignore new file mode 100644 index 000000000..4975e987e --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-bazel/.gitignore @@ -0,0 +1,20 @@ +# Bazel build outputs +bazel-* +MODULE.bazel.lock + +# SAM build outputs +.aws-sam/ + +# JAR files +*.jar + +# Maven lock file (generated) +maven_install.json + +# IDE files +.idea/ +.vscode/ +*.iml + +# OS files +.DS_Store diff --git a/examples/powertools-examples-core-utilities/sam-bazel/BUILD.bazel b/examples/powertools-examples-core-utilities/sam-bazel/BUILD.bazel new file mode 100644 index 000000000..97fa37394 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-bazel/BUILD.bazel @@ -0,0 +1,78 @@ +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + +java_library( + name = "powertools_sam_lib_base", + srcs = glob(["src/main/java/**/*.java"]), + resources = glob(["src/main/resources/**"]), + deps = [ + "@maven//:software_amazon_lambda_powertools_tracing", + "@maven//:software_amazon_lambda_powertools_logging", + "@maven//:software_amazon_lambda_powertools_logging_log4j", + "@maven//:software_amazon_lambda_powertools_metrics", + "@maven//:com_amazonaws_aws_lambda_java_core", + "@maven//:com_amazonaws_aws_lambda_java_events", + "@maven//:org_aspectj_aspectjrt", + "@maven//:org_slf4j_slf4j_api", + "@maven//:com_fasterxml_jackson_core_jackson_databind", + "@maven//:org_apache_logging_log4j_log4j_api", + ], +) + +genrule( + name = "aspectj_weave", + srcs = [ + ":powertools_sam_lib_base", + "@maven//:software_amazon_lambda_powertools_tracing", + "@maven//:software_amazon_lambda_powertools_logging", + "@maven//:software_amazon_lambda_powertools_metrics", + "@maven//:com_amazonaws_aws_lambda_java_core", + "@maven//:com_amazonaws_aws_lambda_java_events", + "@maven//:org_aspectj_aspectjrt", + ], + outs = ["powertools_sam_lib_woven.jar"], + tools = [ + "@maven//:org_aspectj_aspectjweaver", + "@maven//:org_aspectj_aspectjtools", + ], + cmd = """ + # Get dependency JARs for classpath + ASPECTJ_TOOLS="$$(echo $(locations @maven//:org_aspectj_aspectjtools) $(locations @maven//:org_aspectj_aspectjweaver) | tr ' ' ':')" + ASPECTJ_RT="$(locations @maven//:org_aspectj_aspectjrt)" + LAMBDA_JARS="$$(echo $(locations @maven//:com_amazonaws_aws_lambda_java_core) $(locations @maven//:com_amazonaws_aws_lambda_java_events) | tr ' ' ':')" + ALL_DEPS="$$ASPECTJ_TOOLS:$$ASPECTJ_RT:$$LAMBDA_JARS" + + BASE_JAR=$$(echo $(locations :powertools_sam_lib_base) | cut -d' ' -f1) + POWERTOOLS_JARS="$$(echo $(locations @maven//:software_amazon_lambda_powertools_tracing) $(locations @maven//:software_amazon_lambda_powertools_logging) $(locations @maven//:software_amazon_lambda_powertools_metrics) | tr ' ' ':')" + + java -cp "$$ALL_DEPS" \ + org.aspectj.tools.ajc.Main \ + -inpath "$$BASE_JAR" \ + -aspectpath "$$POWERTOOLS_JARS" \ + -outjar $(location powertools_sam_lib_woven.jar) \ + -source 11 -target 11 -nowarn + """, +) + +java_import( + name = "powertools_sam_lib", + jars = [":powertools_sam_lib_woven.jar"], + deps = [ + "@maven//:software_amazon_lambda_powertools_tracing", + "@maven//:software_amazon_lambda_powertools_logging", + "@maven//:software_amazon_lambda_powertools_logging_log4j", + "@maven//:software_amazon_lambda_powertools_metrics", + "@maven//:com_amazonaws_aws_lambda_java_core", + "@maven//:com_amazonaws_aws_lambda_java_events", + "@maven//:org_aspectj_aspectjrt", + ], +) + +java_binary( + name = "powertools_sam", + main_class = "helloworld.App", + runtime_deps = [":powertools_sam_lib"], + deploy_manifest_lines = [ + "Main-Class: helloworld.App", + ], + create_executable = False, +) diff --git a/examples/powertools-examples-core-utilities/sam-bazel/MODULE.bazel b/examples/powertools-examples-core-utilities/sam-bazel/MODULE.bazel new file mode 100644 index 000000000..704b0cf1a --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-bazel/MODULE.bazel @@ -0,0 +1,27 @@ +module(name = "powertools_sam_bazel", version = "1.0.0") + +bazel_dep(name = "rules_java", version = "8.12.0") +bazel_dep(name = "rules_jvm_external", version = "6.3") + +maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") + +maven.install( + artifacts = [ + "software.amazon.lambda:powertools-tracing:2.2.1", + "software.amazon.lambda:powertools-logging:2.2.1", + "software.amazon.lambda:powertools-logging-log4j:2.2.1", + "software.amazon.lambda:powertools-metrics:2.2.1", + "com.amazonaws:aws-lambda-java-core:1.3.0", + "com.amazonaws:aws-lambda-java-events:3.16.1", + "org.aspectj:aspectjrt:1.9.22", + "org.aspectj:aspectjweaver:1.9.22", + "org.aspectj:aspectjtools:1.9.22", + "org.slf4j:slf4j-api:2.0.16", + ], + repositories = [ + "https://repo1.maven.org/maven2", + ], + lock_file = "//:maven_install.json", +) + +use_repo(maven, "maven") diff --git a/examples/powertools-examples-core-utilities/sam-bazel/README.md b/examples/powertools-examples-core-utilities/sam-bazel/README.md new file mode 100644 index 000000000..a18af2dd5 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-bazel/README.md @@ -0,0 +1,63 @@ +# Powertools for AWS Lambda (Java) - Core Utilities Example with Bazel + +This project demonstrates the Lambda for Powertools Java module deployed using [Serverless Application Model](https://aws.amazon.com/serverless/sam/) and built with Bazel. + +For general information on the deployed example itself, you can refer to the parent [README](../README.md) + +## Configuration + +SAM uses [template.yaml](template.yaml) to define the application's AWS resources. This file defines the Lambda function to be deployed as well as API Gateway for it. + +## Deploy the sample application + +To deploy the example, check out the instructions for getting started with SAM in [the examples directory](../../README.md) + +## Build and deploy + +```bash +# Build the application +bazel build //:powertools_sam_deploy.jar + +# Deploy the application +sam deploy --guided +``` + +## Local testing + +```bash +# Build the application +bazel build //:powertools_sam_deploy.jar + +# Test a single function locally +sam local invoke HelloWorldFunction --event events/event.json + +# Start the local API +sam local start-api + +# Test the API endpoints +curl http://127.0.0.1:3000/hello +curl http://127.0.0.1:3000/hellostream +``` + +## Additional notes + +You can watch the trace information or log information using the SAM CLI: + +```bash +# Tail the logs +sam logs --tail $MY_STACK + +# Tail the traces +sam traces --tail +``` + +### Pinning Maven versions + +To ensure reproducible builds, you can pin Maven dependency versions: + +```bash +# Generate lock file for reproducible builds +bazel run @maven//:pin +``` + +This creates `maven_install.json` which locks dependency versions and should be committed to version control. diff --git a/examples/powertools-examples-core-utilities/sam-bazel/events/event.json b/examples/powertools-examples-core-utilities/sam-bazel/events/event.json new file mode 100644 index 000000000..3822fadaa --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-bazel/events/event.json @@ -0,0 +1,63 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } + } + \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam-bazel/src/main/java/helloworld/App.java b/examples/powertools-examples-core-utilities/sam-bazel/src/main/java/helloworld/App.java new file mode 100644 index 000000000..2844e50fe --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-bazel/src/main/java/helloworld/App.java @@ -0,0 +1,108 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.entry; +import static software.amazon.lambda.powertools.tracing.TracingUtils.putMetadata; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricResolution; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import software.amazon.lambda.powertools.tracing.CaptureMode; +import software.amazon.lambda.powertools.tracing.Tracing; +import software.amazon.lambda.powertools.tracing.TracingUtils; + +/** + * Handler for requests to Lambda function. + */ +public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger log = LoggerFactory.getLogger(App.class); + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Logging(logEvent = true, samplingRate = 0.7) + @Tracing(captureMode = CaptureMode.RESPONSE_AND_ERROR) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + Map<String, String> headers = new HashMap<>(); + + headers.put("Content-Type", "application/json"); + headers.put("X-Custom-Header", "application/json"); + + metrics.addMetric("CustomMetric1", 1, MetricUnit.COUNT); + + DimensionSet dimensionSet = new DimensionSet(); + dimensionSet.addDimension("AnotherService", "CustomService"); + dimensionSet.addDimension("AnotherService1", "CustomService1"); + metrics.flushSingleMetric("CustomMetric2", 1, MetricUnit.COUNT, "Another", dimensionSet); + + metrics.addMetric("CustomMetric3", 1, MetricUnit.COUNT, MetricResolution.HIGH); + + MDC.put("test", "willBeLogged"); + + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() + .withHeaders(headers); + try { + final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); + log.info("", entry("ip", pageContents)); + TracingUtils.putAnnotation("Test", "New"); + String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); + + TracingUtils.withSubsegment("loggingResponse", subsegment -> { + String sampled = "log something out"; + log.info(sampled); + log.info(output); + }); + + log.info("After output"); + return response + .withStatusCode(200) + .withBody(output); + } catch (RuntimeException | IOException e) { + return response + .withBody("{}") + .withStatusCode(500); + } + } + + @Tracing(namespace = "getPageContents", captureMode = CaptureMode.DISABLED) + private String getPageContents(String address) throws IOException { + URL url = new URL(address); + putMetadata("getPageContents", address); + try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } + } +} diff --git a/examples/powertools-examples-core-utilities/sam-bazel/src/main/java/helloworld/AppStream.java b/examples/powertools-examples-core-utilities/sam-bazel/src/main/java/helloworld/AppStream.java new file mode 100644 index 000000000..e04641ddc --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-bazel/src/main/java/helloworld/AppStream.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; + +import java.io.InputStreamReader; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +public class AppStream implements RequestStreamHandler { + private static final ObjectMapper mapper = new ObjectMapper(); + private final static Logger log = LogManager.getLogger(AppStream.class); + + @Override + @Logging(logEvent = true) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + // RequestStreamHandler can be used instead of RequestHandler for cases when you'd like to deserialize request body or serialize response body yourself, instead of allowing that to happen automatically + // Note that you still need to return a proper JSON for API Gateway to handle + // See Lambda Response format for examples: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + public void handleRequest(InputStream input, OutputStream output, Context context) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); + PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)))) { + + log.info("Received: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mapper.readTree(reader))); + + writer.write("{\"body\": \"" + System.currentTimeMillis() + "\"} "); + } catch (IOException e) { + log.error("Something has gone wrong: ", e); + } + } +} \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam-bazel/src/main/resources/log4j2.xml b/examples/powertools-examples-core-utilities/sam-bazel/src/main/resources/log4j2.xml new file mode 100644 index 000000000..e1fd14cea --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-bazel/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + <Root level="info"> + <AppenderRef ref="JsonAppender"/> + </Root> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam-bazel/template.yaml b/examples/powertools-examples-core-utilities/sam-bazel/template.yaml new file mode 100644 index 000000000..1e489b8eb --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-bazel/template.yaml @@ -0,0 +1,66 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: > + CoreUtilities + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 20 + Runtime: java11 + MemorySize: 512 + Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html + Environment: + Variables: + # Powertools for AWS Lambda (Java) env vars: https://docs.powertools.aws.dev/lambda/java/#environment-variables + POWERTOOLS_LOG_LEVEL: INFO + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_LOGGER_LOG_EVENT: true + POWERTOOLS_METRICS_NAMESPACE: Coreutilities + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: bazel-bin/powertools_sam_deploy.jar + Handler: helloworld.App::handleRequest + Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + Variables: + POWERTOOLS_SERVICE_NAME: hello + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + + HelloWorldStreamFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: bazel-bin/powertools_sam_deploy.jar + Handler: helloworld.AppStream::handleRequest + MemorySize: 512 + Tracing: Active + Environment: + Variables: + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.7 + Events: + HelloWorld: + Type: Api + Properties: + Path: /hellostream + Method: get + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile b/examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile new file mode 100644 index 000000000..8377d6dc7 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile @@ -0,0 +1,14 @@ +#Use the official AWS SAM base image for Java 21 +FROM public.ecr.aws/sam/build-java21@sha256:a5554d68374e19450c6c88448516ac95a9acedc779f318040f5c230134b4e461 + +#Install GraalVM dependencies +RUN curl -4 -L curl https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz | tar -xvz +RUN mv graalvm-jdk-21.* /usr/lib/graalvm + +#Make native image and mvn available on CLI +RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image +RUN ln -s /usr/lib/maven/bin/mvn /usr/bin/mvn + +#Set GraalVM as default +ENV JAVA_HOME=/usr/lib/graalvm +ENV PATH=/usr/lib/graalvm/bin:$PATH diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/Makefile b/examples/powertools-examples-core-utilities/sam-graalvm/Makefile new file mode 100644 index 000000000..f8abe6870 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/Makefile @@ -0,0 +1,6 @@ +build-HelloWorldFunction: + mvn clean package -P native-image + chmod +x target/hello-world + cp target/hello-world $(ARTIFACTS_DIR) # (ARTIFACTS_DIR --> https://github.com/aws/aws-lambda-builders/blob/develop/aws_lambda_builders/workflows/custom_make/DESIGN.md#implementation) + chmod +x src/main/config/bootstrap + cp src/main/config/bootstrap $(ARTIFACTS_DIR) diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/README.md b/examples/powertools-examples-core-utilities/sam-graalvm/README.md new file mode 100644 index 000000000..5572684ad --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/README.md @@ -0,0 +1,64 @@ +# Powertools for AWS Lambda (Java) - Core Utilities Example with SAM on GraalVM + +This project demonstrates the Lambda for Powertools Java module deployed using [Serverless Application Model](https://aws.amazon.com/serverless/sam/) running as a GraalVM native image. + +For general information on the deployed example itself, you can refer to the parent [README](../README.md) + +## Configuration + +- SAM uses [template.yaml](template.yaml) to define the application's AWS resources. + This file defines the Lambda function to be deployed as well as API Gateway for it. + +- Set the environment to use GraalVM + +```shell +export JAVA_HOME=<path to GraalVM> +``` + +## Build the sample application + +- Build the Docker image that will be used as the environment for SAM build: + +```shell +docker build --platform linux/amd64 . -t powertools-examples-core-sam-graalvm +``` + +- Build the SAM project using the docker image + +```shell +sam build --use-container --build-image powertools-examples-core-sam-graalvm +``` + +#### [Optional] Building with -SNAPSHOT versions of PowerTools + +- If you are testing the example with a -SNAPSHOT version of PowerTools, the maven build inside the docker image will fail. This is because the -SNAPSHOT version of the PowerTools library that you are working on is still not available in maven central/snapshot repository. + To get around this, follow these steps: + - Create the native image using the `docker` command below on your development machine. The native image is created in the `target` directory. + - `` docker run --platform linux/amd64 -it -v `pwd`:`pwd` -w `pwd` -v ~/.m2:/root/.m2 powertools-examples-core-sam-graalvm mvn clean -Pnative-image package -DskipTests `` + - Edit the [`Makefile`](Makefile) remove this line + - `mvn clean package -P native-image` + - Build the SAM project using the docker image + - `sam build --use-container --build-image powertools-examples-core-sam-graalvm` + +## Deploy the sample application + +- SAM deploy + +```shell +sam deploy +``` + +To deploy the example, check out the instructions for getting +started with SAM in [the examples directory](../../README.md) + +## Additional notes + +You can watch the trace information or log information using the SAM CLI: + +```bash +# Tail the logs +sam logs --tail $MY_STACK + +# Tail the traces +sam traces --tail +``` diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/events/event.json b/examples/powertools-examples-core-utilities/sam-graalvm/events/event.json new file mode 100644 index 000000000..3822fadaa --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/events/event.json @@ -0,0 +1,63 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } + } + \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/pom.xml b/examples/powertools-examples-core-utilities/sam-graalvm/pom.xml new file mode 100644 index 000000000..eea0357e9 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/pom.xml @@ -0,0 +1,186 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <name>Powertools for AWS Lambda (Java) - Examples - Core Utilities (logging, tracing, metrics) with SAM GraalVM</name> + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-core-utilities-sam-graalvm</artifactId> + <packaging>jar</packaging> + + <properties> + <log4j.version>2.25.3</log4j.version> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <aspectj.version>1.9.20.1</aspectj.version> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <version>3.16.1</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + <version>2.8.7</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-api</artifactId> + <version>${log4j.version}</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + <version>${log4j.version}</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j2-impl</artifactId> + <version>${log4j.version}</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-layout-template-json</artifactId> + <version>${log4j.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.15.0</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>3.6.1</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <transformers> + <transformer implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer"/> + </transformers> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId> + <version>0.2.0</version> + </dependency> + </dependencies> + </plugin> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>build-native</id> + <goals> + <goal>build</goal> + </goals> + <phase>package</phase> + </execution> + </executions> + <configuration> + <imageName>hello-world</imageName> + <mainClass>com.amazonaws.services.lambda.runtime.api.client.AWSLambda</mainClass> + <buildArgs> + <!-- required for AWS Lambda Runtime Interface Client --> + <arg>--enable-url-protocols=http</arg> + <arg>--add-opens java.base/java.util=ALL-UNNAMED</arg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/config/bootstrap b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/config/bootstrap new file mode 100644 index 000000000..8e7928cd3 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/config/bootstrap @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +./hello-world $_HANDLER \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/java/helloworld/App.java b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/java/helloworld/App.java new file mode 100644 index 000000000..68664feec --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/java/helloworld/App.java @@ -0,0 +1,110 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.entry; +import static software.amazon.lambda.powertools.tracing.TracingUtils.putMetadata; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricResolution; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import software.amazon.lambda.powertools.tracing.CaptureMode; +import software.amazon.lambda.powertools.tracing.Tracing; +import software.amazon.lambda.powertools.tracing.TracingUtils; + +/** + * Handler for requests to Lambda function. + */ +public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger log = LoggerFactory.getLogger(App.class); + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Logging(logEvent = true, samplingRate = 0.7) + @Tracing(captureMode = CaptureMode.RESPONSE_AND_ERROR) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + Map<String, String> headers = new HashMap<>(); + + headers.put("Content-Type", "application/json"); + headers.put("X-Custom-Header", "application/json"); + + metrics.addMetric("CustomMetric1", 1, MetricUnit.COUNT); + + DimensionSet dimensionSet = DimensionSet.of( + "AnotherService", "CustomService", + "AnotherService1", "CustomService1"); + metrics.flushSingleMetric("CustomMetric2", 1, MetricUnit.COUNT, "Another", dimensionSet); + + metrics.addMetric("CustomMetric3", 1, MetricUnit.COUNT, MetricResolution.HIGH); + + MDC.put("test", "willBeLogged"); + + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() + .withHeaders(headers); + try { + final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); + log.info("", entry("ip", pageContents)); + TracingUtils.putAnnotation("Test", "New"); + String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); + + TracingUtils.withSubsegment("loggingResponse", subsegment -> { + String sampled = "log something out"; + log.info(sampled); + log.info(output); + }); + + log.info("After output"); + return response + .withStatusCode(200) + .withBody(output); + } catch (RuntimeException | IOException e) { + return response + .withBody("{}") + .withStatusCode(500); + } + } + + @Tracing + private void log() { + log.info("inside threaded logging for function"); + } + + @Tracing(namespace = "getPageContents", captureMode = CaptureMode.DISABLED) + private String getPageContents(String address) throws IOException { + URL url = new URL(address); + putMetadata("getPageContents", address); + try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } + } +} diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..e97baa294 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,44 @@ +[ + { + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields":[{"name":"logger"}] + }, + { + "name":"java.lang.Void", + "methods":[{"name":"<init>","parameterTypes":[] }] + }, + { + "name":"java.util.Collections$UnmodifiableMap", + "fields":[{"name":"m"}] + }, + { + "name":"jdk.internal.module.IllegalAccessLogger", + "fields":[{"name":"logger"}] + }, + { + "name":"sun.misc.Unsafe", + "fields":[{"name":"theUnsafe"}] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/native-image.properties b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/native-image.properties new file mode 100644 index 000000000..db5ebaa55 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/native-image.properties @@ -0,0 +1 @@ +Args = --enable-url-protocols=http,https \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/reflect-config.json b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/reflect-config.json new file mode 100644 index 000000000..06ea9ce2f --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/reflect-config.json @@ -0,0 +1,11 @@ +[ + { + "name": "helloworld.App", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/resource-config.json b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/log4j2.xml b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/log4j2.xml new file mode 100644 index 000000000..60975d487 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + <Root level="info"> + <AppenderRef ref="JsonAppender"/> + </Root> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/template.yaml b/examples/powertools-examples-core-utilities/sam-graalvm/template.yaml new file mode 100644 index 000000000..feb55743b --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam-graalvm/template.yaml @@ -0,0 +1,52 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + CoreUtilities + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 20 + MemorySize: 512 + Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html + Environment: + Variables: + # Powertools for AWS Lambda (Java) env vars: https://docs.powertools.aws.dev/lambda/java/#environment-variables + POWERTOOLS_LOG_LEVEL: INFO + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_LOGGER_LOG_EVENT: true + POWERTOOLS_METRICS_NAMESPACE: Coreutilities + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: . + Handler: helloworld.App::handleRequest + Runtime: provided.al2023 + MemorySize: 512 + Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + Variables: + POWERTOOLS_SERVICE_NAME: hello + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + Metadata: + BuildMethod: makefile + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/examples/powertools-examples-core-utilities/sam/README.md b/examples/powertools-examples-core-utilities/sam/README.md new file mode 100644 index 000000000..7a4279ae3 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam/README.md @@ -0,0 +1,24 @@ +# Powertools for AWS Lambda (Java) - Core Utilities Example with SAM + +This project demonstrates the Lambda for Powertools Java module deployed using [Serverless Application Model](https://aws.amazon.com/serverless/sam/). + +For general information on the deployed example itself, you can refer to the parent [README](../README.md) + +## Configuration +SAM uses [template.yaml](template.yaml) to define the application's AWS resources. +This file defines the Lambda function to be deployed as well as API Gateway for it. + +## Deploy the sample application +To deploy the example, check out the instructions for getting +started with SAM in [the examples directory](../../README.md) + +## Additional notes + +You can watch the trace information or log information using the SAM CLI: +```bash +# Tail the logs +sam logs --tail $MY_STACK + +# Tail the traces +sam traces --tail +``` \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam/events/event.json b/examples/powertools-examples-core-utilities/sam/events/event.json new file mode 100644 index 000000000..3822fadaa --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam/events/event.json @@ -0,0 +1,63 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } + } + \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/sam/pom.xml b/examples/powertools-examples-core-utilities/sam/pom.xml new file mode 100644 index 000000000..2d6a00161 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam/pom.xml @@ -0,0 +1,127 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <name>Powertools for AWS Lambda (Java) - Examples - Core Utilities (logging, tracing, metrics) with SAM</name> + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-core-utilities-sam</artifactId> + <packaging>jar</packaging> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <aspectj.version>1.9.20.1</aspectj.version> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <version>3.16.1</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>3.6.1</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <transformers> + <transformer implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer"/> + </transformers> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId> + <version>0.2.0</version> + </dependency> + </dependencies> + </plugin> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/examples/powertools-examples-core-utilities/sam/src/main/java/helloworld/App.java b/examples/powertools-examples-core-utilities/sam/src/main/java/helloworld/App.java new file mode 100644 index 000000000..2844e50fe --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam/src/main/java/helloworld/App.java @@ -0,0 +1,108 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.entry; +import static software.amazon.lambda.powertools.tracing.TracingUtils.putMetadata; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricResolution; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import software.amazon.lambda.powertools.tracing.CaptureMode; +import software.amazon.lambda.powertools.tracing.Tracing; +import software.amazon.lambda.powertools.tracing.TracingUtils; + +/** + * Handler for requests to Lambda function. + */ +public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger log = LoggerFactory.getLogger(App.class); + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Logging(logEvent = true, samplingRate = 0.7) + @Tracing(captureMode = CaptureMode.RESPONSE_AND_ERROR) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + Map<String, String> headers = new HashMap<>(); + + headers.put("Content-Type", "application/json"); + headers.put("X-Custom-Header", "application/json"); + + metrics.addMetric("CustomMetric1", 1, MetricUnit.COUNT); + + DimensionSet dimensionSet = new DimensionSet(); + dimensionSet.addDimension("AnotherService", "CustomService"); + dimensionSet.addDimension("AnotherService1", "CustomService1"); + metrics.flushSingleMetric("CustomMetric2", 1, MetricUnit.COUNT, "Another", dimensionSet); + + metrics.addMetric("CustomMetric3", 1, MetricUnit.COUNT, MetricResolution.HIGH); + + MDC.put("test", "willBeLogged"); + + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() + .withHeaders(headers); + try { + final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); + log.info("", entry("ip", pageContents)); + TracingUtils.putAnnotation("Test", "New"); + String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); + + TracingUtils.withSubsegment("loggingResponse", subsegment -> { + String sampled = "log something out"; + log.info(sampled); + log.info(output); + }); + + log.info("After output"); + return response + .withStatusCode(200) + .withBody(output); + } catch (RuntimeException | IOException e) { + return response + .withBody("{}") + .withStatusCode(500); + } + } + + @Tracing(namespace = "getPageContents", captureMode = CaptureMode.DISABLED) + private String getPageContents(String address) throws IOException { + URL url = new URL(address); + putMetadata("getPageContents", address); + try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } + } +} diff --git a/examples/powertools-examples-core-utilities/sam/src/main/java/helloworld/AppStream.java b/examples/powertools-examples-core-utilities/sam/src/main/java/helloworld/AppStream.java new file mode 100644 index 000000000..d3ebbef5d --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam/src/main/java/helloworld/AppStream.java @@ -0,0 +1,62 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; + +public class AppStream implements RequestStreamHandler { + private static final ObjectMapper mapper = new ObjectMapper(); + private static final Logger log = LoggerFactory.getLogger(AppStream.class); + + @Override + @Logging(logEvent = true) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + // RequestStreamHandler can be used instead of RequestHandler for cases when you'd like to deserialize request body + // or serialize response body yourself, instead of allowing that to happen automatically + // Note that you still need to return a proper JSON for API Gateway to handle + // See Lambda Response format for examples: + // https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + public void handleRequest(InputStream input, OutputStream output, Context context) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); + PrintWriter writer = new PrintWriter( + new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)))) { + + log.info( + "Received: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mapper.readTree(reader))); + + writer.write("{\"body\": \"" + System.currentTimeMillis() + "\"} "); + } catch (IOException e) { + log.error("Something has gone wrong: ", e); + } + } +} diff --git a/examples/powertools-examples-core-utilities/sam/src/main/resources/log4j2.xml b/examples/powertools-examples-core-utilities/sam/src/main/resources/log4j2.xml new file mode 100644 index 000000000..e140022e4 --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + <BufferingAppender name="BufferedAppender" bufferAtVerbosity="DEBUG"> + <AppenderRef ref="JsonAppender" /> + </BufferingAppender> + </Appenders> + <Loggers> + <Root level="debug"> + <AppenderRef ref="BufferedAppender" /> + </Root> + </Loggers> +</Configuration> diff --git a/examples/powertools-examples-core-utilities/sam/template.yaml b/examples/powertools-examples-core-utilities/sam/template.yaml new file mode 100644 index 000000000..6b1814dce --- /dev/null +++ b/examples/powertools-examples-core-utilities/sam/template.yaml @@ -0,0 +1,66 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: > + CoreUtilities + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 20 + Runtime: java11 + MemorySize: 512 + Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html + Environment: + Variables: + # Powertools for AWS Lambda (Java) env vars: https://docs.powertools.aws.dev/lambda/java/#environment-variables + POWERTOOLS_LOG_LEVEL: DEBUG # We use log buffering to buffer DEBUG logs (see log4j2.xml) + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_LOGGER_LOG_EVENT: true + POWERTOOLS_METRICS_NAMESPACE: Coreutilities + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: . + Handler: helloworld.App::handleRequest + Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + Variables: + POWERTOOLS_SERVICE_NAME: hello + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + + HelloWorldStreamFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: helloworld.AppStream::handleRequest + MemorySize: 512 + Tracing: Active + Environment: + Variables: + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.7 + Events: + HelloWorld: + Type: Api + Properties: + Path: /hellostream + Method: get + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/examples/powertools-examples-core-utilities/serverless/README.md b/examples/powertools-examples-core-utilities/serverless/README.md new file mode 100644 index 000000000..9e33aa9ff --- /dev/null +++ b/examples/powertools-examples-core-utilities/serverless/README.md @@ -0,0 +1,26 @@ +# Powertools for AWS Lambda (Java) - Core Utilities Example with Serverless Framework + +This project demonstrates the Lambda for Powertools Java module deployed using [Serverless Framework](https://www.serverless.com/framework). +For general information on the deployed example itself, you can refer to the parent [README](../README.md). +To install Serverless Framework if you don't have it yet, you can follow the [Getting Started Guide](https://www.serverless.com/framework/docs/getting-started). + +## Configuration +Serverless Framework uses [serverless.yml](serverless.yml) to define the application's AWS resources. +This file defines the Lambda function to be deployed as well as API Gateway for it. + +It is a [Maven](https://maven.apache.org/) based project, so you can open this project with any Maven compatible Java IDE to build and run tests. + + +## Deploy the sample application + +To deploy the app, simply run the following commands: +```bash +mvn package && sls deploy +``` + +## Useful commands + +Deploy a single function +```bash +sls deploy function -f hello +``` \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/serverless/pom.xml b/examples/powertools-examples-core-utilities/serverless/pom.xml new file mode 100644 index 000000000..26e647dad --- /dev/null +++ b/examples/powertools-examples-core-utilities/serverless/pom.xml @@ -0,0 +1,128 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <name>Powertools for AWS Lambda (Java) - Examples - Core Utilities (logging, tracing, metrics) with Serverless</name> + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-core-utilities-serverless</artifactId> + <packaging>jar</packaging> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <aspectj.version>1.9.20.1</aspectj.version> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <version>3.16.1</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + + <build> + <finalName>helloworld-lambda</finalName> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>3.6.1</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <transformers> + <transformer implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer"/> + </transformers> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId> + <version>0.2.0</version> + </dependency> + </dependencies> + </plugin> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/examples/powertools-examples-core-utilities/serverless/serverless.yml b/examples/powertools-examples-core-utilities/serverless/serverless.yml new file mode 100644 index 000000000..d8dec8080 --- /dev/null +++ b/examples/powertools-examples-core-utilities/serverless/serverless.yml @@ -0,0 +1,40 @@ +service: hello +# app and org for use with dashboard.serverless.com +#app: your-app-name +#org: your-org-name + +# You can pin your service to only deploy with a specific Serverless version +# Check out our docs for more details +frameworkVersion: '3' + +provider: + name: aws + runtime: java11 + +# you can overwrite defaults here +# stage: dev +# region: us-east-1 + +# you can define service wide environment variables here + environment: + POWERTOOLS_LOG_LEVEL: INFO + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_METRICS_NAMESPACE: Coreutilities + +# you can add packaging information here +package: + artifact: target/helloworld-lambda.jar + +functions: + hello: + handler: helloworld.App + memorySize: 512 + timeout: 20 + tracing: "Active" + events: + - httpApi: + path: /hello + method: get +# Define function environment variables here + environment: + POWERTOOLS_SERVICE_NAME: hello diff --git a/examples/powertools-examples-core-utilities/serverless/src/main/java/helloworld/App.java b/examples/powertools-examples-core-utilities/serverless/src/main/java/helloworld/App.java new file mode 100644 index 000000000..771f5c1f1 --- /dev/null +++ b/examples/powertools-examples-core-utilities/serverless/src/main/java/helloworld/App.java @@ -0,0 +1,102 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.entry; +import static software.amazon.lambda.powertools.tracing.TracingUtils.putMetadata; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.slf4j.MDC; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import software.amazon.lambda.powertools.tracing.CaptureMode; +import software.amazon.lambda.powertools.tracing.Tracing; +import software.amazon.lambda.powertools.tracing.TracingUtils; + +/** + * Handler for requests to Lambda function. + */ +public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger log = LogManager.getLogger(App.class); + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Logging(logEvent = true, samplingRate = 0.7) + @Tracing(captureMode = CaptureMode.RESPONSE_AND_ERROR) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + Map<String, String> headers = new HashMap<>(); + + headers.put("Content-Type", "application/json"); + headers.put("X-Custom-Header", "application/json"); + + metrics.addMetric("CustomMetric1", 1, MetricUnit.COUNT); + + DimensionSet dimensionSet = DimensionSet.of( + "AnotherService", "CustomService", + "AnotherService1", "CustomService1"); + metrics.flushSingleMetric("CustomMetric2", 1, MetricUnit.COUNT, "Another", dimensionSet); + + MDC.put("test", "willBeLogged"); + + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() + .withHeaders(headers); + try { + final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); + log.info("", entry("ip", pageContents)); + TracingUtils.putAnnotation("Test", "New"); + String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); + + TracingUtils.withSubsegment("loggingResponse", subsegment -> { + String sampled = "log something out"; + log.info(sampled); + log.info(output); + }); + + log.info("After output"); + return response + .withStatusCode(200) + .withBody(output); + } catch (RuntimeException | IOException e) { + return response + .withBody("{}") + .withStatusCode(500); + } + } + + @Tracing(namespace = "getPageContents", captureMode = CaptureMode.DISABLED) + private String getPageContents(String address) throws IOException { + URL url = new URL(address); + putMetadata("getPageContents", address); + try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } + } +} diff --git a/examples/powertools-examples-core-utilities/serverless/src/main/java/helloworld/AppStream.java b/examples/powertools-examples-core-utilities/serverless/src/main/java/helloworld/AppStream.java new file mode 100644 index 000000000..c13ab9f2e --- /dev/null +++ b/examples/powertools-examples-core-utilities/serverless/src/main/java/helloworld/AppStream.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; + +public class AppStream implements RequestStreamHandler { + private static final ObjectMapper mapper = new ObjectMapper(); + + @Override + @Logging(logEvent = true) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { + Map map = mapper.readValue(input, Map.class); + + System.out.println(map.size()); + } +} diff --git a/examples/powertools-examples-core-utilities/serverless/src/main/resources/log4j2.xml b/examples/powertools-examples-core-utilities/serverless/src/main/resources/log4j2.xml new file mode 100644 index 000000000..0cc0953f0 --- /dev/null +++ b/examples/powertools-examples-core-utilities/serverless/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + <Root level="info"> + <AppenderRef ref="JsonAppender"/> + </Root> + </Loggers> +</Configuration> diff --git a/examples/powertools-examples-core-utilities/terraform/.tflint.hcl b/examples/powertools-examples-core-utilities/terraform/.tflint.hcl new file mode 100644 index 000000000..18e69352b --- /dev/null +++ b/examples/powertools-examples-core-utilities/terraform/.tflint.hcl @@ -0,0 +1,3 @@ +rule "terraform_required_version" { + enabled = false +} \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/terraform/README.md b/examples/powertools-examples-core-utilities/terraform/README.md new file mode 100644 index 000000000..71c78d437 --- /dev/null +++ b/examples/powertools-examples-core-utilities/terraform/README.md @@ -0,0 +1,27 @@ +# Powertools for AWS Lambda (Java) - Core Utilities Example with Terraform + +This project demonstrates the Lambda for Powertools Java module deployed using [Terraform](https://www.terraform.io/). +For general information on the deployed example itself, you can refer to the parent [README](../README.md). +To install Terraform if you don't have it yet, you can follow the [Install Terraform Guide](https://developer.hashicorp.com/terraform/downloads?product_intent=terraform). + +## Configuration +Terraform uses [main.tf](./main.tf) to define the application's AWS resources. +This file defines the Lambda function to be deployed as well as API Gateway for it. + +It is a [Maven](https://maven.apache.org/) based project, so you can open this project with any Maven compatible Java IDE to build and run tests. + + +## Deploy the sample application + +To deploy the app, simply run the following commands: +```bash +terraform init +mvn package && terraform apply +``` + +## Useful commands + +To destroy the app +```bash +terraform destroy +``` diff --git a/examples/powertools-examples-core-utilities/terraform/infra/api-gateway.tf b/examples/powertools-examples-core-utilities/terraform/infra/api-gateway.tf new file mode 100644 index 000000000..dba1c3616 --- /dev/null +++ b/examples/powertools-examples-core-utilities/terraform/infra/api-gateway.tf @@ -0,0 +1,94 @@ +resource "aws_api_gateway_rest_api" "hello_world_api" { + name = "hello_world_api" + description = "API Gateway endpoint URL for Prod stage for Hello World function" +} + +resource "aws_api_gateway_resource" "hello_resource" { + rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}" + parent_id = "${aws_api_gateway_rest_api.hello_world_api.root_resource_id}" + path_part = "hello" +} + +resource "aws_api_gateway_resource" "hello_stream_resource" { + rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}" + parent_id = "${aws_api_gateway_rest_api.hello_world_api.root_resource_id}" + path_part = "hellostream" +} + +resource "aws_api_gateway_method" "hello_get_method" { + rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}" + resource_id = "${aws_api_gateway_resource.hello_resource.id}" + http_method = "GET" + authorization = "NONE" +} + +resource "aws_api_gateway_method" "hello_stream_get_method" { + rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}" + resource_id = "${aws_api_gateway_resource.hello_stream_resource.id}" + http_method = "GET" + authorization = "NONE" +} + +resource "aws_api_gateway_integration" "java_lambda_integration" { + rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}" + resource_id = "${aws_api_gateway_resource.hello_resource.id}" + http_method = "${aws_api_gateway_method.hello_get_method.http_method}" + + integration_http_method = "POST" + type = "AWS_PROXY" + uri = "${aws_lambda_function.hello_world_lambda.invoke_arn}" +} + +resource "aws_api_gateway_integration" "java_stream_lambda_integration" { + rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}" + resource_id = "${aws_api_gateway_resource.hello_stream_resource.id}" + http_method = "${aws_api_gateway_method.hello_stream_get_method.http_method}" + + integration_http_method = "POST" + type = "AWS_PROXY" + uri = "${aws_lambda_function.hello_world_stream_lambda.invoke_arn}" +} + +resource "aws_api_gateway_deployment" "prod_deployment" { + depends_on = [aws_api_gateway_integration.java_lambda_integration, aws_api_gateway_integration.java_stream_lambda_integration] + rest_api_id = "${aws_api_gateway_rest_api.hello_world_api.id}" + stage_name = "prod" +} + +# Allows API gateway to invoke lambda +resource "aws_lambda_permission" "hello_world_lambda_invoke" { + statement_id = "AllowAPIGatewayInvoke" + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.hello_world_lambda.function_name}" + principal = "apigateway.amazonaws.com" + source_arn = "${aws_api_gateway_rest_api.hello_world_api.execution_arn}/${aws_api_gateway_deployment.prod_deployment.stage_name}/GET/hello" +} + +# Allows API gateway to invoke lambda +resource "aws_lambda_permission" "hello_world_lambda_testinvoke" { + statement_id = "AllowAPIGatewayTestInvoke" + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.hello_world_lambda.function_name}" + principal = "apigateway.amazonaws.com" + source_arn = "${aws_api_gateway_rest_api.hello_world_api.execution_arn}/test-invoke-stage/GET/hello" +} + +# Allows API gateway to invoke lambda +resource "aws_lambda_permission" "hello_world_stream_lambda_invoke" { + statement_id = "AllowAPIGatewayInvoke" + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.hello_world_stream_lambda.function_name}" + principal = "apigateway.amazonaws.com" + source_arn = "${aws_api_gateway_rest_api.hello_world_api.execution_arn}/${aws_api_gateway_deployment.prod_deployment.stage_name}/GET/hellostream" +} + +# Allows API gateway to invoke lambda +resource "aws_lambda_permission" "hello_world_stream_lambda_testinvoke" { + statement_id = "AllowAPIGatewayTestInvoke" + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.hello_world_stream_lambda.function_name}" + principal = "apigateway.amazonaws.com" + source_arn = "${aws_api_gateway_rest_api.hello_world_api.execution_arn}/test-invoke-stage/GET/hellostream" +} + +output "invoke" {value=aws_api_gateway_deployment.prod_deployment.invoke_url} \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/terraform/infra/lambda.tf b/examples/powertools-examples-core-utilities/terraform/infra/lambda.tf new file mode 100644 index 000000000..abebccf54 --- /dev/null +++ b/examples/powertools-examples-core-utilities/terraform/infra/lambda.tf @@ -0,0 +1,95 @@ +resource "aws_lambda_function" "hello_world_lambda" { + runtime = "java11" + filename = "target/helloworld-lambda.jar" + source_code_hash = filebase64sha256("target/helloworld-lambda.jar") + function_name = "hello_world_lambda" + + handler = "helloworld.App" + description = "Powertools example, deployed by Terraform" + timeout = 20 + memory_size = 512 + role = "${aws_iam_role.iam_role_for_lambda.arn}" + tracing_config { + mode = "Active" + } + depends_on = [aws_cloudwatch_log_group.log_group] +} + +resource "aws_lambda_function" "hello_world_stream_lambda" { + runtime = "java11" + filename = "target/helloworld-lambda.jar" + source_code_hash = filebase64sha256("target/helloworld-lambda.jar") + function_name = "hello_world_stream_lambda" + + handler = "helloworld.AppStream" + description = "Powertools example, deployed by Terraform" + timeout = 20 + memory_size = 512 + role = "${aws_iam_role.iam_role_for_lambda.arn}" + tracing_config { + mode = "Active" + } + depends_on = [aws_cloudwatch_log_group.log_group] +} + +# Create a log group for the lambda +resource "aws_cloudwatch_log_group" "log_group" { + name = "/aws/lambda/hello_world_lambda" +} + +# Create a log group for the lambda +resource "aws_cloudwatch_log_group" "log_group_stream" { + name = "/aws/lambda/hello_world_stream_lambda" +} + +# lambda role +resource "aws_iam_role" "iam_role_for_lambda" { + name = "lambda-invoke-role" + assume_role_policy = <<EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Principal": { + "Service": "lambda.amazonaws.com" + }, + "Effect": "Allow", + "Sid": "" + } + ] + } +EOF +} + +# lambda policy, allow logs to be published to CloudWatch, and traces to Xray +resource "aws_iam_policy" "iam_policy_for_lambda" { + name = "lambda-invoke-policy" + path = "/" + + policy = <<EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "LambdaPolicy", + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "xray:PutTelemetryRecords", + "xray:PutTraceSegments" + ], + "Resource": "*" + } + ] + } +EOF +} + +# Attach the policy to the role +resource "aws_iam_role_policy_attachment" "aws_iam_role_policy_attachment" { + role = "${aws_iam_role.iam_role_for_lambda.name}" + policy_arn = "${aws_iam_policy.iam_policy_for_lambda.arn}" +} diff --git a/examples/powertools-examples-core-utilities/terraform/main.tf b/examples/powertools-examples-core-utilities/terraform/main.tf new file mode 100644 index 000000000..10504088a --- /dev/null +++ b/examples/powertools-examples-core-utilities/terraform/main.tf @@ -0,0 +1,22 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +# terraform modules +module "powertools_for_java_lambda" { + source = "./infra/" +} + +output "api_url" { + value = module.powertools_for_java_lambda.invoke + description = "URL where the API gateway can be invoked" +} + +# Configure the AWS Provider +provider "aws" { +} \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/terraform/pom.xml b/examples/powertools-examples-core-utilities/terraform/pom.xml new file mode 100644 index 000000000..4de1e415c --- /dev/null +++ b/examples/powertools-examples-core-utilities/terraform/pom.xml @@ -0,0 +1,138 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <name>Powertools for AWS Lambda (Java) - Examples - Core Utilities (logging, tracing, metrics) with Terraform</name> + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-core-utilities-terraform</artifactId> + <packaging>jar</packaging> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <aspectj.version>1.9.20.1</aspectj.version> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <version>3.16.1</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + + <build> + <finalName>helloworld-lambda</finalName> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>3.6.1</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <transformers> + <transformer + implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer" /> + </transformers> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId> + <version>0.2.0</version> + </dependency> + </dependencies> + </plugin> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <LAMBDA_TASK_ROOT>handler</LAMBDA_TASK_ROOT> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/examples/powertools-examples-core-utilities/terraform/src/main/java/helloworld/App.java b/examples/powertools-examples-core-utilities/terraform/src/main/java/helloworld/App.java new file mode 100644 index 000000000..771f5c1f1 --- /dev/null +++ b/examples/powertools-examples-core-utilities/terraform/src/main/java/helloworld/App.java @@ -0,0 +1,102 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.entry; +import static software.amazon.lambda.powertools.tracing.TracingUtils.putMetadata; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.slf4j.MDC; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import software.amazon.lambda.powertools.tracing.CaptureMode; +import software.amazon.lambda.powertools.tracing.Tracing; +import software.amazon.lambda.powertools.tracing.TracingUtils; + +/** + * Handler for requests to Lambda function. + */ +public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger log = LogManager.getLogger(App.class); + private static final Metrics metrics = MetricsFactory.getMetricsInstance(); + + @Logging(logEvent = true, samplingRate = 0.7) + @Tracing(captureMode = CaptureMode.RESPONSE_AND_ERROR) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + Map<String, String> headers = new HashMap<>(); + + headers.put("Content-Type", "application/json"); + headers.put("X-Custom-Header", "application/json"); + + metrics.addMetric("CustomMetric1", 1, MetricUnit.COUNT); + + DimensionSet dimensionSet = DimensionSet.of( + "AnotherService", "CustomService", + "AnotherService1", "CustomService1"); + metrics.flushSingleMetric("CustomMetric2", 1, MetricUnit.COUNT, "Another", dimensionSet); + + MDC.put("test", "willBeLogged"); + + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() + .withHeaders(headers); + try { + final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); + log.info("", entry("ip", pageContents)); + TracingUtils.putAnnotation("Test", "New"); + String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); + + TracingUtils.withSubsegment("loggingResponse", subsegment -> { + String sampled = "log something out"; + log.info(sampled); + log.info(output); + }); + + log.info("After output"); + return response + .withStatusCode(200) + .withBody(output); + } catch (RuntimeException | IOException e) { + return response + .withBody("{}") + .withStatusCode(500); + } + } + + @Tracing(namespace = "getPageContents", captureMode = CaptureMode.DISABLED) + private String getPageContents(String address) throws IOException { + URL url = new URL(address); + putMetadata("getPageContents", address); + try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } + } +} diff --git a/examples/powertools-examples-core-utilities/terraform/src/main/java/helloworld/AppStream.java b/examples/powertools-examples-core-utilities/terraform/src/main/java/helloworld/AppStream.java new file mode 100644 index 000000000..c13ab9f2e --- /dev/null +++ b/examples/powertools-examples-core-utilities/terraform/src/main/java/helloworld/AppStream.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.metrics.FlushMetrics; + +public class AppStream implements RequestStreamHandler { + private static final ObjectMapper mapper = new ObjectMapper(); + + @Override + @Logging(logEvent = true) + @FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) + public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { + Map map = mapper.readValue(input, Map.class); + + System.out.println(map.size()); + } +} diff --git a/examples/powertools-examples-core-utilities/terraform/src/main/resources/log4j2.xml b/examples/powertools-examples-core-utilities/terraform/src/main/resources/log4j2.xml new file mode 100644 index 000000000..0cc0953f0 --- /dev/null +++ b/examples/powertools-examples-core-utilities/terraform/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + <Root level="info"> + <AppenderRef ref="JsonAppender"/> + </Root> + </Loggers> +</Configuration> diff --git a/examples/powertools-examples-idempotency/sam-graalvm/Dockerfile b/examples/powertools-examples-idempotency/sam-graalvm/Dockerfile new file mode 100644 index 000000000..dac9390e5 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/Dockerfile @@ -0,0 +1,14 @@ +# Use the official AWS SAM base image for Java 21 +FROM public.ecr.aws/sam/build-java21@sha256:a5554d68374e19450c6c88448516ac95a9acedc779f318040f5c230134b4e461 + +# Install GraalVM dependencies +RUN curl -4 -L curl https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz | tar -xvz +RUN mv graalvm-jdk-21.* /usr/lib/graalvm + +# Make native image and mvn available on CLI +RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image +RUN ln -s /usr/lib/maven/bin/mvn /usr/bin/mvn + +# Set GraalVM as default +ENV JAVA_HOME=/usr/lib/graalvm +ENV PATH=/usr/lib/graalvm/bin:$PATH diff --git a/examples/powertools-examples-idempotency/sam-graalvm/Makefile b/examples/powertools-examples-idempotency/sam-graalvm/Makefile new file mode 100644 index 000000000..6fb4e537c --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/Makefile @@ -0,0 +1,5 @@ +build-IdempotencyFunction: + chmod +x target/hello-world + cp target/hello-world $(ARTIFACTS_DIR) # (ARTIFACTS_DIR --> https://github.com/aws/aws-lambda-builders/blob/develop/aws_lambda_builders/workflows/custom_make/DESIGN.md#implementation) + chmod +x src/main/config/bootstrap + cp src/main/config/bootstrap $(ARTIFACTS_DIR) diff --git a/examples/powertools-examples-idempotency/sam-graalvm/README.md b/examples/powertools-examples-idempotency/sam-graalvm/README.md new file mode 100644 index 000000000..1c44bd791 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/README.md @@ -0,0 +1,61 @@ +# Powertools for AWS Lambda (Java) - Idempotency Example with SAM on GraalVM + +This project contains an example of a Lambda function using the idempotency module of Powertools for AWS Lambda (Java). For more information on this module, please refer to the [documentation](https://docs.powertools.aws.dev/lambda-java/utilities/idempotency/). + +The example exposes a HTTP POST endpoint. When the user sends the address of a webpage to it, the endpoint fetches the contents of the URL and returns them to the user. + +Have a look at [App.java](src/main/java/helloworld/App.java) for the full details. + +## Build the sample application + +> [!NOTE] +> Building AWS Lambda packages on macOS (ARM64/Intel) for deployment on AWS Lambda (Linux x86_64 or ARM64) will result in incompatible binary dependencies that cause import errors at runtime. + +Choose the appropriate build method based on your operating system: + +### Build locally using Docker + +Recommended for macOS and Windows users: Cross-compile using Docker to match target platform of Lambda: + +```shell +docker build --platform linux/amd64 . -t powertools-examples-idempotency-sam-graalvm +docker run --platform linux/amd64 -it -v `pwd`:`pwd` -w `pwd` -v ~/.m2:/root/.m2 powertools-examples-idempotency-sam-graalvm mvn clean -Pnative-image package -DskipTests +sam build --use-container --build-image powertools-examples-idempotency-sam-graalvm +``` + +**Note**: The Docker run command mounts your local Maven cache (`~/.m2`) and builds the native binary with SNAPSHOT support, then SAM packages the pre-built binary. + +### Build on native OS + +For Linux users with GraalVM installed: + +```shell +export JAVA_HOME=<path to GraalVM> +mvn clean -Pnative-image package -DskipTests +sam build +``` + +## Deploy the sample application + +```shell +sam deploy +``` + +This sample is based on Serverless Application Model (SAM). To deploy it, check out the instructions for getting started with SAM in [the examples directory](../../README.md) + +## Test the application + +```bash +curl -X POST https://[REST-API-ID].execute-api.[REGION].amazonaws.com/Prod/helloidem/ -H "Content-Type: application/json" -d '{"address": "https://checkip.amazonaws.com"}' +``` + +this should return the contents of the webpage, for instance: + +```json +{ "message": "hello world", "location": "123.123.123.1" } +``` + +- First call will execute the handleRequest normally, and store the response in the idempotency table (Look into DynamoDB) +- Second call (and next ones) will retrieve from the cache (if cache is enabled, which is by default) or from the store, the handler won't be called. Until the expiration happens (by default 1 hour). + +Check out [App.java](src/main/java/helloworld/App.java) to see how it works! diff --git a/examples/powertools-examples-idempotency/sam-graalvm/pom.xml b/examples/powertools-examples-idempotency/sam-graalvm/pom.xml new file mode 100644 index 000000000..0536951aa --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/pom.xml @@ -0,0 +1,167 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-idempotency-sam-graalvm</artifactId> + <packaging>jar</packaging> + <name>Powertools for AWS Lambda (Java) - Examples - Idempotency GraalVM</name> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <aspectj.version>1.9.20.1</aspectj.version> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-dynamodb</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <version>3.16.1</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + <version>2.8.7</version> + </dependency> + + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-core</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>3.6.1</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <transformers> + <transformer + implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer" /> + </transformers> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId> + <version>0.2.0</version> + </dependency> + </dependencies> + </plugin> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>build-native</id> + <goals> + <goal>build</goal> + </goals> + <phase>package</phase> + </execution> + </executions> + <configuration> + <imageName>hello-world</imageName> + <mainClass>com.amazonaws.services.lambda.runtime.api.client.AWSLambda</mainClass> + <buildArgs> + <!-- required for AWS Lambda Runtime Interface Client --> + <arg>--enable-url-protocols=http,https</arg> + <arg>--add-opens java.base/java.util=ALL-UNNAMED</arg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/examples/powertools-examples-idempotency/sam-graalvm/src/main/config/bootstrap b/examples/powertools-examples-idempotency/sam-graalvm/src/main/config/bootstrap new file mode 100644 index 000000000..8e7928cd3 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/src/main/config/bootstrap @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +./hello-world $_HANDLER \ No newline at end of file diff --git a/examples/powertools-examples-idempotency/sam-graalvm/src/main/java/helloworld/App.java b/examples/powertools-examples-idempotency/sam-graalvm/src/main/java/helloworld/App.java new file mode 100644 index 000000000..ebe9fac22 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/src/main/java/helloworld/App.java @@ -0,0 +1,142 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.persistence.dynamodb.DynamoDBPersistenceStore; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.tracing.Tracing; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger log = LoggerFactory.getLogger(App.class); + + public App() { + this(null); + } + + public App(DynamoDbClient client) { + Idempotency.config().withConfig( + IdempotencyConfig.builder() + .withEventKeyJMESPath("powertools_json(body).address") + .withResponseHook((responseData, dataRecord) -> { + if (responseData instanceof APIGatewayProxyResponseEvent) { + APIGatewayProxyResponseEvent proxyResponse = (APIGatewayProxyResponseEvent) responseData; + final Map<String, String> headers = new HashMap<>(); + headers.putAll(proxyResponse.getHeaders()); + headers.put("x-idempotency-response", "true"); + headers.put("x-idempotency-expiration", + String.valueOf(dataRecord.getExpiryTimestamp())); + + proxyResponse.setHeaders(headers); + + return proxyResponse; + } + + return responseData; + }) + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withDynamoDbClient(client) + .withTableName(System.getenv("IDEMPOTENCY_TABLE")) + .build()) + .configure(); + } + + /** + * This is your Lambda event handler. It accepts HTTP POST requests from API gateway and returns the contents of the + * given URL. Requests are made idempotent + * by the idempotency library, and results are cached for the default 1h expiry time. + * <p> + * You can test the endpoint like this: + * + * <pre> + * curl -X POST https://[REST-API-ID].execute-api.[REGION].amazonaws.com/Prod/helloidem/ -H "Content-Type: application/json" -d '{"address": "https://checkip.amazonaws.com"}' + * </pre> + * <ul> + * <li>First call will execute the handleRequest normally, and store the response in the idempotency table (Look + * into DynamoDB)</li> + * <li>Second call (and next ones) will retrieve from the cache (if cache is enabled, which is by default) or from + * the store, the handler won't be called. Until the expiration happens (by default 1 hour).</li> + * </ul> + */ + @Idempotent // The magic is here! + @Logging(logEvent = true) + @Tracing + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + Map<String, String> headers = new HashMap<>(); + + headers.put("Content-Type", "application/json"); + headers.put("Access-Control-Allow-Origin", "*"); + headers.put("Access-Control-Allow-Methods", "GET, OPTIONS"); + headers.put("Access-Control-Allow-Headers", "*"); + + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() + .withHeaders(headers); + try { + // Read the 'address' field from the JSON post body + String address = JsonConfig.get().getObjectMapper().readTree(input.getBody()).get("address").asText(); + final String pageContents = this.getPageContents(address); + String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); + + log.info("ip is {}", pageContents); + return response + .withStatusCode(200) + .withBody(output); + + } catch (IOException e) { + return response + .withBody("{}") + .withStatusCode(500); + } + } + + /** + * Helper to retrieve the contents of the given URL and return them as a string. + * <p> + * We could also put the @Idempotent annotation here if we only wanted this sub-operation to be idempotent. Putting + * it on the handler, however, reduces total execution time and saves us time! + * + * @param address + * The URL to fetch + * @return The contents of the given URL + * @throws IOException + */ + @Tracing + private String getPageContents(String address) throws IOException { + URL url = new URL(address); + try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } + } +} diff --git a/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..b60cce0ea --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,30 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..e97baa294 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,44 @@ +[ + { + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields":[{"name":"logger"}] + }, + { + "name":"java.lang.Void", + "methods":[{"name":"<init>","parameterTypes":[] }] + }, + { + "name":"java.util.Collections$UnmodifiableMap", + "fields":[{"name":"m"}] + }, + { + "name":"jdk.internal.module.IllegalAccessLogger", + "fields":[{"name":"logger"}] + }, + { + "name":"sun.misc.Unsafe", + "fields":[{"name":"theUnsafe"}] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/reflect-config.json b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/reflect-config.json new file mode 100644 index 000000000..d01f93780 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/reflect-config.json @@ -0,0 +1,11 @@ +[ + { + "name": "helloworld.App", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/resource-config.json b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/log4j2.xml b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/log4j2.xml new file mode 100644 index 000000000..5dede7b58 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender" /> + </Logger> + <Root level="info"> + <AppenderRef ref="JsonAppender" /> + </Root> + </Loggers> +</Configuration> diff --git a/examples/powertools-examples-idempotency/sam-graalvm/template.yaml b/examples/powertools-examples-idempotency/sam-graalvm/template.yaml new file mode 100644 index 000000000..5ffd70255 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam-graalvm/template.yaml @@ -0,0 +1,60 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: > + Idempotency demo with GraalVM + +Globals: + Function: + Timeout: 20 + MemorySize: 512 + Tracing: Active + Environment: + Variables: + POWERTOOLS_LOG_LEVEL: INFO + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_LOGGER_LOG_EVENT: true + +Resources: + IdempotencyTable: + Type: AWS::DynamoDB::Table + Properties: + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + TimeToLiveSpecification: + AttributeName: expiration + Enabled: true + BillingMode: PAY_PER_REQUEST + + IdempotencyFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: helloworld.App::handleRequest + Runtime: provided.al2023 + MemorySize: 512 + Tracing: Active + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref IdempotencyTable + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: idempotency + IDEMPOTENCY_TABLE: !Ref IdempotencyTable + Events: + HelloWorld: + Type: Api + Properties: + Path: /helloidem + Method: post + +Outputs: + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Idempotent function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/helloidem/" + HelloWorldFunction: + Description: "Idempotent Lambda Function ARN" + Value: !GetAtt IdempotencyFunction.Arn diff --git a/examples/powertools-examples-idempotency/sam/README.md b/examples/powertools-examples-idempotency/sam/README.md new file mode 100644 index 000000000..6f6022054 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam/README.md @@ -0,0 +1,22 @@ +# Powertools for AWS Lambda (Java) - Idempotency Example + +This project contains an example of Lambda function using the idempotency module of Powertools for AWS Lambda (Java). For more information on this module, please refer to the [documentation](https://docs.powertools.aws.dev/lambda-java/utilities/idempotency/). +The example exposes a HTTP POST endpoint. When the user sends the address of a webpage to it, the endpoint fetches the contents of the URL and returns them to the user: + +## Deploy the sample application + +This sample is based on Serverless Application Model (SAM). To deploy it, check out the instructions for getting +started with SAM in [the examples directory](../README.md) + +## Test the application + +```bash + curl -X POST https://[REST-API-ID].execute-api.[REGION].amazonaws.com/Prod/helloidem/ -H "Content-Type: application/json" -d '{"address": "https://checkip.amazonaws.com"}' +``` + +this should return the contents of the webpage, for instance: +```json +{ "message": "hello world", "location": "123.123.123.1" } +``` + +Check out [App.java](src/main/java/helloworld/App.java) to see how it works! diff --git a/examples/powertools-examples-idempotency/sam/pom.xml b/examples/powertools-examples-idempotency/sam/pom.xml new file mode 100644 index 000000000..22d6a9c81 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam/pom.xml @@ -0,0 +1,144 @@ +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-idempotency</artifactId> + <packaging>jar</packaging> + <name>Powertools for AWS Lambda (Java) - Examples - Idempotency</name> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <aspectj.version>1.9.20.1</aspectj.version> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-dynamodb</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <version>3.16.1</version> + </dependency> + + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-core</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>3.6.1</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <transformers> + <transformer + implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer" /> + </transformers> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId> + <version>0.2.0</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/examples/powertools-examples-idempotency/sam/src/main/java/helloworld/App.java b/examples/powertools-examples-idempotency/sam/src/main/java/helloworld/App.java new file mode 100644 index 000000000..ebe9fac22 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam/src/main/java/helloworld/App.java @@ -0,0 +1,142 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package helloworld; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.persistence.dynamodb.DynamoDBPersistenceStore; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.tracing.Tracing; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger log = LoggerFactory.getLogger(App.class); + + public App() { + this(null); + } + + public App(DynamoDbClient client) { + Idempotency.config().withConfig( + IdempotencyConfig.builder() + .withEventKeyJMESPath("powertools_json(body).address") + .withResponseHook((responseData, dataRecord) -> { + if (responseData instanceof APIGatewayProxyResponseEvent) { + APIGatewayProxyResponseEvent proxyResponse = (APIGatewayProxyResponseEvent) responseData; + final Map<String, String> headers = new HashMap<>(); + headers.putAll(proxyResponse.getHeaders()); + headers.put("x-idempotency-response", "true"); + headers.put("x-idempotency-expiration", + String.valueOf(dataRecord.getExpiryTimestamp())); + + proxyResponse.setHeaders(headers); + + return proxyResponse; + } + + return responseData; + }) + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withDynamoDbClient(client) + .withTableName(System.getenv("IDEMPOTENCY_TABLE")) + .build()) + .configure(); + } + + /** + * This is your Lambda event handler. It accepts HTTP POST requests from API gateway and returns the contents of the + * given URL. Requests are made idempotent + * by the idempotency library, and results are cached for the default 1h expiry time. + * <p> + * You can test the endpoint like this: + * + * <pre> + * curl -X POST https://[REST-API-ID].execute-api.[REGION].amazonaws.com/Prod/helloidem/ -H "Content-Type: application/json" -d '{"address": "https://checkip.amazonaws.com"}' + * </pre> + * <ul> + * <li>First call will execute the handleRequest normally, and store the response in the idempotency table (Look + * into DynamoDB)</li> + * <li>Second call (and next ones) will retrieve from the cache (if cache is enabled, which is by default) or from + * the store, the handler won't be called. Until the expiration happens (by default 1 hour).</li> + * </ul> + */ + @Idempotent // The magic is here! + @Logging(logEvent = true) + @Tracing + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + Map<String, String> headers = new HashMap<>(); + + headers.put("Content-Type", "application/json"); + headers.put("Access-Control-Allow-Origin", "*"); + headers.put("Access-Control-Allow-Methods", "GET, OPTIONS"); + headers.put("Access-Control-Allow-Headers", "*"); + + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() + .withHeaders(headers); + try { + // Read the 'address' field from the JSON post body + String address = JsonConfig.get().getObjectMapper().readTree(input.getBody()).get("address").asText(); + final String pageContents = this.getPageContents(address); + String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); + + log.info("ip is {}", pageContents); + return response + .withStatusCode(200) + .withBody(output); + + } catch (IOException e) { + return response + .withBody("{}") + .withStatusCode(500); + } + } + + /** + * Helper to retrieve the contents of the given URL and return them as a string. + * <p> + * We could also put the @Idempotent annotation here if we only wanted this sub-operation to be idempotent. Putting + * it on the handler, however, reduces total execution time and saves us time! + * + * @param address + * The URL to fetch + * @return The contents of the given URL + * @throws IOException + */ + @Tracing + private String getPageContents(String address) throws IOException { + URL url = new URL(address); + try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } + } +} diff --git a/examples/powertools-examples-idempotency/sam/src/main/resources/log4j2.xml b/examples/powertools-examples-idempotency/sam/src/main/resources/log4j2.xml new file mode 100644 index 000000000..5dede7b58 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender" /> + </Logger> + <Root level="info"> + <AppenderRef ref="JsonAppender" /> + </Root> + </Loggers> +</Configuration> diff --git a/examples/powertools-examples-idempotency/sam/template.yaml b/examples/powertools-examples-idempotency/sam/template.yaml new file mode 100644 index 000000000..2d5e5bc56 --- /dev/null +++ b/examples/powertools-examples-idempotency/sam/template.yaml @@ -0,0 +1,59 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + Idempotency demo + +Globals: + Function: + Timeout: 20 + Runtime: java11 + MemorySize: 512 + Tracing: Active + Environment: + Variables: + POWERTOOLS_LOG_LEVEL: INFO + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_LOGGER_LOG_EVENT: true + +Resources: + IdempotencyTable: + Type: AWS::DynamoDB::Table + Properties: + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + TimeToLiveSpecification: + AttributeName: expiration + Enabled: true + BillingMode: PAY_PER_REQUEST + + IdempotencyFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: helloworld.App::handleRequest + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref IdempotencyTable + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: idempotency + IDEMPOTENCY_TABLE: !Ref IdempotencyTable + Events: + HelloWorld: + Type: Api + Properties: + Path: /helloidem + Method: post + +Outputs: + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Idempotent function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/helloidem/" + HelloWorldFunction: + Description: "Idempotent Lambda Function ARN" + Value: !GetAtt IdempotencyFunction.Arn + diff --git a/examples/powertools-examples-kafka/README.md b/examples/powertools-examples-kafka/README.md new file mode 100644 index 000000000..76cd81cb9 --- /dev/null +++ b/examples/powertools-examples-kafka/README.md @@ -0,0 +1,77 @@ +# Powertools for AWS Lambda (Java) - Kafka Example + +This project demonstrates how to use Powertools for AWS Lambda (Java) to deserialize Kafka Lambda events directly into strongly typed Kafka ConsumerRecords<K, V> using different serialization formats. + +## Overview + +The example showcases automatic deserialization of Kafka Lambda events into ConsumerRecords using three formats: +- JSON - Using standard JSON serialization +- Avro - Using Apache Avro schema-based serialization +- Protobuf - Using Google Protocol Buffers serialization + +Each format has its own Lambda function handler that demonstrates how to use the `@Deserialization` annotation with the appropriate `DeserializationType`, eliminating the need to handle complex deserialization logic manually. + +## Build and Deploy + +### Prerequisites +- [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +- Java 11+ +- Maven + +### Build + +```bash +# Build the application +sam build +``` + +### Deploy + +```bash +# Deploy the application to AWS +sam deploy --guided +``` + +During the guided deployment, you'll be prompted to provide values for required parameters. After deployment, SAM will output the ARNs of the deployed Lambda functions. + +### Build with Different Serialization Formats + +The project includes Maven profiles to build with different serialization formats: + +```bash +# Build with JSON only (no Avro or Protobuf) +mvn clean package -P base + +# Build with Avro only +mvn clean package -P avro-only + +# Build with Protobuf only +mvn clean package -P protobuf-only + +# Build with all formats (default) +mvn clean package -P full +``` + +## Testing + +The `events` directory contains sample events for each serialization format: +- `kafka-json-event.json` - Sample event with JSON-serialized products +- `kafka-avro-event.json` - Sample event with Avro-serialized products +- `kafka-protobuf-event.json` - Sample event with Protobuf-serialized products + +You can use these events to test the Lambda functions: + +```bash +# Test the JSON deserialization function +sam local invoke JsonDeserializationFunction --event events/kafka-json-event.json + +# Test the Avro deserialization function +sam local invoke AvroDeserializationFunction --event events/kafka-avro-event.json + +# Test the Protobuf deserialization function +sam local invoke ProtobufDeserializationFunction --event events/kafka-protobuf-event.json +``` + +## Sample Generator Tool + +The project includes a tool to generate sample JSON, Avro, and Protobuf serialized data. See the [tools/README.md](tools/README.md) for more information. \ No newline at end of file diff --git a/examples/powertools-examples-kafka/events/kafka-avro-event.json b/examples/powertools-examples-kafka/events/kafka-avro-event.json new file mode 100644 index 000000000..8d6ef2210 --- /dev/null +++ b/examples/powertools-examples-kafka/events/kafka-avro-event.json @@ -0,0 +1,51 @@ +{ + "eventSource": "aws:kafka", + "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-4", + "bootstrapServers": "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092", + "records": { + "mytopic-0": [ + { + "topic": "mytopic", + "partition": 0, + "offset": 15, + "timestamp": 1545084650987, + "timestampType": "CREATE_TIME", + "key": "NDI=", + "value": "0g8MTGFwdG9wUrgehes/j0A=", + "headers": [ + { + "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] + } + ] + }, + { + "topic": "mytopic", + "partition": 0, + "offset": 16, + "timestamp": 1545084650988, + "timestampType": "CREATE_TIME", + "key": "NDI=", + "value": "1A8UU21hcnRwaG9uZVK4HoXrv4JA", + "headers": [ + { + "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] + } + ] + }, + { + "topic": "mytopic", + "partition": 0, + "offset": 17, + "timestamp": 1545084650989, + "timestampType": "CREATE_TIME", + "key": null, + "value": "1g8USGVhZHBob25lc0jhehSuv2JA", + "headers": [ + { + "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] + } + ] + } + ] + } +} diff --git a/examples/powertools-examples-kafka/events/kafka-json-event.json b/examples/powertools-examples-kafka/events/kafka-json-event.json new file mode 100644 index 000000000..7ffb9a3a6 --- /dev/null +++ b/examples/powertools-examples-kafka/events/kafka-json-event.json @@ -0,0 +1,51 @@ +{ + "eventSource": "aws:kafka", + "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-4", + "bootstrapServers": "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092", + "records": { + "mytopic-0": [ + { + "topic": "mytopic", + "partition": 0, + "offset": 15, + "timestamp": 1545084650987, + "timestampType": "CREATE_TIME", + "key": "NDI=", + "value": "eyJwcmljZSI6OTk5Ljk5LCJuYW1lIjoiTGFwdG9wIiwiaWQiOjEwMDF9", + "headers": [ + { + "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] + } + ] + }, + { + "topic": "mytopic", + "partition": 0, + "offset": 15, + "timestamp": 1545084650987, + "timestampType": "CREATE_TIME", + "key": "NDI=", + "value": "eyJwcmljZSI6NTk5Ljk5LCJuYW1lIjoiU21hcnRwaG9uZSIsImlkIjoxMDAyfQ==", + "headers": [ + { + "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] + } + ] + }, + { + "topic": "mytopic", + "partition": 0, + "offset": 15, + "timestamp": 1545084650987, + "timestampType": "CREATE_TIME", + "key": null, + "value": "eyJwcmljZSI6MTQ5Ljk5LCJuYW1lIjoiSGVhZHBob25lcyIsImlkIjoxMDAzfQ==", + "headers": [ + { + "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] + } + ] + } + ] + } +} diff --git a/examples/powertools-examples-kafka/events/kafka-protobuf-event.json b/examples/powertools-examples-kafka/events/kafka-protobuf-event.json new file mode 100644 index 000000000..6f5ec58ae --- /dev/null +++ b/examples/powertools-examples-kafka/events/kafka-protobuf-event.json @@ -0,0 +1,77 @@ +{ + "eventSource": "aws:kafka", + "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-4", + "bootstrapServers": "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092", + "records": { + "mytopic-0": [ + { + "topic": "mytopic", + "partition": 0, + "offset": 15, + "timestamp": 1545084650987, + "timestampType": "CREATE_TIME", + "key": "NDI=", + "value": "COkHEgZMYXB0b3AZUrgehes/j0A=", + "headers": [ + { + "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] + } + ] + }, + { + "topic": "mytopic", + "partition": 0, + "offset": 16, + "timestamp": 1545084650988, + "timestampType": "CREATE_TIME", + "key": "NDI=", + "value": "AAjpBxIGTGFwdG9wGVK4HoXrP49A", + "headers": [ + { + "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] + } + ], + "valueSchemaMetadata": { + "schemaId": "123", + "dataFormat": "PROTOBUF" + } + }, + { + "topic": "mytopic", + "partition": 0, + "offset": 17, + "timestamp": 1545084650989, + "timestampType": "CREATE_TIME", + "key": null, + "value": "BAIACOkHEgZMYXB0b3AZUrgehes/j0A=", + "headers": [ + { + "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] + } + ], + "valueSchemaMetadata": { + "schemaId": "456", + "dataFormat": "PROTOBUF" + } + }, + { + "topic": "mytopic", + "partition": 0, + "offset": 18, + "timestamp": 1545084650990, + "timestampType": "CREATE_TIME", + "key": "NDI=", + "value": "AQjpBxIGTGFwdG9wGVK4HoXrP49A", + "headers": [ + { + "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] + } + ], + "valueSchemaMetadata": { + "schemaId": "12345678-1234-1234-1234-123456789012", + "dataFormat": "PROTOBUF" + } + } + ] + } +} diff --git a/examples/powertools-examples-kafka/pom.xml b/examples/powertools-examples-kafka/pom.xml new file mode 100644 index 000000000..d152f46c0 --- /dev/null +++ b/examples/powertools-examples-kafka/pom.xml @@ -0,0 +1,232 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-kafka</artifactId> + <packaging>jar</packaging> + <name>Powertools for AWS Lambda (Java) - Examples - Kafka</name> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <aspectj.version>1.9.20.1</aspectj.version> + <avro.version>1.12.1</avro.version> + <protobuf.version>4.33.1</protobuf.version> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-kafka</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.kafka</groupId> + <artifactId>kafka-clients</artifactId> + <version>4.1.1</version> <!-- Supports >= 3.0.0 --> + </dependency> + <dependency> + <groupId>org.apache.avro</groupId> + <artifactId>avro</artifactId> + <version>${avro.version}</version> + </dependency> + <dependency> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-java</artifactId> + <version>${protobuf.version}</version> + </dependency> + + <!-- Basic logging setup --> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>3.6.1</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <transformers> + <transformer + implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer" /> + </transformers> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId> + <version>0.2.0</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <!-- Generate Avro classes from schema --> + <plugin> + <groupId>org.apache.avro</groupId> + <artifactId>avro-maven-plugin</artifactId> + <version>${avro.version}</version> + <executions> + <execution> + <phase>generate-sources</phase> + <goals> + <goal>schema</goal> + </goals> + <configuration> + <sourceDirectory>${project.basedir}/src/main/avro/</sourceDirectory> + <outputDirectory>${project.basedir}/src/main/java/</outputDirectory> + <stringType>String</stringType> + </configuration> + </execution> + </executions> + </plugin> + <!-- Generate Protobuf classes from schema --> + <plugin> + <groupId>io.github.ascopes</groupId> + <artifactId>protobuf-maven-plugin</artifactId> + <version>3.10.3</version> + <executions> + <execution> + <goals> + <goal>generate</goal> + </goals> + <phase>generate-sources</phase> + <configuration> + <protocVersion>${protobuf.version}</protocVersion> + <sourceDirectories> + <sourceDirectory>${project.basedir}/src/main/proto</sourceDirectory> + </sourceDirectories> + <outputDirectory>${project.basedir}/src/main/java</outputDirectory> + <clearOutputDirectory>false</clearOutputDirectory> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <profiles> + <!-- Base profile without Avro or Protobuf (compatible with JSON only) --> + <profile> + <id>base</id> + <properties> + <active.profile>base</active.profile> + </properties> + <dependencies> + <!-- Exclude both Avro and Protobuf --> + <dependency> + <groupId>org.apache.avro</groupId> + <artifactId>avro</artifactId> + <version>${avro.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-java</artifactId> + <version>${protobuf.version}</version> + <scope>provided</scope> + </dependency> + </dependencies> + </profile> + + <!-- Profile with only Avro --> + <profile> + <id>avro-only</id> + <properties> + <active.profile>avro-only</active.profile> + </properties> + <dependencies> + <dependency> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-java</artifactId> + <version>${protobuf.version}</version> + <scope>provided</scope> + </dependency> + </dependencies> + </profile> + + <!-- Profile with only Protobuf --> + <profile> + <id>protobuf-only</id> + <properties> + <active.profile>protobuf-only</active.profile> + </properties> + <dependencies> + <dependency> + <groupId>org.apache.avro</groupId> + <artifactId>avro</artifactId> + <version>${avro.version}</version> + <scope>provided</scope> + </dependency> + </dependencies> + </profile> + + <!-- Profile with both Avro and Protobuf (default) --> + <profile> + <id>full</id> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + <properties> + <active.profile>full</active.profile> + </properties> + </profile> + </profiles> +</project> diff --git a/examples/powertools-examples-kafka/src/main/avro/AvroProduct.avsc b/examples/powertools-examples-kafka/src/main/avro/AvroProduct.avsc new file mode 100644 index 000000000..7155857ea --- /dev/null +++ b/examples/powertools-examples-kafka/src/main/avro/AvroProduct.avsc @@ -0,0 +1,10 @@ +{ + "namespace": "org.demo.kafka.avro", + "type": "record", + "name": "AvroProduct", + "fields": [ + {"name": "id", "type": "int"}, + {"name": "name", "type": "string"}, + {"name": "price", "type": "double"} + ] +} diff --git a/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/AvroDeserializationFunction.java b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/AvroDeserializationFunction.java new file mode 100644 index 000000000..72f383eef --- /dev/null +++ b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/AvroDeserializationFunction.java @@ -0,0 +1,37 @@ +package org.demo.kafka; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.demo.kafka.avro.AvroProduct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.kafka.Deserialization; +import software.amazon.lambda.powertools.kafka.DeserializationType; +import software.amazon.lambda.powertools.logging.Logging; + +public class AvroDeserializationFunction implements RequestHandler<ConsumerRecords<String, AvroProduct>, String> { + + private static final Logger LOGGER = LoggerFactory.getLogger(AvroDeserializationFunction.class); + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_AVRO) + public String handleRequest(ConsumerRecords<String, AvroProduct> records, Context context) { + for (ConsumerRecord<String, AvroProduct> consumerRecord : records) { + LOGGER.info("ConsumerRecord: {}", consumerRecord); + + AvroProduct product = consumerRecord.value(); + LOGGER.info("AvroProduct: {}", product); + + String key = consumerRecord.key(); + LOGGER.info("Key: {}", key); + } + + return "OK"; + } + +} diff --git a/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/JsonDeserializationFunction.java b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/JsonDeserializationFunction.java new file mode 100644 index 000000000..c1d7f13ae --- /dev/null +++ b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/JsonDeserializationFunction.java @@ -0,0 +1,35 @@ +package org.demo.kafka; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.kafka.Deserialization; +import software.amazon.lambda.powertools.kafka.DeserializationType; +import software.amazon.lambda.powertools.logging.Logging; + +public class JsonDeserializationFunction implements RequestHandler<ConsumerRecords<String, Product>, String> { + + private static final Logger LOGGER = LoggerFactory.getLogger(JsonDeserializationFunction.class); + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_JSON) + public String handleRequest(ConsumerRecords<String, Product> consumerRecords, Context context) { + for (ConsumerRecord<String, Product> consumerRecord : consumerRecords) { + LOGGER.info("ConsumerRecord: {}", consumerRecord); + + Product product = consumerRecord.value(); + LOGGER.info("Product: {}", product); + + String key = consumerRecord.key(); + LOGGER.info("Key: {}", key); + } + + return "OK"; + } +} diff --git a/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/Product.java b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/Product.java new file mode 100644 index 000000000..c6166090c --- /dev/null +++ b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/Product.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.kafka; + +public class Product { + private long id; + private String name; + private double price; + + public Product() { + } + + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + @Override + public String toString() { + return "Product{" + + "id=" + id + + ", name='" + name + '\'' + + ", price=" + price + + '}'; + } +} diff --git a/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/ProtobufDeserializationFunction.java b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/ProtobufDeserializationFunction.java new file mode 100644 index 000000000..1978e8890 --- /dev/null +++ b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/ProtobufDeserializationFunction.java @@ -0,0 +1,38 @@ +package org.demo.kafka; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.demo.kafka.protobuf.ProtobufProduct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.kafka.Deserialization; +import software.amazon.lambda.powertools.kafka.DeserializationType; +import software.amazon.lambda.powertools.logging.Logging; + +public class ProtobufDeserializationFunction + implements RequestHandler<ConsumerRecords<String, ProtobufProduct>, String> { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProtobufDeserializationFunction.class); + + @Override + @Logging + @Deserialization(type = DeserializationType.KAFKA_PROTOBUF) + public String handleRequest(ConsumerRecords<String, ProtobufProduct> records, Context context) { + for (ConsumerRecord<String, ProtobufProduct> consumerRecord : records) { + LOGGER.info("ConsumerRecord: {}", consumerRecord); + + ProtobufProduct product = consumerRecord.value(); + LOGGER.info("ProtobufProduct: {}", product); + + String key = consumerRecord.key(); + LOGGER.info("Key: {}", key); + } + + return "OK"; + } + +} diff --git a/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/avro/AvroProduct.java b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/avro/AvroProduct.java new file mode 100644 index 000000000..fad7e2fbf --- /dev/null +++ b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/avro/AvroProduct.java @@ -0,0 +1,476 @@ +/** + * Autogenerated by Avro + * + * DO NOT EDIT DIRECTLY + */ +package org.demo.kafka.avro; + +import org.apache.avro.specific.SpecificData; +import org.apache.avro.util.Utf8; +import org.apache.avro.message.BinaryMessageEncoder; +import org.apache.avro.message.BinaryMessageDecoder; +import org.apache.avro.message.SchemaStore; + +@org.apache.avro.specific.AvroGenerated +public class AvroProduct extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord { + private static final long serialVersionUID = -2929699301240218341L; + + + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"AvroProduct\",\"namespace\":\"org.demo.kafka.avro\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"name\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"price\",\"type\":\"double\"}]}"); + public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; } + + private static final SpecificData MODEL$ = new SpecificData(); + + private static final BinaryMessageEncoder<AvroProduct> ENCODER = + new BinaryMessageEncoder<>(MODEL$, SCHEMA$); + + private static final BinaryMessageDecoder<AvroProduct> DECODER = + new BinaryMessageDecoder<>(MODEL$, SCHEMA$); + + /** + * Return the BinaryMessageEncoder instance used by this class. + * @return the message encoder used by this class + */ + public static BinaryMessageEncoder<AvroProduct> getEncoder() { + return ENCODER; + } + + /** + * Return the BinaryMessageDecoder instance used by this class. + * @return the message decoder used by this class + */ + public static BinaryMessageDecoder<AvroProduct> getDecoder() { + return DECODER; + } + + /** + * Create a new BinaryMessageDecoder instance for this class that uses the specified {@link SchemaStore}. + * @param resolver a {@link SchemaStore} used to find schemas by fingerprint + * @return a BinaryMessageDecoder instance for this class backed by the given SchemaStore + */ + public static BinaryMessageDecoder<AvroProduct> createDecoder(SchemaStore resolver) { + return new BinaryMessageDecoder<>(MODEL$, SCHEMA$, resolver); + } + + /** + * Serializes this AvroProduct to a ByteBuffer. + * @return a buffer holding the serialized data for this instance + * @throws java.io.IOException if this instance could not be serialized + */ + public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException { + return ENCODER.encode(this); + } + + /** + * Deserializes a AvroProduct from a ByteBuffer. + * @param b a byte buffer holding serialized data for an instance of this class + * @return a AvroProduct instance decoded from the given buffer + * @throws java.io.IOException if the given bytes could not be deserialized into an instance of this class + */ + public static AvroProduct fromByteBuffer( + java.nio.ByteBuffer b) throws java.io.IOException { + return DECODER.decode(b); + } + + private int id; + private java.lang.String name; + private double price; + + /** + * Default constructor. Note that this does not initialize fields + * to their default values from the schema. If that is desired then + * one should use <code>newBuilder()</code>. + */ + public AvroProduct() {} + + /** + * All-args constructor. + * @param id The new value for id + * @param name The new value for name + * @param price The new value for price + */ + public AvroProduct(java.lang.Integer id, java.lang.String name, java.lang.Double price) { + this.id = id; + this.name = name; + this.price = price; + } + + @Override + public org.apache.avro.specific.SpecificData getSpecificData() { return MODEL$; } + + @Override + public org.apache.avro.Schema getSchema() { return SCHEMA$; } + + // Used by DatumWriter. Applications should not call. + @Override + public java.lang.Object get(int field$) { + switch (field$) { + case 0: return id; + case 1: return name; + case 2: return price; + default: throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + // Used by DatumReader. Applications should not call. + @Override + @SuppressWarnings(value="unchecked") + public void put(int field$, java.lang.Object value$) { + switch (field$) { + case 0: id = (java.lang.Integer)value$; break; + case 1: name = value$ != null ? value$.toString() : null; break; + case 2: price = (java.lang.Double)value$; break; + default: throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + /** + * Gets the value of the 'id' field. + * @return The value of the 'id' field. + */ + public int getId() { + return id; + } + + + /** + * Sets the value of the 'id' field. + * @param value the value to set. + */ + public void setId(int value) { + this.id = value; + } + + /** + * Gets the value of the 'name' field. + * @return The value of the 'name' field. + */ + public java.lang.String getName() { + return name; + } + + + /** + * Sets the value of the 'name' field. + * @param value the value to set. + */ + public void setName(java.lang.String value) { + this.name = value; + } + + /** + * Gets the value of the 'price' field. + * @return The value of the 'price' field. + */ + public double getPrice() { + return price; + } + + + /** + * Sets the value of the 'price' field. + * @param value the value to set. + */ + public void setPrice(double value) { + this.price = value; + } + + /** + * Creates a new AvroProduct RecordBuilder. + * @return A new AvroProduct RecordBuilder + */ + public static org.demo.kafka.avro.AvroProduct.Builder newBuilder() { + return new org.demo.kafka.avro.AvroProduct.Builder(); + } + + /** + * Creates a new AvroProduct RecordBuilder by copying an existing Builder. + * @param other The existing builder to copy. + * @return A new AvroProduct RecordBuilder + */ + public static org.demo.kafka.avro.AvroProduct.Builder newBuilder(org.demo.kafka.avro.AvroProduct.Builder other) { + if (other == null) { + return new org.demo.kafka.avro.AvroProduct.Builder(); + } else { + return new org.demo.kafka.avro.AvroProduct.Builder(other); + } + } + + /** + * Creates a new AvroProduct RecordBuilder by copying an existing AvroProduct instance. + * @param other The existing instance to copy. + * @return A new AvroProduct RecordBuilder + */ + public static org.demo.kafka.avro.AvroProduct.Builder newBuilder(org.demo.kafka.avro.AvroProduct other) { + if (other == null) { + return new org.demo.kafka.avro.AvroProduct.Builder(); + } else { + return new org.demo.kafka.avro.AvroProduct.Builder(other); + } + } + + /** + * RecordBuilder for AvroProduct instances. + */ + @org.apache.avro.specific.AvroGenerated + public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase<AvroProduct> + implements org.apache.avro.data.RecordBuilder<AvroProduct> { + + private int id; + private java.lang.String name; + private double price; + + /** Creates a new Builder */ + private Builder() { + super(SCHEMA$, MODEL$); + } + + /** + * Creates a Builder by copying an existing Builder. + * @param other The existing Builder to copy. + */ + private Builder(org.demo.kafka.avro.AvroProduct.Builder other) { + super(other); + if (isValidValue(fields()[0], other.id)) { + this.id = data().deepCopy(fields()[0].schema(), other.id); + fieldSetFlags()[0] = other.fieldSetFlags()[0]; + } + if (isValidValue(fields()[1], other.name)) { + this.name = data().deepCopy(fields()[1].schema(), other.name); + fieldSetFlags()[1] = other.fieldSetFlags()[1]; + } + if (isValidValue(fields()[2], other.price)) { + this.price = data().deepCopy(fields()[2].schema(), other.price); + fieldSetFlags()[2] = other.fieldSetFlags()[2]; + } + } + + /** + * Creates a Builder by copying an existing AvroProduct instance + * @param other The existing instance to copy. + */ + private Builder(org.demo.kafka.avro.AvroProduct other) { + super(SCHEMA$, MODEL$); + if (isValidValue(fields()[0], other.id)) { + this.id = data().deepCopy(fields()[0].schema(), other.id); + fieldSetFlags()[0] = true; + } + if (isValidValue(fields()[1], other.name)) { + this.name = data().deepCopy(fields()[1].schema(), other.name); + fieldSetFlags()[1] = true; + } + if (isValidValue(fields()[2], other.price)) { + this.price = data().deepCopy(fields()[2].schema(), other.price); + fieldSetFlags()[2] = true; + } + } + + /** + * Gets the value of the 'id' field. + * @return The value. + */ + public int getId() { + return id; + } + + + /** + * Sets the value of the 'id' field. + * @param value The value of 'id'. + * @return This builder. + */ + public org.demo.kafka.avro.AvroProduct.Builder setId(int value) { + validate(fields()[0], value); + this.id = value; + fieldSetFlags()[0] = true; + return this; + } + + /** + * Checks whether the 'id' field has been set. + * @return True if the 'id' field has been set, false otherwise. + */ + public boolean hasId() { + return fieldSetFlags()[0]; + } + + + /** + * Clears the value of the 'id' field. + * @return This builder. + */ + public org.demo.kafka.avro.AvroProduct.Builder clearId() { + fieldSetFlags()[0] = false; + return this; + } + + /** + * Gets the value of the 'name' field. + * @return The value. + */ + public java.lang.String getName() { + return name; + } + + + /** + * Sets the value of the 'name' field. + * @param value The value of 'name'. + * @return This builder. + */ + public org.demo.kafka.avro.AvroProduct.Builder setName(java.lang.String value) { + validate(fields()[1], value); + this.name = value; + fieldSetFlags()[1] = true; + return this; + } + + /** + * Checks whether the 'name' field has been set. + * @return True if the 'name' field has been set, false otherwise. + */ + public boolean hasName() { + return fieldSetFlags()[1]; + } + + + /** + * Clears the value of the 'name' field. + * @return This builder. + */ + public org.demo.kafka.avro.AvroProduct.Builder clearName() { + name = null; + fieldSetFlags()[1] = false; + return this; + } + + /** + * Gets the value of the 'price' field. + * @return The value. + */ + public double getPrice() { + return price; + } + + + /** + * Sets the value of the 'price' field. + * @param value The value of 'price'. + * @return This builder. + */ + public org.demo.kafka.avro.AvroProduct.Builder setPrice(double value) { + validate(fields()[2], value); + this.price = value; + fieldSetFlags()[2] = true; + return this; + } + + /** + * Checks whether the 'price' field has been set. + * @return True if the 'price' field has been set, false otherwise. + */ + public boolean hasPrice() { + return fieldSetFlags()[2]; + } + + + /** + * Clears the value of the 'price' field. + * @return This builder. + */ + public org.demo.kafka.avro.AvroProduct.Builder clearPrice() { + fieldSetFlags()[2] = false; + return this; + } + + @Override + @SuppressWarnings("unchecked") + public AvroProduct build() { + try { + AvroProduct record = new AvroProduct(); + record.id = fieldSetFlags()[0] ? this.id : (java.lang.Integer) defaultValue(fields()[0]); + record.name = fieldSetFlags()[1] ? this.name : (java.lang.String) defaultValue(fields()[1]); + record.price = fieldSetFlags()[2] ? this.price : (java.lang.Double) defaultValue(fields()[2]); + return record; + } catch (org.apache.avro.AvroMissingFieldException e) { + throw e; + } catch (java.lang.Exception e) { + throw new org.apache.avro.AvroRuntimeException(e); + } + } + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumWriter<AvroProduct> + WRITER$ = (org.apache.avro.io.DatumWriter<AvroProduct>)MODEL$.createDatumWriter(SCHEMA$); + + @Override public void writeExternal(java.io.ObjectOutput out) + throws java.io.IOException { + WRITER$.write(this, SpecificData.getEncoder(out)); + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumReader<AvroProduct> + READER$ = (org.apache.avro.io.DatumReader<AvroProduct>)MODEL$.createDatumReader(SCHEMA$); + + @Override public void readExternal(java.io.ObjectInput in) + throws java.io.IOException { + READER$.read(this, SpecificData.getDecoder(in)); + } + + @Override protected boolean hasCustomCoders() { return true; } + + @Override public void customEncode(org.apache.avro.io.Encoder out) + throws java.io.IOException + { + out.writeInt(this.id); + + out.writeString(this.name); + + out.writeDouble(this.price); + + } + + @Override public void customDecode(org.apache.avro.io.ResolvingDecoder in) + throws java.io.IOException + { + org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff(); + if (fieldOrder == null) { + this.id = in.readInt(); + + this.name = in.readString(); + + this.price = in.readDouble(); + + } else { + for (int i = 0; i < 3; i++) { + switch (fieldOrder[i].pos()) { + case 0: + this.id = in.readInt(); + break; + + case 1: + this.name = in.readString(); + break; + + case 2: + this.price = in.readDouble(); + break; + + default: + throw new java.io.IOException("Corrupt ResolvingDecoder."); + } + } + } + } +} + + + + + + + + + + diff --git a/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/protobuf/ProtobufProduct.java b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/protobuf/ProtobufProduct.java new file mode 100644 index 000000000..2bf5db844 --- /dev/null +++ b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/protobuf/ProtobufProduct.java @@ -0,0 +1,636 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: ProtobufProduct.proto +// Protobuf Java Version: 4.33.1 + +package org.demo.kafka.protobuf; + +/** + * Protobuf type {@code org.demo.kafka.protobuf.ProtobufProduct} + */ +@com.google.protobuf.Generated +public final class ProtobufProduct extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:org.demo.kafka.protobuf.ProtobufProduct) + ProtobufProductOrBuilder { +private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 33, + /* patch= */ 1, + /* suffix= */ "", + "ProtobufProduct"); + } + // Use ProtobufProduct.newBuilder() to construct. + private ProtobufProduct(com.google.protobuf.GeneratedMessage.Builder<?> builder) { + super(builder); + } + private ProtobufProduct() { + name_ = ""; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.demo.kafka.protobuf.ProtobufProductOuterClass.internal_static_org_demo_kafka_protobuf_ProtobufProduct_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.demo.kafka.protobuf.ProtobufProductOuterClass.internal_static_org_demo_kafka_protobuf_ProtobufProduct_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.demo.kafka.protobuf.ProtobufProduct.class, org.demo.kafka.protobuf.ProtobufProduct.Builder.class); + } + + public static final int ID_FIELD_NUMBER = 1; + private int id_ = 0; + /** + * <code>int32 id = 1;</code> + * @return The id. + */ + @java.lang.Override + public int getId() { + return id_; + } + + public static final int NAME_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private volatile java.lang.Object name_ = ""; + /** + * <code>string name = 2;</code> + * @return The name. + */ + @java.lang.Override + public java.lang.String getName() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + name_ = s; + return s; + } + } + /** + * <code>string name = 2;</code> + * @return The bytes for name. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int PRICE_FIELD_NUMBER = 3; + private double price_ = 0D; + /** + * <code>double price = 3;</code> + * @return The price. + */ + @java.lang.Override + public double getPrice() { + return price_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (id_ != 0) { + output.writeInt32(1, id_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(name_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 2, name_); + } + if (java.lang.Double.doubleToRawLongBits(price_) != 0) { + output.writeDouble(3, price_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (id_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, id_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(name_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(2, name_); + } + if (java.lang.Double.doubleToRawLongBits(price_) != 0) { + size += com.google.protobuf.CodedOutputStream + .computeDoubleSize(3, price_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.demo.kafka.protobuf.ProtobufProduct)) { + return super.equals(obj); + } + org.demo.kafka.protobuf.ProtobufProduct other = (org.demo.kafka.protobuf.ProtobufProduct) obj; + + if (getId() + != other.getId()) return false; + if (!getName() + .equals(other.getName())) return false; + if (java.lang.Double.doubleToLongBits(getPrice()) + != java.lang.Double.doubleToLongBits( + other.getPrice())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + ID_FIELD_NUMBER; + hash = (53 * hash) + getId(); + hash = (37 * hash) + NAME_FIELD_NUMBER; + hash = (53 * hash) + getName().hashCode(); + hash = (37 * hash) + PRICE_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + java.lang.Double.doubleToLongBits(getPrice())); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.demo.kafka.protobuf.ProtobufProduct parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.demo.kafka.protobuf.ProtobufProduct parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.demo.kafka.protobuf.ProtobufProduct prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code org.demo.kafka.protobuf.ProtobufProduct} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder<Builder> implements + // @@protoc_insertion_point(builder_implements:org.demo.kafka.protobuf.ProtobufProduct) + org.demo.kafka.protobuf.ProtobufProductOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.demo.kafka.protobuf.ProtobufProductOuterClass.internal_static_org_demo_kafka_protobuf_ProtobufProduct_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.demo.kafka.protobuf.ProtobufProductOuterClass.internal_static_org_demo_kafka_protobuf_ProtobufProduct_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.demo.kafka.protobuf.ProtobufProduct.class, org.demo.kafka.protobuf.ProtobufProduct.Builder.class); + } + + // Construct using org.demo.kafka.protobuf.ProtobufProduct.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + id_ = 0; + name_ = ""; + price_ = 0D; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.demo.kafka.protobuf.ProtobufProductOuterClass.internal_static_org_demo_kafka_protobuf_ProtobufProduct_descriptor; + } + + @java.lang.Override + public org.demo.kafka.protobuf.ProtobufProduct getDefaultInstanceForType() { + return org.demo.kafka.protobuf.ProtobufProduct.getDefaultInstance(); + } + + @java.lang.Override + public org.demo.kafka.protobuf.ProtobufProduct build() { + org.demo.kafka.protobuf.ProtobufProduct result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.demo.kafka.protobuf.ProtobufProduct buildPartial() { + org.demo.kafka.protobuf.ProtobufProduct result = new org.demo.kafka.protobuf.ProtobufProduct(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.demo.kafka.protobuf.ProtobufProduct result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.id_ = id_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.name_ = name_; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.price_ = price_; + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.demo.kafka.protobuf.ProtobufProduct) { + return mergeFrom((org.demo.kafka.protobuf.ProtobufProduct)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.demo.kafka.protobuf.ProtobufProduct other) { + if (other == org.demo.kafka.protobuf.ProtobufProduct.getDefaultInstance()) return this; + if (other.getId() != 0) { + setId(other.getId()); + } + if (!other.getName().isEmpty()) { + name_ = other.name_; + bitField0_ |= 0x00000002; + onChanged(); + } + if (java.lang.Double.doubleToRawLongBits(other.getPrice()) != 0) { + setPrice(other.getPrice()); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + id_ = input.readInt32(); + bitField0_ |= 0x00000001; + break; + } // case 8 + case 18: { + name_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 18 + case 25: { + price_ = input.readDouble(); + bitField0_ |= 0x00000004; + break; + } // case 25 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private int id_ ; + /** + * <code>int32 id = 1;</code> + * @return The id. + */ + @java.lang.Override + public int getId() { + return id_; + } + /** + * <code>int32 id = 1;</code> + * @param value The id to set. + * @return This builder for chaining. + */ + public Builder setId(int value) { + + id_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * <code>int32 id = 1;</code> + * @return This builder for chaining. + */ + public Builder clearId() { + bitField0_ = (bitField0_ & ~0x00000001); + id_ = 0; + onChanged(); + return this; + } + + private java.lang.Object name_ = ""; + /** + * <code>string name = 2;</code> + * @return The name. + */ + public java.lang.String getName() { + java.lang.Object ref = name_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + name_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * <code>string name = 2;</code> + * @return The bytes for name. + */ + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * <code>string name = 2;</code> + * @param value The name to set. + * @return This builder for chaining. + */ + public Builder setName( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + name_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * <code>string name = 2;</code> + * @return This builder for chaining. + */ + public Builder clearName() { + name_ = getDefaultInstance().getName(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + /** + * <code>string name = 2;</code> + * @param value The bytes for name to set. + * @return This builder for chaining. + */ + public Builder setNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + name_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + private double price_ ; + /** + * <code>double price = 3;</code> + * @return The price. + */ + @java.lang.Override + public double getPrice() { + return price_; + } + /** + * <code>double price = 3;</code> + * @param value The price to set. + * @return This builder for chaining. + */ + public Builder setPrice(double value) { + + price_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + /** + * <code>double price = 3;</code> + * @return This builder for chaining. + */ + public Builder clearPrice() { + bitField0_ = (bitField0_ & ~0x00000004); + price_ = 0D; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:org.demo.kafka.protobuf.ProtobufProduct) + } + + // @@protoc_insertion_point(class_scope:org.demo.kafka.protobuf.ProtobufProduct) + private static final org.demo.kafka.protobuf.ProtobufProduct DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.demo.kafka.protobuf.ProtobufProduct(); + } + + public static org.demo.kafka.protobuf.ProtobufProduct getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser<ProtobufProduct> + PARSER = new com.google.protobuf.AbstractParser<ProtobufProduct>() { + @java.lang.Override + public ProtobufProduct parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser<ProtobufProduct> parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser<ProtobufProduct> getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.demo.kafka.protobuf.ProtobufProduct getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + +} + diff --git a/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/protobuf/ProtobufProductOrBuilder.java b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/protobuf/ProtobufProductOrBuilder.java new file mode 100644 index 000000000..caf17ad50 --- /dev/null +++ b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/protobuf/ProtobufProductOrBuilder.java @@ -0,0 +1,36 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: ProtobufProduct.proto +// Protobuf Java Version: 4.33.1 + +package org.demo.kafka.protobuf; + +@com.google.protobuf.Generated +public interface ProtobufProductOrBuilder extends + // @@protoc_insertion_point(interface_extends:org.demo.kafka.protobuf.ProtobufProduct) + com.google.protobuf.MessageOrBuilder { + + /** + * <code>int32 id = 1;</code> + * @return The id. + */ + int getId(); + + /** + * <code>string name = 2;</code> + * @return The name. + */ + java.lang.String getName(); + /** + * <code>string name = 2;</code> + * @return The bytes for name. + */ + com.google.protobuf.ByteString + getNameBytes(); + + /** + * <code>double price = 3;</code> + * @return The price. + */ + double getPrice(); +} diff --git a/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/protobuf/ProtobufProductOuterClass.java b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/protobuf/ProtobufProductOuterClass.java new file mode 100644 index 000000000..ce3214777 --- /dev/null +++ b/examples/powertools-examples-kafka/src/main/java/org/demo/kafka/protobuf/ProtobufProductOuterClass.java @@ -0,0 +1,63 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: ProtobufProduct.proto +// Protobuf Java Version: 4.33.1 + +package org.demo.kafka.protobuf; + +@com.google.protobuf.Generated +public final class ProtobufProductOuterClass extends com.google.protobuf.GeneratedFile { + private ProtobufProductOuterClass() {} + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 33, + /* patch= */ 1, + /* suffix= */ "", + "ProtobufProductOuterClass"); + } + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + static final com.google.protobuf.Descriptors.Descriptor + internal_static_org_demo_kafka_protobuf_ProtobufProduct_descriptor; + static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_org_demo_kafka_protobuf_ProtobufProduct_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\025ProtobufProduct.proto\022\027org.demo.kafka." + + "protobuf\":\n\017ProtobufProduct\022\n\n\002id\030\001 \001(\005\022" + + "\014\n\004name\030\002 \001(\t\022\r\n\005price\030\003 \001(\001B6\n\027org.demo" + + ".kafka.protobufB\031ProtobufProductOuterCla" + + "ssP\001b\006proto3" + }; + descriptor = com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }); + internal_static_org_demo_kafka_protobuf_ProtobufProduct_descriptor = + getDescriptor().getMessageType(0); + internal_static_org_demo_kafka_protobuf_ProtobufProduct_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_org_demo_kafka_protobuf_ProtobufProduct_descriptor, + new java.lang.String[] { "Id", "Name", "Price", }); + descriptor.resolveAllFeaturesImmutable(); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/examples/powertools-examples-kafka/src/main/proto/ProtobufProduct.proto b/examples/powertools-examples-kafka/src/main/proto/ProtobufProduct.proto new file mode 100644 index 000000000..4d3338a6f --- /dev/null +++ b/examples/powertools-examples-kafka/src/main/proto/ProtobufProduct.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package org.demo.kafka.protobuf; + +option java_package = "org.demo.kafka.protobuf"; +option java_outer_classname = "ProtobufProductOuterClass"; +option java_multiple_files = true; + +message ProtobufProduct { + int32 id = 1; + string name = 2; + double price = 3; +} \ No newline at end of file diff --git a/example/HelloWorldFunction/src/main/resources/log4j2.xml b/examples/powertools-examples-kafka/src/main/resources/log4j2.xml similarity index 84% rename from example/HelloWorldFunction/src/main/resources/log4j2.xml rename to examples/powertools-examples-kafka/src/main/resources/log4j2.xml index 033da8a11..fe943d707 100644 --- a/example/HelloWorldFunction/src/main/resources/log4j2.xml +++ b/examples/powertools-examples-kafka/src/main/resources/log4j2.xml @@ -2,7 +2,7 @@ <Configuration packages="com.amazonaws.services.lambda.runtime.log4j2"> <Appenders> <Console name="JsonAppender" target="SYSTEM_OUT"> - <LambdaJsonLayout compact="true" eventEol="true"/> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> </Console> </Appenders> <Loggers> diff --git a/examples/powertools-examples-kafka/template.yaml b/examples/powertools-examples-kafka/template.yaml new file mode 100644 index 000000000..509b13ca3 --- /dev/null +++ b/examples/powertools-examples-kafka/template.yaml @@ -0,0 +1,59 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: > + Kafka Deserialization example with Kafka Lambda ESM + +Globals: + Function: + Timeout: 20 + Runtime: java11 + MemorySize: 512 + Tracing: Active + +Resources: + JsonDeserializationFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: org.demo.kafka.JsonDeserializationFunction::handleRequest + Environment: + Variables: + JAVA_TOOL_OPTIONS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" + POWERTOOLS_LOG_LEVEL: DEBUG + POWERTOOLS_SERVICE_NAME: JsonDeserialization + POWERTOOLS_METRICS_NAMESPACE: JsonDeserializationFunction + + AvroDeserializationFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: org.demo.kafka.AvroDeserializationFunction::handleRequest + Environment: + Variables: + JAVA_TOOL_OPTIONS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" + POWERTOOLS_LOG_LEVEL: DEBUG + POWERTOOLS_SERVICE_NAME: AvroDeserialization + POWERTOOLS_METRICS_NAMESPACE: AvroDeserializationFunction + + ProtobufDeserializationFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: org.demo.kafka.ProtobufDeserializationFunction::handleRequest + Environment: + Variables: + JAVA_TOOL_OPTIONS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" + POWERTOOLS_LOG_LEVEL: DEBUG + POWERTOOLS_SERVICE_NAME: ProtobufDeserialization + POWERTOOLS_METRICS_NAMESPACE: ProtobufDeserializationFunction + +Outputs: + JsonFunction: + Description: "Kafka JSON Lambda Function ARN" + Value: !GetAtt JsonDeserializationFunction.Arn + AvroFunction: + Description: "Kafka Avro Lambda Function ARN" + Value: !GetAtt AvroDeserializationFunction.Arn + ProtobufFunction: + Description: "Kafka Protobuf Lambda Function ARN" + Value: !GetAtt ProtobufDeserializationFunction.Arn diff --git a/examples/powertools-examples-kafka/tools/README.md b/examples/powertools-examples-kafka/tools/README.md new file mode 100644 index 000000000..02e8dde9b --- /dev/null +++ b/examples/powertools-examples-kafka/tools/README.md @@ -0,0 +1,73 @@ +# Kafka Sample Generator Tool + +This tool generates base64-encoded serialized products for testing the Kafka consumer functions with different serialization formats. + +## Supported Formats + +- **JSON**: Generates base64-encoded JSON serialized products +- **Avro**: Generates base64-encoded Avro serialized products +- **Protobuf**: Generates base64-encoded Protobuf serialized products + +## Usage + +Run the following Maven commands from this directory: + +```bash +# Generate Avro and Protobuf classes from schemas +mvn generate-sources + +# Compile the code +mvn compile +``` + +### Generate JSON Samples + +```bash +# Run the JSON sample generator +mvn exec:java -Dexec.mainClass="org.demo.kafka.tools.GenerateJsonSamples" +``` + +The tool will output base64-encoded values for JSON products that can be used in `../events/kafka-json-event.json`. + +### Generate Avro Samples + +```bash +# Run the Avro sample generator +mvn exec:java -Dexec.mainClass="org.demo.kafka.tools.GenerateAvroSamples" +``` + +The tool will output base64-encoded values for Avro products that can be used in `../events/kafka-avro-event.json`. + +### Generate Protobuf Samples + +```bash +# Run the Protobuf sample generator +mvn exec:java -Dexec.mainClass="org.demo.kafka.tools.GenerateProtobufSamples" +``` + +The tool will output base64-encoded values for Protobuf products that can be used in `../events/kafka-protobuf-event.json`. This generator creates samples with and without Confluent message-indexes to test different serialization scenarios. + +## Output + +Each generator produces: + +1. Three different products (Laptop, Smartphone, Headphones) +2. An integer key (42) and one entry with a nullish key to test for edge-cases +3. A complete sample event structure that can be used directly for testing + +The Protobuf generators additionally create samples with different Confluent message-index formats: +- Standard protobuf (no message indexes) +- Simple message index (single 0 byte) +- Complex message index (length-prefixed array) + +For more information about Confluent Schema Registry serialization formats and wire format specifications, see the [Confluent documentation](https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format). + +## Example + +After generating the samples, you can copy the output into the respective event files: + +- `../events/kafka-json-event.json` for JSON samples +- `../events/kafka-avro-event.json` for Avro samples +- `../events/kafka-protobuf-event.json` for Protobuf samples + +These event files can then be used to test the Lambda functions with the appropriate deserializer. diff --git a/examples/powertools-examples-kafka/tools/pom.xml b/examples/powertools-examples-kafka/tools/pom.xml new file mode 100644 index 000000000..e6f2654d1 --- /dev/null +++ b/examples/powertools-examples-kafka/tools/pom.xml @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>software.amazon.lambda.examples</groupId> + <artifactId>powertools-examples-kafka-tools</artifactId> + <version>2.0.0</version> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <avro.version>1.12.0</avro.version> + <protobuf.version>4.31.0</protobuf.version> + <kafka-clients.version>4.0.0</kafka-clients.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.avro</groupId> + <artifactId>avro</artifactId> + <version>${avro.version}</version> + </dependency> + <dependency> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-java</artifactId> + <version>${protobuf.version}</version> + </dependency> + <dependency> + <groupId>org.apache.kafka</groupId> + <artifactId>kafka-clients</artifactId> + <version>${kafka-clients.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>2.19.0</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.avro</groupId> + <artifactId>avro-maven-plugin</artifactId> + <version>${avro.version}</version> + <executions> + <execution> + <phase>generate-sources</phase> + <goals> + <goal>schema</goal> + </goals> + <configuration> + <sourceDirectory>${project.basedir}/../src/main/avro/</sourceDirectory> + <outputDirectory>${project.basedir}/src/main/java/</outputDirectory> + <stringType>String</stringType> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>io.github.ascopes</groupId> + <artifactId>protobuf-maven-plugin</artifactId> + <version>3.10.2</version> + <executions> + <execution> + <goals> + <goal>generate</goal> + </goals> + <phase>generate-sources</phase> + <configuration> + <protocVersion>${protobuf.version}</protocVersion> + <sourceDirectories> + <sourceDirectory>${project.basedir}/../src/main/proto</sourceDirectory> + </sourceDirectories> + <outputDirectory>${project.basedir}/src/main/java</outputDirectory> + <clearOutputDirectory>false</clearOutputDirectory> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>3.1.0</version> + <executions> + <execution> + <id>generate-json-samples</id> + <configuration> + <mainClass>org.demo.kafka.tools.GenerateJsonSamples</mainClass> + </configuration> + </execution> + <execution> + <id>generate-avro-samples</id> + <configuration> + <mainClass>org.demo.kafka.tools.GenerateAvroSamples</mainClass> + </configuration> + </execution> + <execution> + <id>generate-protobuf-samples</id> + <configuration> + <mainClass>org.demo.kafka.tools.GenerateProtobufSamples</mainClass> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/avro/AvroProduct.java b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/avro/AvroProduct.java new file mode 100644 index 000000000..fad7e2fbf --- /dev/null +++ b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/avro/AvroProduct.java @@ -0,0 +1,476 @@ +/** + * Autogenerated by Avro + * + * DO NOT EDIT DIRECTLY + */ +package org.demo.kafka.avro; + +import org.apache.avro.specific.SpecificData; +import org.apache.avro.util.Utf8; +import org.apache.avro.message.BinaryMessageEncoder; +import org.apache.avro.message.BinaryMessageDecoder; +import org.apache.avro.message.SchemaStore; + +@org.apache.avro.specific.AvroGenerated +public class AvroProduct extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord { + private static final long serialVersionUID = -2929699301240218341L; + + + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"AvroProduct\",\"namespace\":\"org.demo.kafka.avro\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"name\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"price\",\"type\":\"double\"}]}"); + public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; } + + private static final SpecificData MODEL$ = new SpecificData(); + + private static final BinaryMessageEncoder<AvroProduct> ENCODER = + new BinaryMessageEncoder<>(MODEL$, SCHEMA$); + + private static final BinaryMessageDecoder<AvroProduct> DECODER = + new BinaryMessageDecoder<>(MODEL$, SCHEMA$); + + /** + * Return the BinaryMessageEncoder instance used by this class. + * @return the message encoder used by this class + */ + public static BinaryMessageEncoder<AvroProduct> getEncoder() { + return ENCODER; + } + + /** + * Return the BinaryMessageDecoder instance used by this class. + * @return the message decoder used by this class + */ + public static BinaryMessageDecoder<AvroProduct> getDecoder() { + return DECODER; + } + + /** + * Create a new BinaryMessageDecoder instance for this class that uses the specified {@link SchemaStore}. + * @param resolver a {@link SchemaStore} used to find schemas by fingerprint + * @return a BinaryMessageDecoder instance for this class backed by the given SchemaStore + */ + public static BinaryMessageDecoder<AvroProduct> createDecoder(SchemaStore resolver) { + return new BinaryMessageDecoder<>(MODEL$, SCHEMA$, resolver); + } + + /** + * Serializes this AvroProduct to a ByteBuffer. + * @return a buffer holding the serialized data for this instance + * @throws java.io.IOException if this instance could not be serialized + */ + public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException { + return ENCODER.encode(this); + } + + /** + * Deserializes a AvroProduct from a ByteBuffer. + * @param b a byte buffer holding serialized data for an instance of this class + * @return a AvroProduct instance decoded from the given buffer + * @throws java.io.IOException if the given bytes could not be deserialized into an instance of this class + */ + public static AvroProduct fromByteBuffer( + java.nio.ByteBuffer b) throws java.io.IOException { + return DECODER.decode(b); + } + + private int id; + private java.lang.String name; + private double price; + + /** + * Default constructor. Note that this does not initialize fields + * to their default values from the schema. If that is desired then + * one should use <code>newBuilder()</code>. + */ + public AvroProduct() {} + + /** + * All-args constructor. + * @param id The new value for id + * @param name The new value for name + * @param price The new value for price + */ + public AvroProduct(java.lang.Integer id, java.lang.String name, java.lang.Double price) { + this.id = id; + this.name = name; + this.price = price; + } + + @Override + public org.apache.avro.specific.SpecificData getSpecificData() { return MODEL$; } + + @Override + public org.apache.avro.Schema getSchema() { return SCHEMA$; } + + // Used by DatumWriter. Applications should not call. + @Override + public java.lang.Object get(int field$) { + switch (field$) { + case 0: return id; + case 1: return name; + case 2: return price; + default: throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + // Used by DatumReader. Applications should not call. + @Override + @SuppressWarnings(value="unchecked") + public void put(int field$, java.lang.Object value$) { + switch (field$) { + case 0: id = (java.lang.Integer)value$; break; + case 1: name = value$ != null ? value$.toString() : null; break; + case 2: price = (java.lang.Double)value$; break; + default: throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + /** + * Gets the value of the 'id' field. + * @return The value of the 'id' field. + */ + public int getId() { + return id; + } + + + /** + * Sets the value of the 'id' field. + * @param value the value to set. + */ + public void setId(int value) { + this.id = value; + } + + /** + * Gets the value of the 'name' field. + * @return The value of the 'name' field. + */ + public java.lang.String getName() { + return name; + } + + + /** + * Sets the value of the 'name' field. + * @param value the value to set. + */ + public void setName(java.lang.String value) { + this.name = value; + } + + /** + * Gets the value of the 'price' field. + * @return The value of the 'price' field. + */ + public double getPrice() { + return price; + } + + + /** + * Sets the value of the 'price' field. + * @param value the value to set. + */ + public void setPrice(double value) { + this.price = value; + } + + /** + * Creates a new AvroProduct RecordBuilder. + * @return A new AvroProduct RecordBuilder + */ + public static org.demo.kafka.avro.AvroProduct.Builder newBuilder() { + return new org.demo.kafka.avro.AvroProduct.Builder(); + } + + /** + * Creates a new AvroProduct RecordBuilder by copying an existing Builder. + * @param other The existing builder to copy. + * @return A new AvroProduct RecordBuilder + */ + public static org.demo.kafka.avro.AvroProduct.Builder newBuilder(org.demo.kafka.avro.AvroProduct.Builder other) { + if (other == null) { + return new org.demo.kafka.avro.AvroProduct.Builder(); + } else { + return new org.demo.kafka.avro.AvroProduct.Builder(other); + } + } + + /** + * Creates a new AvroProduct RecordBuilder by copying an existing AvroProduct instance. + * @param other The existing instance to copy. + * @return A new AvroProduct RecordBuilder + */ + public static org.demo.kafka.avro.AvroProduct.Builder newBuilder(org.demo.kafka.avro.AvroProduct other) { + if (other == null) { + return new org.demo.kafka.avro.AvroProduct.Builder(); + } else { + return new org.demo.kafka.avro.AvroProduct.Builder(other); + } + } + + /** + * RecordBuilder for AvroProduct instances. + */ + @org.apache.avro.specific.AvroGenerated + public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase<AvroProduct> + implements org.apache.avro.data.RecordBuilder<AvroProduct> { + + private int id; + private java.lang.String name; + private double price; + + /** Creates a new Builder */ + private Builder() { + super(SCHEMA$, MODEL$); + } + + /** + * Creates a Builder by copying an existing Builder. + * @param other The existing Builder to copy. + */ + private Builder(org.demo.kafka.avro.AvroProduct.Builder other) { + super(other); + if (isValidValue(fields()[0], other.id)) { + this.id = data().deepCopy(fields()[0].schema(), other.id); + fieldSetFlags()[0] = other.fieldSetFlags()[0]; + } + if (isValidValue(fields()[1], other.name)) { + this.name = data().deepCopy(fields()[1].schema(), other.name); + fieldSetFlags()[1] = other.fieldSetFlags()[1]; + } + if (isValidValue(fields()[2], other.price)) { + this.price = data().deepCopy(fields()[2].schema(), other.price); + fieldSetFlags()[2] = other.fieldSetFlags()[2]; + } + } + + /** + * Creates a Builder by copying an existing AvroProduct instance + * @param other The existing instance to copy. + */ + private Builder(org.demo.kafka.avro.AvroProduct other) { + super(SCHEMA$, MODEL$); + if (isValidValue(fields()[0], other.id)) { + this.id = data().deepCopy(fields()[0].schema(), other.id); + fieldSetFlags()[0] = true; + } + if (isValidValue(fields()[1], other.name)) { + this.name = data().deepCopy(fields()[1].schema(), other.name); + fieldSetFlags()[1] = true; + } + if (isValidValue(fields()[2], other.price)) { + this.price = data().deepCopy(fields()[2].schema(), other.price); + fieldSetFlags()[2] = true; + } + } + + /** + * Gets the value of the 'id' field. + * @return The value. + */ + public int getId() { + return id; + } + + + /** + * Sets the value of the 'id' field. + * @param value The value of 'id'. + * @return This builder. + */ + public org.demo.kafka.avro.AvroProduct.Builder setId(int value) { + validate(fields()[0], value); + this.id = value; + fieldSetFlags()[0] = true; + return this; + } + + /** + * Checks whether the 'id' field has been set. + * @return True if the 'id' field has been set, false otherwise. + */ + public boolean hasId() { + return fieldSetFlags()[0]; + } + + + /** + * Clears the value of the 'id' field. + * @return This builder. + */ + public org.demo.kafka.avro.AvroProduct.Builder clearId() { + fieldSetFlags()[0] = false; + return this; + } + + /** + * Gets the value of the 'name' field. + * @return The value. + */ + public java.lang.String getName() { + return name; + } + + + /** + * Sets the value of the 'name' field. + * @param value The value of 'name'. + * @return This builder. + */ + public org.demo.kafka.avro.AvroProduct.Builder setName(java.lang.String value) { + validate(fields()[1], value); + this.name = value; + fieldSetFlags()[1] = true; + return this; + } + + /** + * Checks whether the 'name' field has been set. + * @return True if the 'name' field has been set, false otherwise. + */ + public boolean hasName() { + return fieldSetFlags()[1]; + } + + + /** + * Clears the value of the 'name' field. + * @return This builder. + */ + public org.demo.kafka.avro.AvroProduct.Builder clearName() { + name = null; + fieldSetFlags()[1] = false; + return this; + } + + /** + * Gets the value of the 'price' field. + * @return The value. + */ + public double getPrice() { + return price; + } + + + /** + * Sets the value of the 'price' field. + * @param value The value of 'price'. + * @return This builder. + */ + public org.demo.kafka.avro.AvroProduct.Builder setPrice(double value) { + validate(fields()[2], value); + this.price = value; + fieldSetFlags()[2] = true; + return this; + } + + /** + * Checks whether the 'price' field has been set. + * @return True if the 'price' field has been set, false otherwise. + */ + public boolean hasPrice() { + return fieldSetFlags()[2]; + } + + + /** + * Clears the value of the 'price' field. + * @return This builder. + */ + public org.demo.kafka.avro.AvroProduct.Builder clearPrice() { + fieldSetFlags()[2] = false; + return this; + } + + @Override + @SuppressWarnings("unchecked") + public AvroProduct build() { + try { + AvroProduct record = new AvroProduct(); + record.id = fieldSetFlags()[0] ? this.id : (java.lang.Integer) defaultValue(fields()[0]); + record.name = fieldSetFlags()[1] ? this.name : (java.lang.String) defaultValue(fields()[1]); + record.price = fieldSetFlags()[2] ? this.price : (java.lang.Double) defaultValue(fields()[2]); + return record; + } catch (org.apache.avro.AvroMissingFieldException e) { + throw e; + } catch (java.lang.Exception e) { + throw new org.apache.avro.AvroRuntimeException(e); + } + } + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumWriter<AvroProduct> + WRITER$ = (org.apache.avro.io.DatumWriter<AvroProduct>)MODEL$.createDatumWriter(SCHEMA$); + + @Override public void writeExternal(java.io.ObjectOutput out) + throws java.io.IOException { + WRITER$.write(this, SpecificData.getEncoder(out)); + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumReader<AvroProduct> + READER$ = (org.apache.avro.io.DatumReader<AvroProduct>)MODEL$.createDatumReader(SCHEMA$); + + @Override public void readExternal(java.io.ObjectInput in) + throws java.io.IOException { + READER$.read(this, SpecificData.getDecoder(in)); + } + + @Override protected boolean hasCustomCoders() { return true; } + + @Override public void customEncode(org.apache.avro.io.Encoder out) + throws java.io.IOException + { + out.writeInt(this.id); + + out.writeString(this.name); + + out.writeDouble(this.price); + + } + + @Override public void customDecode(org.apache.avro.io.ResolvingDecoder in) + throws java.io.IOException + { + org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff(); + if (fieldOrder == null) { + this.id = in.readInt(); + + this.name = in.readString(); + + this.price = in.readDouble(); + + } else { + for (int i = 0; i < 3; i++) { + switch (fieldOrder[i].pos()) { + case 0: + this.id = in.readInt(); + break; + + case 1: + this.name = in.readString(); + break; + + case 2: + this.price = in.readDouble(); + break; + + default: + throw new java.io.IOException("Corrupt ResolvingDecoder."); + } + } + } + } +} + + + + + + + + + + diff --git a/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/protobuf/ProtobufProduct.java b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/protobuf/ProtobufProduct.java new file mode 100644 index 000000000..6da9113fc --- /dev/null +++ b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/protobuf/ProtobufProduct.java @@ -0,0 +1,636 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: ProtobufProduct.proto +// Protobuf Java Version: 4.31.0 + +package org.demo.kafka.protobuf; + +/** + * Protobuf type {@code org.demo.kafka.protobuf.ProtobufProduct} + */ +@com.google.protobuf.Generated +public final class ProtobufProduct extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:org.demo.kafka.protobuf.ProtobufProduct) + ProtobufProductOrBuilder { +private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 31, + /* patch= */ 0, + /* suffix= */ "", + ProtobufProduct.class.getName()); + } + // Use ProtobufProduct.newBuilder() to construct. + private ProtobufProduct(com.google.protobuf.GeneratedMessage.Builder<?> builder) { + super(builder); + } + private ProtobufProduct() { + name_ = ""; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.demo.kafka.protobuf.ProtobufProductOuterClass.internal_static_org_demo_kafka_protobuf_ProtobufProduct_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.demo.kafka.protobuf.ProtobufProductOuterClass.internal_static_org_demo_kafka_protobuf_ProtobufProduct_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.demo.kafka.protobuf.ProtobufProduct.class, org.demo.kafka.protobuf.ProtobufProduct.Builder.class); + } + + public static final int ID_FIELD_NUMBER = 1; + private int id_ = 0; + /** + * <code>int32 id = 1;</code> + * @return The id. + */ + @java.lang.Override + public int getId() { + return id_; + } + + public static final int NAME_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private volatile java.lang.Object name_ = ""; + /** + * <code>string name = 2;</code> + * @return The name. + */ + @java.lang.Override + public java.lang.String getName() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + name_ = s; + return s; + } + } + /** + * <code>string name = 2;</code> + * @return The bytes for name. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int PRICE_FIELD_NUMBER = 3; + private double price_ = 0D; + /** + * <code>double price = 3;</code> + * @return The price. + */ + @java.lang.Override + public double getPrice() { + return price_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (id_ != 0) { + output.writeInt32(1, id_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(name_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 2, name_); + } + if (java.lang.Double.doubleToRawLongBits(price_) != 0) { + output.writeDouble(3, price_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (id_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, id_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(name_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(2, name_); + } + if (java.lang.Double.doubleToRawLongBits(price_) != 0) { + size += com.google.protobuf.CodedOutputStream + .computeDoubleSize(3, price_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.demo.kafka.protobuf.ProtobufProduct)) { + return super.equals(obj); + } + org.demo.kafka.protobuf.ProtobufProduct other = (org.demo.kafka.protobuf.ProtobufProduct) obj; + + if (getId() + != other.getId()) return false; + if (!getName() + .equals(other.getName())) return false; + if (java.lang.Double.doubleToLongBits(getPrice()) + != java.lang.Double.doubleToLongBits( + other.getPrice())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + ID_FIELD_NUMBER; + hash = (53 * hash) + getId(); + hash = (37 * hash) + NAME_FIELD_NUMBER; + hash = (53 * hash) + getName().hashCode(); + hash = (37 * hash) + PRICE_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + java.lang.Double.doubleToLongBits(getPrice())); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.demo.kafka.protobuf.ProtobufProduct parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.demo.kafka.protobuf.ProtobufProduct parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.demo.kafka.protobuf.ProtobufProduct parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.demo.kafka.protobuf.ProtobufProduct prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code org.demo.kafka.protobuf.ProtobufProduct} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder<Builder> implements + // @@protoc_insertion_point(builder_implements:org.demo.kafka.protobuf.ProtobufProduct) + org.demo.kafka.protobuf.ProtobufProductOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.demo.kafka.protobuf.ProtobufProductOuterClass.internal_static_org_demo_kafka_protobuf_ProtobufProduct_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.demo.kafka.protobuf.ProtobufProductOuterClass.internal_static_org_demo_kafka_protobuf_ProtobufProduct_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.demo.kafka.protobuf.ProtobufProduct.class, org.demo.kafka.protobuf.ProtobufProduct.Builder.class); + } + + // Construct using org.demo.kafka.protobuf.ProtobufProduct.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + id_ = 0; + name_ = ""; + price_ = 0D; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.demo.kafka.protobuf.ProtobufProductOuterClass.internal_static_org_demo_kafka_protobuf_ProtobufProduct_descriptor; + } + + @java.lang.Override + public org.demo.kafka.protobuf.ProtobufProduct getDefaultInstanceForType() { + return org.demo.kafka.protobuf.ProtobufProduct.getDefaultInstance(); + } + + @java.lang.Override + public org.demo.kafka.protobuf.ProtobufProduct build() { + org.demo.kafka.protobuf.ProtobufProduct result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.demo.kafka.protobuf.ProtobufProduct buildPartial() { + org.demo.kafka.protobuf.ProtobufProduct result = new org.demo.kafka.protobuf.ProtobufProduct(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.demo.kafka.protobuf.ProtobufProduct result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.id_ = id_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.name_ = name_; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.price_ = price_; + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.demo.kafka.protobuf.ProtobufProduct) { + return mergeFrom((org.demo.kafka.protobuf.ProtobufProduct)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.demo.kafka.protobuf.ProtobufProduct other) { + if (other == org.demo.kafka.protobuf.ProtobufProduct.getDefaultInstance()) return this; + if (other.getId() != 0) { + setId(other.getId()); + } + if (!other.getName().isEmpty()) { + name_ = other.name_; + bitField0_ |= 0x00000002; + onChanged(); + } + if (java.lang.Double.doubleToRawLongBits(other.getPrice()) != 0) { + setPrice(other.getPrice()); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + id_ = input.readInt32(); + bitField0_ |= 0x00000001; + break; + } // case 8 + case 18: { + name_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 18 + case 25: { + price_ = input.readDouble(); + bitField0_ |= 0x00000004; + break; + } // case 25 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private int id_ ; + /** + * <code>int32 id = 1;</code> + * @return The id. + */ + @java.lang.Override + public int getId() { + return id_; + } + /** + * <code>int32 id = 1;</code> + * @param value The id to set. + * @return This builder for chaining. + */ + public Builder setId(int value) { + + id_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * <code>int32 id = 1;</code> + * @return This builder for chaining. + */ + public Builder clearId() { + bitField0_ = (bitField0_ & ~0x00000001); + id_ = 0; + onChanged(); + return this; + } + + private java.lang.Object name_ = ""; + /** + * <code>string name = 2;</code> + * @return The name. + */ + public java.lang.String getName() { + java.lang.Object ref = name_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + name_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * <code>string name = 2;</code> + * @return The bytes for name. + */ + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * <code>string name = 2;</code> + * @param value The name to set. + * @return This builder for chaining. + */ + public Builder setName( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + name_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * <code>string name = 2;</code> + * @return This builder for chaining. + */ + public Builder clearName() { + name_ = getDefaultInstance().getName(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + /** + * <code>string name = 2;</code> + * @param value The bytes for name to set. + * @return This builder for chaining. + */ + public Builder setNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + name_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + private double price_ ; + /** + * <code>double price = 3;</code> + * @return The price. + */ + @java.lang.Override + public double getPrice() { + return price_; + } + /** + * <code>double price = 3;</code> + * @param value The price to set. + * @return This builder for chaining. + */ + public Builder setPrice(double value) { + + price_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + /** + * <code>double price = 3;</code> + * @return This builder for chaining. + */ + public Builder clearPrice() { + bitField0_ = (bitField0_ & ~0x00000004); + price_ = 0D; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:org.demo.kafka.protobuf.ProtobufProduct) + } + + // @@protoc_insertion_point(class_scope:org.demo.kafka.protobuf.ProtobufProduct) + private static final org.demo.kafka.protobuf.ProtobufProduct DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.demo.kafka.protobuf.ProtobufProduct(); + } + + public static org.demo.kafka.protobuf.ProtobufProduct getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser<ProtobufProduct> + PARSER = new com.google.protobuf.AbstractParser<ProtobufProduct>() { + @java.lang.Override + public ProtobufProduct parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser<ProtobufProduct> parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser<ProtobufProduct> getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.demo.kafka.protobuf.ProtobufProduct getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + +} + diff --git a/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/protobuf/ProtobufProductOrBuilder.java b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/protobuf/ProtobufProductOrBuilder.java new file mode 100644 index 000000000..9c1518db3 --- /dev/null +++ b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/protobuf/ProtobufProductOrBuilder.java @@ -0,0 +1,36 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: ProtobufProduct.proto +// Protobuf Java Version: 4.31.0 + +package org.demo.kafka.protobuf; + +@com.google.protobuf.Generated +public interface ProtobufProductOrBuilder extends + // @@protoc_insertion_point(interface_extends:org.demo.kafka.protobuf.ProtobufProduct) + com.google.protobuf.MessageOrBuilder { + + /** + * <code>int32 id = 1;</code> + * @return The id. + */ + int getId(); + + /** + * <code>string name = 2;</code> + * @return The name. + */ + java.lang.String getName(); + /** + * <code>string name = 2;</code> + * @return The bytes for name. + */ + com.google.protobuf.ByteString + getNameBytes(); + + /** + * <code>double price = 3;</code> + * @return The price. + */ + double getPrice(); +} diff --git a/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/protobuf/ProtobufProductOuterClass.java b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/protobuf/ProtobufProductOuterClass.java new file mode 100644 index 000000000..6a99f35ec --- /dev/null +++ b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/protobuf/ProtobufProductOuterClass.java @@ -0,0 +1,63 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: ProtobufProduct.proto +// Protobuf Java Version: 4.31.0 + +package org.demo.kafka.protobuf; + +@com.google.protobuf.Generated +public final class ProtobufProductOuterClass { + private ProtobufProductOuterClass() {} + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 31, + /* patch= */ 0, + /* suffix= */ "", + ProtobufProductOuterClass.class.getName()); + } + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + static final com.google.protobuf.Descriptors.Descriptor + internal_static_org_demo_kafka_protobuf_ProtobufProduct_descriptor; + static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_org_demo_kafka_protobuf_ProtobufProduct_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\025ProtobufProduct.proto\022\027org.demo.kafka." + + "protobuf\":\n\017ProtobufProduct\022\n\n\002id\030\001 \001(\005\022" + + "\014\n\004name\030\002 \001(\t\022\r\n\005price\030\003 \001(\001B6\n\027org.demo" + + ".kafka.protobufB\031ProtobufProductOuterCla" + + "ssP\001b\006proto3" + }; + descriptor = com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }); + internal_static_org_demo_kafka_protobuf_ProtobufProduct_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_org_demo_kafka_protobuf_ProtobufProduct_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_org_demo_kafka_protobuf_ProtobufProduct_descriptor, + new java.lang.String[] { "Id", "Name", "Price", }); + descriptor.resolveAllFeaturesImmutable(); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/tools/GenerateAvroSamples.java b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/tools/GenerateAvroSamples.java new file mode 100644 index 000000000..e6f4d38fd --- /dev/null +++ b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/tools/GenerateAvroSamples.java @@ -0,0 +1,127 @@ +package org.demo.kafka.tools; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Base64; + +import org.apache.avro.io.BinaryEncoder; +import org.apache.avro.io.DatumWriter; +import org.apache.avro.io.EncoderFactory; +import org.apache.avro.specific.SpecificDatumWriter; +import org.demo.kafka.avro.AvroProduct; + +/** + * Utility class to generate base64-encoded Avro serialized products + * for use in test events. + */ +public final class GenerateAvroSamples { + + private GenerateAvroSamples() { + // Utility class + } + + public static void main(String[] args) throws IOException { + // Create three different products + AvroProduct product1 = new AvroProduct(1001, "Laptop", 999.99); + AvroProduct product2 = new AvroProduct(1002, "Smartphone", 599.99); + AvroProduct product3 = new AvroProduct(1003, "Headphones", 149.99); + + // Serialize and encode each product + String encodedProduct1 = serializeAndEncode(product1); + String encodedProduct2 = serializeAndEncode(product2); + String encodedProduct3 = serializeAndEncode(product3); + + // Serialize and encode an integer key + String encodedKey = serializeAndEncodeInteger(42); + + // Print the results + System.out.println("Base64 encoded Avro products for use in kafka-avro-event.json:"); + System.out.println("\nProduct 1 (with key):"); + System.out.println("key: \"" + encodedKey + "\","); + System.out.println("value: \"" + encodedProduct1 + "\","); + + System.out.println("\nProduct 2 (with key):"); + System.out.println("key: \"" + encodedKey + "\","); + System.out.println("value: \"" + encodedProduct2 + "\","); + + System.out.println("\nProduct 3 (without key):"); + System.out.println("key: null,"); + System.out.println("value: \"" + encodedProduct3 + "\","); + + // Print a sample event structure + System.out.println("\nSample event structure:"); + printSampleEvent(encodedKey, encodedProduct1, encodedProduct2, encodedProduct3); + } + + private static String serializeAndEncode(AvroProduct product) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryEncoder encoder = EncoderFactory.get().binaryEncoder(baos, null); + DatumWriter<AvroProduct> writer = new SpecificDatumWriter<>(AvroProduct.class); + + writer.write(product, encoder); + encoder.flush(); + + return Base64.getEncoder().encodeToString(baos.toByteArray()); + } + + private static String serializeAndEncodeInteger(Integer value) throws IOException { + // For simple types like integers, we'll just convert to string and encode + return Base64.getEncoder().encodeToString(value.toString().getBytes()); + } + + private static void printSampleEvent(String key, String product1, String product2, String product3) { + System.out.println("{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"eventSourceArn\": \"arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-4\",\n" + + + " \"bootstrapServers\": \"b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092\",\n" + + + " \"records\": {\n" + + " \"mytopic-0\": [\n" + + " {\n" + + " \"topic\": \"mytopic\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": \"" + key + "\",\n" + + " \"value\": \"" + product1 + "\",\n" + + " \"headers\": [\n" + + " {\n" + + " \"headerKey\": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101]\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"topic\": \"mytopic\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 16,\n" + + " \"timestamp\": 1545084650988,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": \"" + key + "\",\n" + + " \"value\": \"" + product2 + "\",\n" + + " \"headers\": [\n" + + " {\n" + + " \"headerKey\": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101]\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"topic\": \"mytopic\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 17,\n" + + " \"timestamp\": 1545084650989,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": \"" + product3 + "\",\n" + + " \"headers\": [\n" + + " {\n" + + " \"headerKey\": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101]\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"); + } +} diff --git a/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/tools/GenerateJsonSamples.java b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/tools/GenerateJsonSamples.java new file mode 100644 index 000000000..d0ef7cb55 --- /dev/null +++ b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/tools/GenerateJsonSamples.java @@ -0,0 +1,130 @@ +package org.demo.kafka.tools; + +import java.io.IOException; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Utility class to generate base64-encoded JSON serialized products + * for use in test events. + */ +public final class GenerateJsonSamples { + + private GenerateJsonSamples() { + // Utility class + } + + public static void main(String[] args) throws IOException { + // Create three different products + Map<String, Object> product1 = new HashMap<>(); + product1.put("id", 1001); + product1.put("name", "Laptop"); + product1.put("price", 999.99); + + Map<String, Object> product2 = new HashMap<>(); + product2.put("id", 1002); + product2.put("name", "Smartphone"); + product2.put("price", 599.99); + + Map<String, Object> product3 = new HashMap<>(); + product3.put("id", 1003); + product3.put("name", "Headphones"); + product3.put("price", 149.99); + + // Serialize and encode each product + String encodedProduct1 = serializeAndEncode(product1); + String encodedProduct2 = serializeAndEncode(product2); + String encodedProduct3 = serializeAndEncode(product3); + + // Serialize and encode an integer key + String encodedKey = serializeAndEncodeInteger(42); + + // Print the results + System.out.println("Base64 encoded JSON products for use in kafka-json-event.json:"); + System.out.println("\nProduct 1 (with key):"); + System.out.println("key: \"" + encodedKey + "\","); + System.out.println("value: \"" + encodedProduct1 + "\","); + + System.out.println("\nProduct 2 (with key):"); + System.out.println("key: \"" + encodedKey + "\","); + System.out.println("value: \"" + encodedProduct2 + "\","); + + System.out.println("\nProduct 3 (without key):"); + System.out.println("key: null,"); + System.out.println("value: \"" + encodedProduct3 + "\","); + + // Print a sample event structure + System.out.println("\nSample event structure:"); + printSampleEvent(encodedKey, encodedProduct1, encodedProduct2, encodedProduct3); + } + + private static String serializeAndEncode(Map<String, Object> product) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(product); + return Base64.getEncoder().encodeToString(json.getBytes()); + } + + private static String serializeAndEncodeInteger(Integer value) { + // For simple types like integers, we'll just convert to string and encode + return Base64.getEncoder().encodeToString(value.toString().getBytes()); + } + + private static void printSampleEvent(String key, String product1, String product2, String product3) { + System.out.println("{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"eventSourceArn\": \"arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-4\",\n" + + + " \"bootstrapServers\": \"b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092\",\n" + + + " \"records\": {\n" + + " \"mytopic-0\": [\n" + + " {\n" + + " \"topic\": \"mytopic\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": \"" + key + "\",\n" + + " \"value\": \"" + product1 + "\",\n" + + " \"headers\": [\n" + + " {\n" + + " \"headerKey\": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101]\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"topic\": \"mytopic\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": \"" + key + "\",\n" + + " \"value\": \"" + product2 + "\",\n" + + " \"headers\": [\n" + + " {\n" + + " \"headerKey\": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101]\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"topic\": \"mytopic\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": \"" + product3 + "\",\n" + + " \"headers\": [\n" + + " {\n" + + " \"headerKey\": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101]\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"); + } +} diff --git a/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/tools/GenerateProtobufSamples.java b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/tools/GenerateProtobufSamples.java new file mode 100644 index 000000000..aa5f6e330 --- /dev/null +++ b/examples/powertools-examples-kafka/tools/src/main/java/org/demo/kafka/tools/GenerateProtobufSamples.java @@ -0,0 +1,215 @@ +package org.demo.kafka.tools; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Base64; + +import org.apache.kafka.common.utils.ByteUtils; +import org.demo.kafka.protobuf.ProtobufProduct; + +import com.google.protobuf.CodedOutputStream; + +/** + * Utility class to generate base64-encoded Protobuf serialized products + * for use in test events. + */ +public final class GenerateProtobufSamples { + + private GenerateProtobufSamples() { + // Utility class + } + + public static void main(String[] args) throws IOException { + // Create a single product that will be used for all four scenarios + ProtobufProduct product = ProtobufProduct.newBuilder() + .setId(1001) + .setName("Laptop") + .setPrice(999.99) + .build(); + + // Create four different serializations of the same product + String standardProduct = serializeAndEncode(product); + String productWithConfluentSimpleIndex = serializeWithConfluentSimpleMessageIndex(product); + String productWithConfluentComplexIndex = serializeWithConfluentComplexMessageIndex(product); + String productWithGlueMagicByte = serializeWithGlueMagicByte(product); + + // Serialize and encode an integer key (same for all records) + String encodedKey = serializeAndEncodeInteger(42); + + // Print the results + System.out.println("Base64 encoded Protobuf products with different scenarios:"); + System.out.println("\n1. Plain Protobuf (no schema registry):"); + System.out.println("value: \"" + standardProduct + "\""); + + System.out.println("\n2. Confluent with Simple Message Index (optimized single 0):"); + System.out.println("value: \"" + productWithConfluentSimpleIndex + "\""); + + System.out.println("\n3. Confluent with Complex Message Index (array [1,0]):"); + System.out.println("value: \"" + productWithConfluentComplexIndex + "\""); + + System.out.println("\n4. Glue with Magic Byte:"); + System.out.println("value: \"" + productWithGlueMagicByte + "\""); + + // Print the merged event structure + System.out.println("\n" + "=".repeat(80)); + System.out.println("MERGED EVENT WITH ALL FOUR SCENARIOS"); + System.out.println("=".repeat(80)); + printSampleEvent(encodedKey, standardProduct, productWithConfluentSimpleIndex, productWithConfluentComplexIndex, + productWithGlueMagicByte); + } + + private static String serializeAndEncode(ProtobufProduct product) { + return Base64.getEncoder().encodeToString(product.toByteArray()); + } + + /** + * Serializes a protobuf product with a simple Confluent message index (optimized single 0). + * Format: [0][protobuf_data] + * + * @see {@link https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format} + */ + private static String serializeWithConfluentSimpleMessageIndex(ProtobufProduct product) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // Write optimized simple message index for Confluent (single 0 byte for [0]) + baos.write(0); + + // Write the protobuf data + baos.write(product.toByteArray()); + + return Base64.getEncoder().encodeToString(baos.toByteArray()); + } + + /** + * Serializes a protobuf product with a complex Confluent message index (array [1,0]). + * Format: [2][1][0][protobuf_data] where 2 is the array length using varint encoding + * + * @see {@link https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format} + */ + private static String serializeWithConfluentComplexMessageIndex(ProtobufProduct product) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // Write complex message index array [1,0] using ByteUtils + ByteBuffer buffer = ByteBuffer.allocate(1024); + ByteUtils.writeVarint(2, buffer); // Array length + ByteUtils.writeVarint(1, buffer); // First index value + ByteUtils.writeVarint(0, buffer); // Second index value + + buffer.flip(); + byte[] indexData = new byte[buffer.remaining()]; + buffer.get(indexData); + baos.write(indexData); + + // Write the protobuf data + baos.write(product.toByteArray()); + + return Base64.getEncoder().encodeToString(baos.toByteArray()); + } + + /** + * Serializes a protobuf product with Glue magic byte. + * Format: [1][protobuf_data] where 1 is the magic byte + */ + private static String serializeWithGlueMagicByte(ProtobufProduct product) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + CodedOutputStream codedOutput = CodedOutputStream.newInstance(baos); + + // Write Glue magic byte (single UInt32) + codedOutput.writeUInt32NoTag(1); + + // Write the protobuf data + product.writeTo(codedOutput); + + codedOutput.flush(); + return Base64.getEncoder().encodeToString(baos.toByteArray()); + } + + private static String serializeAndEncodeInteger(Integer value) { + // For simple types like integers, we'll just convert to string and encode + return Base64.getEncoder().encodeToString(value.toString().getBytes()); + } + + private static void printSampleEvent(String key, String standardProduct, String confluentSimpleProduct, + String confluentComplexProduct, String glueProduct) { + System.out.println("{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"eventSourceArn\": \"arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-4\",\n" + + + " \"bootstrapServers\": \"b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092\",\n" + + + " \"records\": {\n" + + " \"mytopic-0\": [\n" + + " {\n" + + " \"topic\": \"mytopic\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": \"" + key + "\",\n" + + " \"value\": \"" + standardProduct + "\",\n" + + " \"headers\": [\n" + + " {\n" + + " \"headerKey\": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101]\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"topic\": \"mytopic\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 16,\n" + + " \"timestamp\": 1545084650988,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": \"" + key + "\",\n" + + " \"value\": \"" + confluentSimpleProduct + "\",\n" + + " \"headers\": [\n" + + " {\n" + + " \"headerKey\": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101]\n" + + " }\n" + + " ],\n" + + " \"valueSchemaMetadata\": {\n" + + " \"schemaId\": \"123\",\n" + + " \"dataFormat\": \"PROTOBUF\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"topic\": \"mytopic\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 17,\n" + + " \"timestamp\": 1545084650989,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": \"" + confluentComplexProduct + "\",\n" + + " \"headers\": [\n" + + " {\n" + + " \"headerKey\": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101]\n" + + " }\n" + + " ],\n" + + " \"valueSchemaMetadata\": {\n" + + " \"schemaId\": \"456\",\n" + + " \"dataFormat\": \"PROTOBUF\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"topic\": \"mytopic\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 18,\n" + + " \"timestamp\": 1545084650990,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": \"" + key + "\",\n" + + " \"value\": \"" + glueProduct + "\",\n" + + " \"headers\": [\n" + + " {\n" + + " \"headerKey\": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101]\n" + + " }\n" + + " ],\n" + + " \"valueSchemaMetadata\": {\n" + + " \"schemaId\": \"12345678-1234-1234-1234-123456789012\",\n" + + " \"dataFormat\": \"PROTOBUF\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"); + } +} diff --git a/examples/powertools-examples-parameters/sam-graalvm/Dockerfile b/examples/powertools-examples-parameters/sam-graalvm/Dockerfile new file mode 100644 index 000000000..8377d6dc7 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/Dockerfile @@ -0,0 +1,14 @@ +#Use the official AWS SAM base image for Java 21 +FROM public.ecr.aws/sam/build-java21@sha256:a5554d68374e19450c6c88448516ac95a9acedc779f318040f5c230134b4e461 + +#Install GraalVM dependencies +RUN curl -4 -L curl https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz | tar -xvz +RUN mv graalvm-jdk-21.* /usr/lib/graalvm + +#Make native image and mvn available on CLI +RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image +RUN ln -s /usr/lib/maven/bin/mvn /usr/bin/mvn + +#Set GraalVM as default +ENV JAVA_HOME=/usr/lib/graalvm +ENV PATH=/usr/lib/graalvm/bin:$PATH diff --git a/examples/powertools-examples-parameters/sam-graalvm/Makefile b/examples/powertools-examples-parameters/sam-graalvm/Makefile new file mode 100644 index 000000000..5a3a00a69 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/Makefile @@ -0,0 +1,6 @@ +build-ParametersFunction: + mvn clean package -P native-image + chmod +x target/hello-world + cp target/hello-world $(ARTIFACTS_DIR) # (ARTIFACTS_DIR --> https://github.com/aws/aws-lambda-builders/blob/develop/aws_lambda_builders/workflows/custom_make/DESIGN.md#implementation) + chmod +x src/main/config/bootstrap + cp src/main/config/bootstrap $(ARTIFACTS_DIR) diff --git a/examples/powertools-examples-parameters/sam-graalvm/README.md b/examples/powertools-examples-parameters/sam-graalvm/README.md new file mode 100644 index 000000000..3797c778b --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/README.md @@ -0,0 +1,80 @@ +# Powertools for AWS Lambda (Java) - Parameters Example with SAM on GraalVM + +This project contains an example of Lambda function using the parameters module of Powertools for AWS Lambda (Java). For more information on this module, please refer to the [documentation](https://docs.powertools.aws.dev/lambda-java/utilities/parameters/). + +The example uses the [SSM Parameter Store](https://docs.powertools.aws.dev/lambda/java/utilities/parameters/#ssm-parameter-store) +and the [Secrets Manager](https://docs.powertools.aws.dev/lambda/java/utilities/parameters/#secrets-manager) to inject +runtime parameters into the application. +Have a look at [ParametersFunction.java](src/main/java/org/demo/parameters/ParametersFunction.java) for the full details. + +## Configuration + +- SAM uses [template.yaml](template.yaml) to define the application's AWS resources. + This file defines the Lambda function to be deployed as well as API Gateway for it. + +- Set the environment to use GraalVM + +```shell +export JAVA_HOME=<path to GraalVM> +``` + +## Build the sample application + +- Build the Docker image that will be used as the environment for SAM build: + +```shell +docker build --platform linux/amd64 . -t powertools-examples-parameters-sam-graalvm +``` + +- Build the SAM project using the docker image + +```shell +sam build --use-container --build-image powertools-examples-parameters-sam-graalvm +``` + +#### [Optional] Building with -SNAPSHOT versions of PowerTools + +- If you are testing the example with a -SNAPSHOT version of PowerTools, the maven build inside the docker image will fail. This is because the -SNAPSHOT version of the PowerTools library that you are working on is still not available in maven central/snapshot repository. + To get around this, follow these steps: + - Create the native image using the `docker` command below on your development machine. The native image is created in the `target` directory. + - `` docker run --platform linux/amd64 -it -v `pwd`:`pwd` -w `pwd` -v ~/.m2:/root/.m2 powertools-examples-parameters-sam-graalvm mvn clean -Pnative-image package -DskipTests `` + - Edit the [`Makefile`](Makefile) remove this line + - `mvn clean package -P native-image` + - Build the SAM project using the docker image + - `sam build --use-container --build-image powertools-examples-parameters-sam-graalvm` + +## Deploy the sample application + +- SAM deploy + +```shell +sam deploy +``` + +This sample is based on Serverless Application Model (SAM). To deploy it, check out the instructions for getting +started with SAM in [the examples directory](../../README.md) + +## Test the application + +First, hit the URL of the application. You can do this with curl or your browser: + +```bash + curl https://[REST-API-ID].execute-api.[REGION].amazonaws.com/Prod/params/ +``` +You will get your IP address back. The contents of the logs will be more interesting, and show you the values +of the parameters injected into the handler: + +```bash +sam logs --stack-name $MY_STACK_NAME --tail +``` + +```json +{ + ... + "thread": "main", + "level": "INFO", + "loggerName": "org.demo.parameters.ParametersFunction", + "message": "secretjsonobj=MyObject{id=23443, code='hk38543oj24kn796kp67bkb234gkj679l68'}\n", + ... +} +``` diff --git a/examples/powertools-examples-parameters/sam-graalvm/pom.xml b/examples/powertools-examples-parameters/sam-graalvm/pom.xml new file mode 100644 index 000000000..2aaffd9d1 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/pom.xml @@ -0,0 +1,203 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-parameters-sam-graalvm</artifactId> + <packaging>jar</packaging> + <name>Powertools for AWS Lambda (Java) - Examples - Parameters GraalVM</name> + + <properties> + <log4j.version>2.25.3</log4j.version> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <aspectj.version>1.9.20.1</aspectj.version> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters-ssm</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters-secrets</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <version>3.16.1</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + <version>2.8.7</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-api</artifactId> + <version>${log4j.version}</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + <version>${log4j.version}</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j2-impl</artifactId> + <version>${log4j.version}</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-layout-template-json</artifactId> + <version>${log4j.version}</version> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>5.18.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.11.1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>5.13.1</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + </plugin> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>3.6.1</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <transformers> + <transformer + implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer" /> + </transformers> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId> + <version>0.2.0</version> + </dependency> + </dependencies> + </plugin> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>build-native</id> + <goals> + <goal>build</goal> + </goals> + <phase>package</phase> + </execution> + </executions> + <configuration> + <imageName>hello-world</imageName> + <mainClass>com.amazonaws.services.lambda.runtime.api.client.AWSLambda</mainClass> + <buildArgs> + <!-- required for AWS Lambda Runtime Interface Client --> + <arg>--enable-url-protocols=http</arg> + <arg>--add-opens java.base/java.util=ALL-UNNAMED</arg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/config/bootstrap b/examples/powertools-examples-parameters/sam-graalvm/src/main/config/bootstrap new file mode 100644 index 000000000..8e7928cd3 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/config/bootstrap @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +./hello-world $_HANDLER \ No newline at end of file diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/java/org/demo/parameters/MyObject.java b/examples/powertools-examples-parameters/sam-graalvm/src/main/java/org/demo/parameters/MyObject.java new file mode 100644 index 000000000..d406ae3df --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/java/org/demo/parameters/MyObject.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.parameters; + +public class MyObject { + + private long id; + private String code; + + public MyObject() { + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + @Override + public String toString() { + return "MyObject{" + + "id=" + id + + ", code='" + code + '\'' + + '}'; + } +} diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/java/org/demo/parameters/ParametersFunction.java b/examples/powertools-examples-parameters/sam-graalvm/src/main/java/org/demo/parameters/ParametersFunction.java new file mode 100644 index 000000000..9c3c422cf --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/java/org/demo/parameters/ParametersFunction.java @@ -0,0 +1,109 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.parameters; + +import static java.time.temporal.ChronoUnit.SECONDS; +import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; +import static software.amazon.lambda.powertools.parameters.transform.Transformer.json; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.parameters.secrets.SecretsParam; +import software.amazon.lambda.powertools.parameters.secrets.SecretsProvider; +import software.amazon.lambda.powertools.parameters.ssm.SSMParam; +import software.amazon.lambda.powertools.parameters.ssm.SSMProvider; + +public class ParametersFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger log = LoggerFactory.getLogger(ParametersFunction.class); + + // Annotation-style injection from secrets manager + @SecretsParam(key = "/powertools-java/userpwd") + String secretParamInjected; + + // Annotation-style injection from Systems Manager + @SSMParam(key = "/powertools-java/sample/simplekey") + String ssmParamInjected; + + SSMProvider ssmProvider = SSMProvider + .builder() + .build(); + SecretsProvider secretsProvider = SecretsProvider + .builder() + .build(); + + String simpleValue = ssmProvider.withMaxAge(30, SECONDS).get("/powertools-java/sample/simplekey"); + String listValue = ssmProvider.withMaxAge(60, SECONDS).get("/powertools-java/sample/keylist"); + MyObject jsonObj = ssmProvider.withTransformation(json).get("/powertools-java/sample/keyjson", MyObject.class); + Map<String, String> allValues = ssmProvider.getMultiple("/powertools-java/sample"); + String b64value = ssmProvider.withTransformation(base64).get("/powertools-java/sample/keybase64"); + + Map<String, String> secretJson = + secretsProvider.withTransformation(json).get("/powertools-java/userpwd", Map.class); + MyObject secretJsonObj = secretsProvider.withMaxAge(42, SECONDS).withTransformation(json) + .get("/powertools-java/secretcode", MyObject.class); + + @Override + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + + log.info("\n=============== SSM Parameter Store ==============="); + log.info("simplevalue={}, listvalue={}, b64value={}\n", simpleValue, listValue, b64value); + log.info("jsonobj={}\n", jsonObj); + + log.info("allvalues (multiple):"); + allValues.forEach((key, value) -> log.info("- {}={}\n", key, value)); + + log.info("\n=============== Secrets Manager ==============="); + log.info("secretjson:"); + secretJson.forEach((key, value) -> log.info("- {}={}\n", key, value)); + log.info("secretjsonobj={}\n", secretJsonObj); + + Map<String, String> headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + headers.put("X-Custom-Header", "application/json"); + + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() + .withHeaders(headers); + try { + final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); + String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); + + return response + .withStatusCode(200) + .withBody(output); + } catch (IOException e) { + return response + .withBody("{}") + .withStatusCode(500); + } + } + + private String getPageContents(String address) throws IOException { + URL url = new URL(address); + try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } + } +} diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..e97baa294 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,44 @@ +[ + { + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields":[{"name":"logger"}] + }, + { + "name":"java.lang.Void", + "methods":[{"name":"<init>","parameterTypes":[] }] + }, + { + "name":"java.util.Collections$UnmodifiableMap", + "fields":[{"name":"m"}] + }, + { + "name":"jdk.internal.module.IllegalAccessLogger", + "fields":[{"name":"logger"}] + }, + { + "name":"sun.misc.Unsafe", + "fields":[{"name":"theUnsafe"}] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/native-image.properties b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/native-image.properties new file mode 100644 index 000000000..db5ebaa55 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/native-image.properties @@ -0,0 +1 @@ +Args = --enable-url-protocols=http,https \ No newline at end of file diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/reflect-config.json b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/reflect-config.json new file mode 100644 index 000000000..16a37f483 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "org.demo.parameters.ParametersFunction", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.demo.parameters.MyObject", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/resource-config.json b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/powertools-test-suite/src/test/resources/log4j2.xml b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/log4j2.xml similarity index 57% rename from powertools-test-suite/src/test/resources/log4j2.xml rename to examples/powertools-examples-parameters/sam-graalvm/src/main/resources/log4j2.xml index 8ac9b34ce..fe943d707 100644 --- a/powertools-test-suite/src/test/resources/log4j2.xml +++ b/examples/powertools-examples-parameters/sam-graalvm/src/main/resources/log4j2.xml @@ -1,18 +1,14 @@ <?xml version="1.0" encoding="UTF-8"?> <Configuration packages="com.amazonaws.services.lambda.runtime.log4j2"> <Appenders> - <File name="JsonAppender" fileName="target/logfile.json"> - <LambdaJsonLayout compact="true" eventEol="true"/> - </File> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> </Appenders> - <Loggers> <Logger name="JsonLogger" level="INFO" additivity="false"> <AppenderRef ref="JsonAppender"/> </Logger> - <Logger name="com.amazonaws.auth.profile.internal" level="ERROR" additivity="false"> - <Appender-ref ref="JsonAppender"/> - </Logger> <Root level="info"> <AppenderRef ref="JsonAppender"/> </Root> diff --git a/examples/powertools-examples-parameters/sam-graalvm/template.yaml b/examples/powertools-examples-parameters/sam-graalvm/template.yaml new file mode 100644 index 000000000..b4a4d6907 --- /dev/null +++ b/examples/powertools-examples-parameters/sam-graalvm/template.yaml @@ -0,0 +1,100 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + validation demo + +Globals: + Function: + Timeout: 20 + MemorySize: 512 + Tracing: Active + + +Resources: + ParametersFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: org.demo.parameters.ParametersFunction::handleRequest + Runtime: provided.al2023 + MemorySize: 512 + Tracing: Active + Environment: + Variables: + LOG_LEVEL: INFO + Policies: + - AWSSecretsManagerGetSecretValuePolicy: + SecretArn: !Ref UserPwd + - AWSSecretsManagerGetSecretValuePolicy: + SecretArn: !Ref SecretConfig + - Statement: + - Sid: SSMGetParameterPolicy + Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters + - ssm:GetParametersByPath + Resource: '*' + Events: + HelloWorld: + Type: Api + Properties: + Path: /params + Method: get + + UserPwd: + Type: AWS::SecretsManager::Secret + Properties: + Name: /powertools-java/userpwd + Description: Generated secret for lambda-powertools-java powertools-parameters + module + GenerateSecretString: + SecretStringTemplate: '{"username": "test-user"}' + GenerateStringKey: password + PasswordLength: 15 + ExcludeCharacters: '"@/\' + SecretConfig: + Type: AWS::SecretsManager::Secret + Properties: + Name: /powertools-java/secretcode + Description: Json secret for lambda-powertools-java powertools-parameters module + SecretString: '{"id":23443,"code":"hk38543oj24kn796kp67bkb234gkj679l68"}' + BasicParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /powertools-java/sample/simplekey + Type: String + Value: simplevalue + Description: Simple SSM Parameter for lambda-powertools-java powertools-parameters + module + ParameterList: + Type: AWS::SSM::Parameter + Properties: + Name: /powertools-java/sample/keylist + Type: StringList + Value: value1,value2,value3 + Description: SSM Parameter List for lambda-powertools-java powertools-parameters + module + JsonParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /powertools-java/sample/keyjson + Type: String + Value: '{"id":23443,"code":"hk38543oj24kn796kp67bkb234gkj679l68"}' + Description: Json SSM Parameter for lambda-powertools-java powertools-parameters + module + Base64Parameter: + Type: AWS::SSM::Parameter + Properties: + Name: /powertools-java/sample/keybase64 + Type: String + Value: aGVsbG8gd29ybGQ= + Description: Base64 SSM Parameter for lambda-powertools-java powertools-parameters module + +Outputs: + ParametersApi: + Description: "API Gateway endpoint URL for Prod stage for Parameters function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/params/" + ParametersFunction: + Description: "Parameters Lambda Function ARN" + Value: !GetAtt ParametersFunction.Arn \ No newline at end of file diff --git a/examples/powertools-examples-parameters/sam/README.md b/examples/powertools-examples-parameters/sam/README.md new file mode 100644 index 000000000..661752957 --- /dev/null +++ b/examples/powertools-examples-parameters/sam/README.md @@ -0,0 +1,38 @@ +# Powertools for AWS Lambda (Java) - Parameters Example + +This project contains an example of Lambda function using the parameters module of Powertools for AWS Lambda (Java). For more information on this module, please refer to the [documentation](https://docs.powertools.aws.dev/lambda-java/utilities/parameters/). + +The example uses the [SSM Parameter Store](https://docs.powertools.aws.dev/lambda/java/utilities/parameters/#ssm-parameter-store) +and the [Secrets Manager](https://docs.powertools.aws.dev/lambda/java/utilities/parameters/#secrets-manager) to inject +runtime parameters into the application. +Have a look at [ParametersFunction.java](src/main/java/org/demo/parameters/ParametersFunction.java) for the full details. + +## Deploy the sample application + +This sample is based on Serverless Application Model (SAM). To deploy it, check out the instructions for getting +started with SAM in [the examples directory](../../README.md) + +## Test the application + +First, hit the URL of the application. You can do this with curl or your browser: + +```bash + curl https://[REST-API-ID].execute-api.[REGION].amazonaws.com/Prod/params/ +``` +You will get your IP address back. The contents of the logs will be more interesting, and show you the values +of the parameters injected into the handler: + +```bash +sam logs --stack-name $MY_STACK_NAME --tail +``` + +```json +{ + ... + "thread": "main", + "level": "INFO", + "loggerName": "org.demo.parameters.ParametersFunction", + "message": "secretjsonobj=MyObject{id=23443, code='hk38543oj24kn796kp67bkb234gkj679l68'}\n", + ... +} +``` diff --git a/examples/powertools-examples-parameters/sam/pom.xml b/examples/powertools-examples-parameters/sam/pom.xml new file mode 100644 index 000000000..d2c3e68d2 --- /dev/null +++ b/examples/powertools-examples-parameters/sam/pom.xml @@ -0,0 +1,116 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-parameters-sam</artifactId> + <packaging>jar</packaging> + <name>Powertools for AWS Lambda (Java) - Examples - Parameters</name> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <aspectj.version>1.9.20.1</aspectj.version> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters-ssm</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters-secrets</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <version>3.16.1</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>5.18.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.11.1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>5.13.1</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + </plugin> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/examples/powertools-examples-parameters/sam/src/main/java/org/demo/parameters/MyObject.java b/examples/powertools-examples-parameters/sam/src/main/java/org/demo/parameters/MyObject.java new file mode 100644 index 000000000..d406ae3df --- /dev/null +++ b/examples/powertools-examples-parameters/sam/src/main/java/org/demo/parameters/MyObject.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.parameters; + +public class MyObject { + + private long id; + private String code; + + public MyObject() { + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + @Override + public String toString() { + return "MyObject{" + + "id=" + id + + ", code='" + code + '\'' + + '}'; + } +} diff --git a/examples/powertools-examples-parameters/sam/src/main/java/org/demo/parameters/ParametersFunction.java b/examples/powertools-examples-parameters/sam/src/main/java/org/demo/parameters/ParametersFunction.java new file mode 100644 index 000000000..9c3c422cf --- /dev/null +++ b/examples/powertools-examples-parameters/sam/src/main/java/org/demo/parameters/ParametersFunction.java @@ -0,0 +1,109 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.parameters; + +import static java.time.temporal.ChronoUnit.SECONDS; +import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; +import static software.amazon.lambda.powertools.parameters.transform.Transformer.json; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.parameters.secrets.SecretsParam; +import software.amazon.lambda.powertools.parameters.secrets.SecretsProvider; +import software.amazon.lambda.powertools.parameters.ssm.SSMParam; +import software.amazon.lambda.powertools.parameters.ssm.SSMProvider; + +public class ParametersFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + private static final Logger log = LoggerFactory.getLogger(ParametersFunction.class); + + // Annotation-style injection from secrets manager + @SecretsParam(key = "/powertools-java/userpwd") + String secretParamInjected; + + // Annotation-style injection from Systems Manager + @SSMParam(key = "/powertools-java/sample/simplekey") + String ssmParamInjected; + + SSMProvider ssmProvider = SSMProvider + .builder() + .build(); + SecretsProvider secretsProvider = SecretsProvider + .builder() + .build(); + + String simpleValue = ssmProvider.withMaxAge(30, SECONDS).get("/powertools-java/sample/simplekey"); + String listValue = ssmProvider.withMaxAge(60, SECONDS).get("/powertools-java/sample/keylist"); + MyObject jsonObj = ssmProvider.withTransformation(json).get("/powertools-java/sample/keyjson", MyObject.class); + Map<String, String> allValues = ssmProvider.getMultiple("/powertools-java/sample"); + String b64value = ssmProvider.withTransformation(base64).get("/powertools-java/sample/keybase64"); + + Map<String, String> secretJson = + secretsProvider.withTransformation(json).get("/powertools-java/userpwd", Map.class); + MyObject secretJsonObj = secretsProvider.withMaxAge(42, SECONDS).withTransformation(json) + .get("/powertools-java/secretcode", MyObject.class); + + @Override + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + + log.info("\n=============== SSM Parameter Store ==============="); + log.info("simplevalue={}, listvalue={}, b64value={}\n", simpleValue, listValue, b64value); + log.info("jsonobj={}\n", jsonObj); + + log.info("allvalues (multiple):"); + allValues.forEach((key, value) -> log.info("- {}={}\n", key, value)); + + log.info("\n=============== Secrets Manager ==============="); + log.info("secretjson:"); + secretJson.forEach((key, value) -> log.info("- {}={}\n", key, value)); + log.info("secretjsonobj={}\n", secretJsonObj); + + Map<String, String> headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + headers.put("X-Custom-Header", "application/json"); + + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() + .withHeaders(headers); + try { + final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); + String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); + + return response + .withStatusCode(200) + .withBody(output); + } catch (IOException e) { + return response + .withBody("{}") + .withStatusCode(500); + } + } + + private String getPageContents(String address) throws IOException { + URL url = new URL(address); + try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } + } +} diff --git a/examples/powertools-examples-parameters/sam/src/main/resources/log4j2.xml b/examples/powertools-examples-parameters/sam/src/main/resources/log4j2.xml new file mode 100644 index 000000000..fe943d707 --- /dev/null +++ b/examples/powertools-examples-parameters/sam/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration packages="com.amazonaws.services.lambda.runtime.log4j2"> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + <Root level="info"> + <AppenderRef ref="JsonAppender"/> + </Root> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/examples/powertools-examples-parameters/sam/template.yaml b/examples/powertools-examples-parameters/sam/template.yaml new file mode 100644 index 000000000..9d3bf8b0e --- /dev/null +++ b/examples/powertools-examples-parameters/sam/template.yaml @@ -0,0 +1,100 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + validation demo + +Globals: + Function: + Timeout: 20 + Runtime: java11 + MemorySize: 512 + Tracing: Active + + +Resources: + ParametersFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: org.demo.parameters.ParametersFunction::handleRequest + MemorySize: 512 + Tracing: Active + Environment: + Variables: + LOG_LEVEL: INFO + Policies: + - AWSSecretsManagerGetSecretValuePolicy: + SecretArn: !Ref UserPwd + - AWSSecretsManagerGetSecretValuePolicy: + SecretArn: !Ref SecretConfig + - Statement: + - Sid: SSMGetParameterPolicy + Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters + - ssm:GetParametersByPath + Resource: '*' + Events: + HelloWorld: + Type: Api + Properties: + Path: /params + Method: get + + UserPwd: + Type: AWS::SecretsManager::Secret + Properties: + Name: /powertools-java/userpwd + Description: Generated secret for lambda-powertools-java powertools-parameters + module + GenerateSecretString: + SecretStringTemplate: '{"username": "test-user"}' + GenerateStringKey: password + PasswordLength: 15 + ExcludeCharacters: '"@/\' + SecretConfig: + Type: AWS::SecretsManager::Secret + Properties: + Name: /powertools-java/secretcode + Description: Json secret for lambda-powertools-java powertools-parameters module + SecretString: '{"id":23443,"code":"hk38543oj24kn796kp67bkb234gkj679l68"}' + BasicParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /powertools-java/sample/simplekey + Type: String + Value: simplevalue + Description: Simple SSM Parameter for lambda-powertools-java powertools-parameters + module + ParameterList: + Type: AWS::SSM::Parameter + Properties: + Name: /powertools-java/sample/keylist + Type: StringList + Value: value1,value2,value3 + Description: SSM Parameter List for lambda-powertools-java powertools-parameters + module + JsonParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /powertools-java/sample/keyjson + Type: String + Value: '{"id":23443,"code":"hk38543oj24kn796kp67bkb234gkj679l68"}' + Description: Json SSM Parameter for lambda-powertools-java powertools-parameters + module + Base64Parameter: + Type: AWS::SSM::Parameter + Properties: + Name: /powertools-java/sample/keybase64 + Type: String + Value: aGVsbG8gd29ybGQ= + Description: Base64 SSM Parameter for lambda-powertools-java powertools-parameters module + +Outputs: + ParametersApi: + Description: "API Gateway endpoint URL for Prod stage for Parameters function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/params/" + ParametersFunction: + Description: "Parameters Lambda Function ARN" + Value: !GetAtt ParametersFunction.Arn \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam-graalvm/Dockerfile b/examples/powertools-examples-serialization/sam-graalvm/Dockerfile new file mode 100644 index 000000000..8377d6dc7 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/Dockerfile @@ -0,0 +1,14 @@ +#Use the official AWS SAM base image for Java 21 +FROM public.ecr.aws/sam/build-java21@sha256:a5554d68374e19450c6c88448516ac95a9acedc779f318040f5c230134b4e461 + +#Install GraalVM dependencies +RUN curl -4 -L curl https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz | tar -xvz +RUN mv graalvm-jdk-21.* /usr/lib/graalvm + +#Make native image and mvn available on CLI +RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image +RUN ln -s /usr/lib/maven/bin/mvn /usr/bin/mvn + +#Set GraalVM as default +ENV JAVA_HOME=/usr/lib/graalvm +ENV PATH=/usr/lib/graalvm/bin:$PATH diff --git a/examples/powertools-examples-serialization/sam-graalvm/Makefile b/examples/powertools-examples-serialization/sam-graalvm/Makefile new file mode 100644 index 000000000..304b66239 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/Makefile @@ -0,0 +1,13 @@ +build-APIGatewayDeserializationFunction: + mvn clean package -P native-image + chmod +x target/hello-world + cp target/hello-world $(ARTIFACTS_DIR) # (ARTIFACTS_DIR --> https://github.com/aws/aws-lambda-builders/blob/develop/aws_lambda_builders/workflows/custom_make/DESIGN.md#implementation) + chmod +x src/main/config/bootstrap + cp src/main/config/bootstrap $(ARTIFACTS_DIR) + +build-SQSEventDeserializationFunction: + mvn clean package -P native-image + chmod +x target/hello-world + cp target/hello-world $(ARTIFACTS_DIR) # (ARTIFACTS_DIR --> https://github.com/aws/aws-lambda-builders/blob/develop/aws_lambda_builders/workflows/custom_make/DESIGN.md#implementation) + chmod +x src/main/config/bootstrap + cp src/main/config/bootstrap $(ARTIFACTS_DIR) diff --git a/examples/powertools-examples-serialization/sam-graalvm/README.md b/examples/powertools-examples-serialization/sam-graalvm/README.md new file mode 100644 index 000000000..01be98085 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/README.md @@ -0,0 +1,111 @@ +# Powertools for AWS Lambda (Java) - Serialization Example + +This project contains an example of Lambda function using the serialization utilities module of Powertools for AWS Lambda (Java). For more information on this module, please refer to the [documentation](https://docs.powertools.aws.dev/lambda-java/latest/utilities/serialization/). + +The project contains two `RequestHandler`s - + +* [APIGatewayRequestDeserializationFunction](src/main/java/org/demo/serialization/APIGatewayRequestDeserializationFunction.java) - Uses the serialization library to deserialize an API Gateway request body +* [SQSEventDeserializationFunction](src/main/java/org/demo/serialization/SQSEventDeserializationFunction.java) - Uses the serialization library to deserialize an SQS message body + +In both cases, the output of the serialized message will be printed to the function logs. The message format +in JSON looks like this: + +```json +{ + "id":1234, + "name":"product", + "price":42 +} +``` + +## Deploy the sample application + +This sample is based on Serverless Application Model (SAM). To deploy it, check out the instructions for getting +started with SAM in [the examples directory](../../README.md) + +## Configuration + +- Set the environment to use GraalVM + +```shell +export JAVA_HOME=<path to GraalVM> +``` + +## Build the sample application + +- Build the Docker image that will be used as the environment for SAM build: + +```shell +docker build --platform linux/amd64 . -t powertools-examples-serialization-sam-graalvm +``` + +- Build the SAM project using the docker image + +```shell +sam build --use-container --build-image powertools-examples-serialization-sam-graalvm +``` + +#### [Optional] Building with -SNAPSHOT versions of PowerTools + +- If you are testing the example with a -SNAPSHOT version of PowerTools, the maven build inside the docker image will fail. This is because the -SNAPSHOT version of the PowerTools library that you are working on is still not available in maven central/snapshot repository. + To get around this, follow these steps: + - Create the native image using the `docker` command below on your development machine. The native image is created in the `target` directory. + - `` docker run --platform linux/amd64 -it -v `pwd`:`pwd` -w `pwd` -v ~/.m2:/root/.m2 powertools-examples-serialization-sam-graalvm mvn clean -Pnative-image package -DskipTests `` + - Edit the [`Makefile`](Makefile) remove this line + - `mvn clean package -P native-image` + - Build the SAM project using the docker image + - `sam build --use-container --build-image powertools-examples-serialization-sam-graalvm` + + +## Test the application + +### 1. API Gateway Endpoint + +To test the HTTP endpoint, we can post a product to the test URL: + +```bash +curl -X POST https://[REST-API-ID].execute-api.[REGION].amazonaws.com/Prod/product/ -H "Content-Type: application/json" -d '{"id": 1234, "name": "product", "price": 42}' +``` + +The result will indicate that the handler has successfully deserialized the request body: + +``` +Received request for productId: 1234 +``` + +If we look at the logs using `sam logs --tail --stack-name $MY_STACK`, we will see the full deserialized request: + +```json +{ + ... + "level": "INFO", + "loggerName": "org.demo.serialization.APIGatewayRequestDeserializationFunction", + "message": "product=Product{id=1234, name='product', price=42.0}\n", + ... +} +``` + +### 2. SQS Queue +For the SQS handler, we have to send a request to our queue. We can either construct the Queue URL (see below), or +find it from the SQS section of the AWS console. + +```bash + aws sqs send-message --queue-url "https://sqs.[REGION].amazonaws.com/[ACCOUNT-ID]/sqs-event-deserialization-queue" --message-body '{"id": 1234, "name": "product", "price": 123}' +``` + +Here we can find the message by filtering through the logs for messages that have come back from our SQS handler: + +```bash +sam logs --tail --stack-name $MY_STACK --filter SQS +``` + +```bash + { + ... + "level": "INFO", + "loggerName": "org.demo.serialization.SQSEventDeserializationFunction", + "message": "products=[Product{id=1234, name='product', price=42.0}]\n", + ... +} + +``` diff --git a/examples/powertools-examples-serialization/sam-graalvm/events/APIGatewayEvent.json b/examples/powertools-examples-serialization/sam-graalvm/events/APIGatewayEvent.json new file mode 100644 index 000000000..cb38a3429 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/events/APIGatewayEvent.json @@ -0,0 +1,63 @@ +{ + "body": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } + } + \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam-graalvm/events/SQSEvent.json b/examples/powertools-examples-serialization/sam-graalvm/events/SQSEvent.json new file mode 100644 index 000000000..9056d3ef2 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/events/SQSEvent.json @@ -0,0 +1,39 @@ +{ + "Records": [ + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{ \"id\": 1234, \"name\": \"product\", \"price\": 42}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{ \"id\": 12345, \"name\": \"product5\", \"price\": 45}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam-graalvm/pom.xml b/examples/powertools-examples-serialization/sam-graalvm/pom.xml new file mode 100644 index 000000000..5077c8989 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/pom.xml @@ -0,0 +1,89 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-serialization-sam-graalvm</artifactId> + <packaging>jar</packaging> + <name>Powertools for AWS Lambda (Java) - Examples - Serialization GraalVM</name> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-serialization</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <version>3.16.1</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + <version>2.8.7</version> + </dependency> + </dependencies> + + <build> + <plugins> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>build-native</id> + <goals> + <goal>build</goal> + </goals> + <phase>package</phase> + </execution> + </executions> + <configuration> + <imageName>hello-world</imageName> + <mainClass>com.amazonaws.services.lambda.runtime.api.client.AWSLambda</mainClass> + <buildArgs> + <!-- required for AWS Lambda Runtime Interface Client --> + <arg>--enable-url-protocols=http</arg> + <arg>--add-opens java.base/java.util=ALL-UNNAMED</arg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/config/bootstrap b/examples/powertools-examples-serialization/sam-graalvm/src/main/config/bootstrap new file mode 100644 index 000000000..8e7928cd3 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/config/bootstrap @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +./hello-world $_HANDLER \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/java/org/demo/serialization/APIGatewayRequestDeserializationFunction.java b/examples/powertools-examples-serialization/sam-graalvm/src/main/java/org/demo/serialization/APIGatewayRequestDeserializationFunction.java new file mode 100644 index 000000000..3ca75cf4a --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/java/org/demo/serialization/APIGatewayRequestDeserializationFunction.java @@ -0,0 +1,53 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.serialization; + +import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class APIGatewayRequestDeserializationFunction + implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(APIGatewayRequestDeserializationFunction.class); + private static final Map<String, String> HEADERS = new HashMap<String, String>() { + private static final long serialVersionUID = 7074189990115081999L; + { + put("Content-Type", "application/json"); + put("X-Custom-Header", "application/json"); + } + }; + + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent event, Context context) { + + Product product = extractDataFrom(event).as(Product.class); + LOGGER.info("\n=============== Deserialized request body: ==============="); + LOGGER.info("product={}\n", product); + + return new APIGatewayProxyResponseEvent() + .withHeaders(HEADERS) + .withStatusCode(200) + .withBody("Received request for productId: " + product.getId()); + } +} + diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/java/org/demo/serialization/Product.java b/examples/powertools-examples-serialization/sam-graalvm/src/main/java/org/demo/serialization/Product.java new file mode 100644 index 000000000..25bae34f6 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/java/org/demo/serialization/Product.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.serialization; + +public class Product { + private long id; + private String name; + private double price; + + public Product() { + } + + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + @Override + public String toString() { + return "Product{" + + "id=" + id + + ", name='" + name + '\'' + + ", price=" + price + + '}'; + } +} diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/java/org/demo/serialization/SQSEventDeserializationFunction.java b/examples/powertools-examples-serialization/sam-graalvm/src/main/java/org/demo/serialization/SQSEventDeserializationFunction.java new file mode 100644 index 000000000..79097e19c --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/java/org/demo/serialization/SQSEventDeserializationFunction.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.serialization; + +import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class SQSEventDeserializationFunction implements RequestHandler<SQSEvent, String> { + + private static final Logger LOGGER = LoggerFactory.getLogger(SQSEventDeserializationFunction.class); + + public String handleRequest(SQSEvent event, Context context) { + List<Product> products = extractDataFrom(event).asListOf(Product.class); + + LOGGER.info("\n=============== Deserialized messages: ==============="); + LOGGER.info("products={}\n", products); + + return "Number of received messages: " + products.size(); + } +} + diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..7d38fc57d --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,48 @@ +[ + { + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields":[{"name":"logger"}] + }, + { + "name":"java.lang.Void", + "methods":[{"name":"<init>","parameterTypes":[] }] + }, + { + "name":"java.util.Collections$UnmodifiableMap", + "fields":[{"name":"m"}] + }, + { + "name":"jdk.internal.module.IllegalAccessLogger", + "fields":[{"name":"logger"}] + }, + { + "name":"sun.misc.Unsafe", + "fields":[{"name":"theUnsafe"}] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + }, + { + "name":"software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor", + "fields":[{"name":"isColdStart"},{"name":"serviceName"}] + } +] diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/native-image.properties b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/native-image.properties new file mode 100644 index 000000000..a2249e17d --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/native-image.properties @@ -0,0 +1,3 @@ +Args = --enable-url-protocols=http,https \ +--initialize-at-run-time=\ + software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/reflect-config.json b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/reflect-config.json new file mode 100644 index 000000000..e35ca7c4f --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/reflect-config.json @@ -0,0 +1,29 @@ +[ + { + "name": "org.demo.serialization.APIGatewayRequestDeserializationFunction", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.demo.serialization.Product", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.demo.serialization.SQSEventDeserializationFunction", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/resource-config.json b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/META-INF/native-image/helloworld/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/log4j2.xml b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/log4j2.xml new file mode 100644 index 000000000..fe943d707 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration packages="com.amazonaws.services.lambda.runtime.log4j2"> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + <Root level="info"> + <AppenderRef ref="JsonAppender"/> + </Root> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam-graalvm/template.yaml b/examples/powertools-examples-serialization/sam-graalvm/template.yaml new file mode 100644 index 000000000..39fd22928 --- /dev/null +++ b/examples/powertools-examples-serialization/sam-graalvm/template.yaml @@ -0,0 +1,64 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + serialization utils demo + +Globals: + Function: + Timeout: 20 + MemorySize: 512 + Tracing: Active + + +Resources: + APIGatewayDeserializationFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: org.demo.serialization.APIGatewayRequestDeserializationFunction::handleRequest + Runtime: provided.al2023 + Events: + Product: + Type: Api + Properties: + Path: /product + Method: post + + DemoSqsQueue: + Type: AWS::SQS::Queue + Properties: + QueueName: "sqs-event-deserialization-queue" + + SQSEventDeserializationFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: org.demo.serialization.SQSEventDeserializationFunction::handleRequest + Runtime: provided.al2023 + Policies: + - Statement: + - Sid: SQSSendMessageBatch + Effect: Allow + Action: + - sqs:SendMessageBatch + - sqs:SendMessage + Resource: !GetAtt DemoSqsQueue.Arn + Events: + SQSEvent: + Type: SQS + Properties: + Queue: !GetAtt DemoSqsQueue.Arn + BatchSize: 2 + MaximumBatchingWindowInSeconds: 30 + + +Outputs: + Api: + Description: "API Gateway endpoint URL for Prod stage for Serialization function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/product/" + Function: + Description: "Serialization Lambda Function ARN" + Value: !GetAtt APIGatewayDeserializationFunction.Arn + DemoSqsQueue: + Description: "ARN for SQS queue" + Value: !GetAtt DemoSqsQueue.Arn \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam/README.md b/examples/powertools-examples-serialization/sam/README.md new file mode 100644 index 000000000..247752f29 --- /dev/null +++ b/examples/powertools-examples-serialization/sam/README.md @@ -0,0 +1,77 @@ +# Powertools for AWS Lambda (Java) - Serialization Example + +This project contains an example of Lambda function using the serialization utilities module of Powertools for AWS Lambda (Java). For more information on this module, please refer to the [documentation](https://docs.powertools.aws.dev/lambda-java/utilities/serialization/). + +The project contains two `RequestHandler`s - + +* [APIGatewayRequestDeserializationFunction](src/main/java/org/demo/serialization/APIGatewayRequestDeserializationFunction.java) - Uses the serialization library to deserialize an API Gateway request body +* [SQSEventDeserializationFunction](src/main/java/org/demo/serialization/SQSEventDeserializationFunction.java) - Uses the serialization library to deserialize an SQS message body + +In both cases, the output of the serialized message will be printed to the function logs. The message format +in JSON looks like this: + +```json +{ + "id":1234, + "name":"product", + "price":42 +} +``` + +## Deploy the sample application + +This sample is based on Serverless Application Model (SAM). To deploy it, check out the instructions for getting +started with SAM in [the examples directory](../../README.md) + +## Test the application + +### 1. API Gateway Endpoint + +To test the HTTP endpoint, we can post a product to the test URL: + +```bash +curl -X POST https://[REST-API-ID].execute-api.[REGION].amazonaws.com/Prod/product/ -H "Content-Type: application/json" -d '{"id": 1234, "name": "product", "price": 42}' +``` + +The result will indicate that the handler has successfully deserialized the request body: + +``` +Received request for productId: 1234 +``` + +If we look at the logs using `sam logs --tail --stack-name $MY_STACK`, we will see the full deserialized request: + +```json +{ + ... + "level": "INFO", + "loggerName": "org.demo.serialization.APIGatewayRequestDeserializationFunction", + "message": "product=Product{id=1234, name='product', price=42.0}\n", + ... +} +``` + +### 2. SQS Queue +For the SQS handler, we have to send a request to our queue. We can either construct the Queue URL (see below), or +find it from the SQS section of the AWS console. + +```bash + aws sqs send-message --queue-url "https://sqs.[REGION].amazonaws.com/[ACCOUNT-ID]/sqs-event-deserialization-queue" --message-body '{"id": 1234, "name": "product", "price": 123}' +``` + +Here we can find the message by filtering through the logs for messages that have come back from our SQS handler: + +```bash +sam logs --tail --stack-name $MY_STACK --filter SQS +``` + +```bash + { + ... + "level": "INFO", + "loggerName": "org.demo.serialization.SQSEventDeserializationFunction", + "message": "products=[Product{id=1234, name='product', price=42.0}]\n", + ... +} + +``` diff --git a/examples/powertools-examples-serialization/sam/events/APIGatewayEvent.json b/examples/powertools-examples-serialization/sam/events/APIGatewayEvent.json new file mode 100644 index 000000000..cb38a3429 --- /dev/null +++ b/examples/powertools-examples-serialization/sam/events/APIGatewayEvent.json @@ -0,0 +1,63 @@ +{ + "body": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } + } + \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam/events/SQSEvent.json b/examples/powertools-examples-serialization/sam/events/SQSEvent.json new file mode 100644 index 000000000..9056d3ef2 --- /dev/null +++ b/examples/powertools-examples-serialization/sam/events/SQSEvent.json @@ -0,0 +1,39 @@ +{ + "Records": [ + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{ \"id\": 1234, \"name\": \"product\", \"price\": 42}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{ \"id\": 12345, \"name\": \"product5\", \"price\": 45}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam/pom.xml b/examples/powertools-examples-serialization/sam/pom.xml new file mode 100644 index 000000000..cf66c3e14 --- /dev/null +++ b/examples/powertools-examples-serialization/sam/pom.xml @@ -0,0 +1,60 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-serialization-sam</artifactId> + <packaging>jar</packaging> + <name>Powertools for AWS Lambda (Java) - Examples - Serialization</name> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-serialization</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <version>3.16.1</version> + </dependency> + </dependencies> + + <build> + <plugins> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/examples/powertools-examples-serialization/sam/src/main/java/org/demo/serialization/APIGatewayRequestDeserializationFunction.java b/examples/powertools-examples-serialization/sam/src/main/java/org/demo/serialization/APIGatewayRequestDeserializationFunction.java new file mode 100644 index 000000000..3ca75cf4a --- /dev/null +++ b/examples/powertools-examples-serialization/sam/src/main/java/org/demo/serialization/APIGatewayRequestDeserializationFunction.java @@ -0,0 +1,53 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.serialization; + +import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class APIGatewayRequestDeserializationFunction + implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + private static final Logger LOGGER = LoggerFactory.getLogger(APIGatewayRequestDeserializationFunction.class); + private static final Map<String, String> HEADERS = new HashMap<String, String>() { + private static final long serialVersionUID = 7074189990115081999L; + { + put("Content-Type", "application/json"); + put("X-Custom-Header", "application/json"); + } + }; + + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent event, Context context) { + + Product product = extractDataFrom(event).as(Product.class); + LOGGER.info("\n=============== Deserialized request body: ==============="); + LOGGER.info("product={}\n", product); + + return new APIGatewayProxyResponseEvent() + .withHeaders(HEADERS) + .withStatusCode(200) + .withBody("Received request for productId: " + product.getId()); + } +} + diff --git a/examples/powertools-examples-serialization/sam/src/main/java/org/demo/serialization/Product.java b/examples/powertools-examples-serialization/sam/src/main/java/org/demo/serialization/Product.java new file mode 100644 index 000000000..25bae34f6 --- /dev/null +++ b/examples/powertools-examples-serialization/sam/src/main/java/org/demo/serialization/Product.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.serialization; + +public class Product { + private long id; + private String name; + private double price; + + public Product() { + } + + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + @Override + public String toString() { + return "Product{" + + "id=" + id + + ", name='" + name + '\'' + + ", price=" + price + + '}'; + } +} diff --git a/examples/powertools-examples-serialization/sam/src/main/java/org/demo/serialization/SQSEventDeserializationFunction.java b/examples/powertools-examples-serialization/sam/src/main/java/org/demo/serialization/SQSEventDeserializationFunction.java new file mode 100644 index 000000000..79097e19c --- /dev/null +++ b/examples/powertools-examples-serialization/sam/src/main/java/org/demo/serialization/SQSEventDeserializationFunction.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.serialization; + +import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class SQSEventDeserializationFunction implements RequestHandler<SQSEvent, String> { + + private static final Logger LOGGER = LoggerFactory.getLogger(SQSEventDeserializationFunction.class); + + public String handleRequest(SQSEvent event, Context context) { + List<Product> products = extractDataFrom(event).asListOf(Product.class); + + LOGGER.info("\n=============== Deserialized messages: ==============="); + LOGGER.info("products={}\n", products); + + return "Number of received messages: " + products.size(); + } +} + diff --git a/examples/powertools-examples-serialization/sam/src/main/resources/log4j2.xml b/examples/powertools-examples-serialization/sam/src/main/resources/log4j2.xml new file mode 100644 index 000000000..fe943d707 --- /dev/null +++ b/examples/powertools-examples-serialization/sam/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration packages="com.amazonaws.services.lambda.runtime.log4j2"> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + <Root level="info"> + <AppenderRef ref="JsonAppender"/> + </Root> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/examples/powertools-examples-serialization/sam/template.yaml b/examples/powertools-examples-serialization/sam/template.yaml new file mode 100644 index 000000000..f330ec146 --- /dev/null +++ b/examples/powertools-examples-serialization/sam/template.yaml @@ -0,0 +1,63 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + serialization utils demo + +Globals: + Function: + Timeout: 20 + Runtime: java11 + MemorySize: 512 + Tracing: Active + + +Resources: + APIGatewayDeserializationFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: org.demo.serialization.APIGatewayRequestDeserializationFunction::handleRequest + Events: + Product: + Type: Api + Properties: + Path: /product + Method: post + + DemoSqsQueue: + Type: AWS::SQS::Queue + Properties: + QueueName: "sqs-event-deserialization-queue" + + SQSEventDeserializationFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: org.demo.serialization.SQSEventDeserializationFunction::handleRequest + Policies: + - Statement: + - Sid: SQSSendMessageBatch + Effect: Allow + Action: + - sqs:SendMessageBatch + - sqs:SendMessage + Resource: !GetAtt DemoSqsQueue.Arn + Events: + SQSEvent: + Type: SQS + Properties: + Queue: !GetAtt DemoSqsQueue.Arn + BatchSize: 2 + MaximumBatchingWindowInSeconds: 30 + + +Outputs: + Api: + Description: "API Gateway endpoint URL for Prod stage for Serialization function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/product/" + Function: + Description: "Serialization Lambda Function ARN" + Value: !GetAtt APIGatewayDeserializationFunction.Arn + DemoSqsQueue: + Description: "ARN for SQS queue" + Value: !GetAtt DemoSqsQueue.Arn \ No newline at end of file diff --git a/examples/powertools-examples-validation/README.md b/examples/powertools-examples-validation/README.md new file mode 100644 index 000000000..2f2028301 --- /dev/null +++ b/examples/powertools-examples-validation/README.md @@ -0,0 +1,26 @@ +# Powertools for AWS Lambda (Java) - Validation Example + +This project contains an example of Lambda function using the validation module of Powertools for AWS Lambda (Java). +For more information on this module, please refer to the [documentation](https://docs.powertools.aws.dev/lambda-java/utilities/validation/). + +The handler [InboundValidation](src/main/java/org/demo/validation/InboundValidation.java) validates incoming HTTP requests +received from the API gateway against [schema.json](src/main/resources/schema.json). + +## Deploy the sample application + +This sample is based on Serverless Application Model (SAM). To deploy it, check out the instructions for getting +started with SAM in [the examples directory](../README.md) + +## Test the application + +To test the validation, we can POST a JSON object shaped like our schema: +```bash + curl -X POST https://[REST-API-ID].execute-api.[REGION].amazonaws.com/Prod/hello/ -H "Content-Type: application/json" -d '{"id": 123,"name":"The Hitchhikers Guide to the Galaxy","price":10.99}' +``` + +If we break the schema - for instance, by removing one of the compulsory fields, +we will get an error back from our API and will see a `ValidationException` in the logs: + +```bash + sam logs --tail --stack-name $MY_STACK +``` diff --git a/examples/powertools-examples-validation/events/event.json b/examples/powertools-examples-validation/events/event.json new file mode 100644 index 000000000..3822fadaa --- /dev/null +++ b/examples/powertools-examples-validation/events/event.json @@ -0,0 +1,63 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } + } + \ No newline at end of file diff --git a/examples/powertools-examples-validation/pom.xml b/examples/powertools-examples-validation/pom.xml new file mode 100644 index 000000000..2fa8462a5 --- /dev/null +++ b/examples/powertools-examples-validation/pom.xml @@ -0,0 +1,116 @@ +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>software.amazon.lambda.examples</groupId> + <version>2.9.0</version> + <artifactId>powertools-examples-validation</artifactId> + <packaging>jar</packaging> + <name>Powertools for AWS Lambda (Java) - Examples - Validation</name> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <aspectj.version>1.9.20.1</aspectj.version> + </properties> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-validation</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>5.18.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.11.1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>5.13.1</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-validation</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <!-- Don't deploy the example --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/example/HelloWorldFunction/src/main/java/helloworld/AppValidation.java b/examples/powertools-examples-validation/src/main/java/org/demo/validation/InboundValidation.java similarity index 60% rename from example/HelloWorldFunction/src/main/java/helloworld/AppValidation.java rename to examples/powertools-examples-validation/src/main/java/org/demo/validation/InboundValidation.java index b66a0f864..d3b8e51e4 100644 --- a/example/HelloWorldFunction/src/main/java/helloworld/AppValidation.java +++ b/examples/powertools-examples-validation/src/main/java/org/demo/validation/InboundValidation.java @@ -1,11 +1,23 @@ -package helloworld; +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.demo.validation; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; -import software.amazon.lambda.powertools.validation.Validation; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -13,14 +25,16 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; +import software.amazon.lambda.powertools.validation.Validation; /** - * Handler for requests to Lambda function. + * Request handler for Lambda function which demonstrates validation of request message. */ -public class AppValidation implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { +public class InboundValidation implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { @Validation(inboundSchema = "classpath:/schema.json") - public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent apiGatewayProxyRequestEvent, + Context context) { Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/json"); @@ -44,7 +58,7 @@ public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEv private String getPageContents(String address) throws IOException { URL url = new URL(address); - try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"))) { return br.lines().collect(Collectors.joining(System.lineSeparator())); } } diff --git a/example/HelloWorldFunction/src/main/resources/schema.json b/examples/powertools-examples-validation/src/main/resources/schema.json similarity index 100% rename from example/HelloWorldFunction/src/main/resources/schema.json rename to examples/powertools-examples-validation/src/main/resources/schema.json diff --git a/examples/powertools-examples-validation/template.yaml b/examples/powertools-examples-validation/template.yaml new file mode 100644 index 000000000..f18a9fb00 --- /dev/null +++ b/examples/powertools-examples-validation/template.yaml @@ -0,0 +1,33 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + validation demo + +Globals: + Function: + Timeout: 20 + Runtime: java11 + MemorySize: 512 + Tracing: Active + + +Resources: + ValidationFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: org.demo.validation.InboundValidation::handleRequest + Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: post + +Outputs: + Api: + Description: "API Gateway endpoint URL for Prod stage for Validation function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + Function: + Description: "Validation Lambda Function ARN" + Value: !GetAtt ValidationFunction.Arn \ No newline at end of file diff --git a/license-header b/license-header new file mode 100644 index 000000000..5669f143f --- /dev/null +++ b/license-header @@ -0,0 +1,13 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ diff --git a/mkdocs.yml b/mkdocs.yml index 4e65aa01e..b52b88cca 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,20 +1,33 @@ -site_name: Lambda Powertools Java -site_description: AWS Lambda Powertools for Java +site_name: Powertools for AWS Lambda (Java) +site_description: Powertools for AWS Lambda (Java) site_author: Amazon Web Services +site_url: https://docs.aws.amazon.com/powertools/java/latest/ nav: - Homepage: index.md + - Usage patterns: usage-patterns.md - Changelog: changelog.md + - Upgrade Guide: upgrade.md - FAQs: FAQs.md + - Roadmap: roadmap.md - Core utilities: - core/logging.md - core/tracing.md - core/metrics.md - Utilities: + - utilities/idempotency.md - utilities/parameters.md - - utilities/sqs_large_message_handling.md - utilities/batch.md + - utilities/kafka.md + - utilities/large_messages.md - utilities/validation.md - utilities/custom_resources.md + - utilities/serialization.md + - Processes: + - processes/maintainers.md + - "Versioning policy": processes/versioning.md + - Resources: + - "llms.txt": ./llms.txt + - "llms.txt (full version)": ./llms-full.txt theme: name: material @@ -52,30 +65,74 @@ markdown_extensions: alternate_style: true - pymdownx.details - pymdownx.snippets: - base_path: '.' + base_path: "." check_paths: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - meta - toc: permalink: true toc_depth: 4 - attr_list -copyright: Copyright © 2021 Amazon Web Services +copyright: | + <div id="awsdocs-legal-zone-copyright"> + <a href="https://aws.amazon.com/privacy" target="_blank" rel="nofollow">Privacy</a> | + <a href="https://aws.amazon.com/terms/" target="_blank" rel="nofollow">Site terms</a> | + <span class="copyright"> + © 2025, Amazon Web Services, Inc. or its affiliates. All rights reserved. + </span> + </div> plugins: - git-revision-date - search - macros + - privacy + - llmstxt: + markdown_description: Powertools for AWS Lambda (Java) is a developer toolkit to implement Serverless best practices and increase developer velocity. It provides a suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. + full_output: llms-full.txt + sections: + Project Overview: + - index.md + - usage-patterns.md + - changelog.md + - FAQs.md + - roadmap.md + Core Utilities: + - core/logging.md + - core/metrics.md + - core/tracing.md + Utilities: + - utilities/idempotency.md + - utilities/parameters.md + - utilities/batch.md + - utilities/kafka.md + - utilities/large_messages.md + - utilities/validation.md + - utilities/custom_resources.md + - utilities/serialization.md + Processes: + - processes/maintainers.md + - processes/versioning.md extra_css: - stylesheets/extra.css extra_javascript: - - javascript/aws-amplify.min.js - - javascript/extra.js + - https://docs.powertools.aws.dev/shared/mermaid.min.js + - https://docs.aws.amazon.com/assets/js/awsdocs-boot.js extra: powertools: - version: 1.10.2 + version: 2.9.0 + version: + provider: mike + default: latest -repo_url: https://github.com/awslabs/aws-lambda-powertools-java -edit_uri: edit/master/docs +repo_url: https://github.com/aws-powertools/powertools-lambda-java +edit_uri: edit/main/docs diff --git a/pom.xml b/pom.xml index 10df15437..e6cf78b14 100644 --- a/pom.xml +++ b/pom.xml @@ -1,22 +1,49 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + <project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>software.amazon.lambda</groupId> <artifactId>powertools-parent</artifactId> - <version>1.10.2</version> + <version>2.9.0</version> <packaging>pom</packaging> - <name>AWS Lambda Powertools Java library Parent</name> + <name>Powertools for AWS Lambda (Java) - Parent</name> <description> A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. </description> + <scm> + <url>https://github.com/aws-powertools/powertools-lambda-java</url> + <connection>scm:git:git://github.com/aws-powertools/powertools-lambda-java.git</connection> + <developerConnection>scm:git:ssh://github.com:aws-powertools/powertools-lambda-java.git</developerConnection> + </scm> + <developers> + <developer> + <name>Powertools for AWS team</name> + <email>aws-powertools-maintainers@amazon.com</email> + <organization>Amazon Web Services Inc.</organization> + <organizationUrl>https://aws.amazon.com</organizationUrl> + </developer> + </developers> <url>https://aws.amazon.com/lambda/</url> <issueManagement> <system>GitHub Issues</system> - <url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url> + <url>https://github.com/aws-powertools/powertools-lambda-java/issues</url> </issueManagement> <licenses> <license> @@ -27,67 +54,107 @@ </licenses> <modules> - <module>powertools-core</module> + <module>powertools-common</module> + <module>powertools-serialization</module> + <module>powertools-kafka</module> <module>powertools-logging</module> + <module>powertools-logging/powertools-logging-log4j</module> + <module>powertools-logging/powertools-logging-logback</module> <module>powertools-tracing</module> - <module>powertools-sqs</module> <module>powertools-metrics</module> <module>powertools-parameters</module> <module>powertools-validation</module> - <module>powertools-test-suite</module> <module>powertools-cloudformation</module> + <module>powertools-idempotency</module> + <module>powertools-large-messages</module> + <module>powertools-e2e-tests</module> + <module>powertools-e2e-tests/handlers</module> + <module>powertools-batch</module> + <module>powertools-parameters/powertools-parameters-ssm</module> + <module>powertools-parameters/powertools-parameters-secrets</module> + <module>powertools-parameters/powertools-parameters-dynamodb</module> + <module>powertools-parameters/powertools-parameters-appconfig</module> + <module>powertools-parameters/powertools-parameters-tests</module> + <module>examples</module> </modules> - <scm> - <url>https://github.com/awslabs/aws-lambda-powertools-java.git</url> - </scm> - - <developers> - <developer> - <name>AWS Lambda Powertools team</name> - <organization>Amazon Web Services</organization> - <organizationUrl>https://aws.amazon.com/</organizationUrl> - </developer> - </developers> - <properties> - <maven.compiler.source>1.8</maven.compiler.source> - <maven.compiler.target>1.8</maven.compiler.target> - <log4j.version>2.17.1</log4j.version> - <jackson.version>2.13.1</jackson.version> - <aspectj.version>1.9.7</aspectj.version> - <aws.sdk.version>2.17.107</aws.sdk.version> - <aws.xray.recorder.version>2.10.0</aws.xray.recorder.version> - <payloadoffloading-common.version>1.1.1</payloadoffloading-common.version> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <maven.deploy.plugin.version>3.1.2</maven.deploy.plugin.version> + <log4j.version>2.25.3</log4j.version> + <slf4j.version>2.0.17</slf4j.version> + <jackson.version>2.20.1</jackson.version> + <aws.sdk.version>2.40.9</aws.sdk.version> + <aws.xray.recorder.version>2.20.0</aws.xray.recorder.version> + <payloadoffloading-common.version>2.2.0</payloadoffloading-common.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <lambda.core.version>1.2.1</lambda.core.version> - <lambda.events.version>3.11.0</lambda.events.version> - <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version> - <aspectj-maven-plugin.version>1.14.0</aspectj-maven-plugin.version> - <maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version> - <jacoco-maven-plugin.version>0.8.7</jacoco-maven-plugin.version> - <cobertura-maven-plugin.version>2.7</cobertura-maven-plugin.version> - <nexus-staging-maven-plugin.version>1.6.8</nexus-staging-maven-plugin.version> - <maven-javadoc-plugin.version>3.3.1</maven-javadoc-plugin.version> - <maven-source-plugin.version>3.2.1</maven-source-plugin.version> - <maven-gpg-plugin.version>3.0.1</maven-gpg-plugin.version> - <junit-jupiter.version>5.8.2</junit-jupiter.version> - <aws-embedded-metrics.version>1.0.6</aws-embedded-metrics.version> - <jmespath.version>0.5.1</jmespath.version> + <lambda.core.version>1.4.0</lambda.core.version> + <lambda.events.version>3.16.1</lambda.events.version> + <lambda.serial.version>1.1.6</lambda.serial.version> + <maven-compiler-plugin.version>3.14.1</maven-compiler-plugin.version> + <aspectj.version>1.9.7</aspectj.version> + <aspectj-maven-plugin.version>1.13.1</aspectj-maven-plugin.version> + <jacoco-maven-plugin.version>0.8.11</jacoco-maven-plugin.version> + <nexus-staging-maven-plugin.version>1.6.13</nexus-staging-maven-plugin.version> + <maven-javadoc-plugin.version>3.12.0</maven-javadoc-plugin.version> + <maven-source-plugin.version>3.3.1</maven-source-plugin.version> + <maven-gpg-plugin.version>3.2.1</maven-gpg-plugin.version> + <aspectj-maven-plugin.version>1.14.1</aspectj-maven-plugin.version> + <maven-surefire-plugin.version>3.5.4</maven-surefire-plugin.version> + <jacoco-maven-plugin.version>0.8.14</jacoco-maven-plugin.version> + <nexus-staging-maven-plugin.version>1.7.0</nexus-staging-maven-plugin.version> + <maven-javadoc-plugin.version>3.12.0</maven-javadoc-plugin.version> + <maven-source-plugin.version>3.3.1</maven-source-plugin.version> + <maven-gpg-plugin.version>3.2.8</maven-gpg-plugin.version> + <junit.version>5.14.0</junit.version> + <aws-embedded-metrics.version>4.1.2</aws-embedded-metrics.version> + <jmespath.version>0.6.0</jmespath.version> + <aws.sdk.v1.version>1.12.781</aws.sdk.v1.version> + <versions-maven-plugin.version>2.20.1</versions-maven-plugin.version> + <elastic.version>1.7.0</elastic.version> + <mockito.version>5.20.0</mockito.version> + <mockito-junit-jupiter.version>5.21.0</mockito-junit-jupiter.version> + <junit-pioneer.version>2.3.0</junit-pioneer.version> + <crac.version>1.5.0</crac.version> + + <!-- As we have a .mvn directory at the root of the project, this will evaluate to the root directory + regardless of where maven is run - sub-module, or root. --> + <project.rootdir>${maven.multiModuleProjectDirectory}</project.rootdir> </properties> <distributionManagement> <snapshotRepository> - <id>ossrh</id> - <url>https://aws.oss.sonatype.org/content/repositories/snapshots</url> + <id>central</id> + <url>https://central.sonatype.com/repository/maven-snapshots/</url> </snapshotRepository> </distributionManagement> + <!-- https://central.sonatype.org/publish/publish-portal-snapshots/#consuming-via-maven --> + <repositories> + <repository> + <name>Central Portal Snapshots</name> + <id>central-portal-snapshots</id> + <url>https://central.sonatype.com/repository/maven-snapshots/</url> + <releases> + <enabled>false</enabled> + </releases> + <snapshots> + <enabled>true</enabled> + </snapshots> + </repository> + </repositories> + <dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-core</artifactId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-serialization</artifactId> <version>${project.version}</version> </dependency> <dependency> @@ -95,6 +162,16 @@ <artifactId>powertools-logging</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-logback</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>software.amazon.lambda</groupId> <artifactId>powertools-sqs</artifactId> @@ -115,6 +192,11 @@ <artifactId>aws-lambda-java-events</artifactId> <version>${lambda.events.version}</version> </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-serialization</artifactId> + <version>${lambda.serial.version}</version> + </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> @@ -148,9 +230,11 @@ <version>${aspectj.version}</version> </dependency> <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-databind</artifactId> + <groupId>com.fasterxml.jackson</groupId> + <artifactId>jackson-bom</artifactId> <version>${jackson.version}</version> + <scope>import</scope> + <type>pom</type> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> @@ -162,6 +246,11 @@ <artifactId>log4j-slf4j-impl</artifactId> <version>${log4j.version}</version> </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j2-impl</artifactId> + <version>${log4j.version}</version> + </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> @@ -177,6 +266,21 @@ <artifactId>log4j-jcl</artifactId> <version>${log4j.version}</version> </dependency> + <dependency> + <groupId>co.elastic.logging</groupId> + <artifactId>logback-ecs-encoder</artifactId> + <version>${elastic.version}</version> + </dependency> + <dependency> + <groupId>org.crac</groupId> + <artifactId>crac</artifactId> + <version>${crac.version}</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>${slf4j.version}</version> + </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-xray-recorder-sdk-core</artifactId> @@ -202,71 +306,89 @@ <artifactId>aws-embedded-metrics</artifactId> <version>${aws-embedded-metrics.version}</version> </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.20.0</version> + </dependency> <!-- Test dependencies --> <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-api</artifactId> - <version>${junit-jupiter.version}</version> - <scope>test</scope> + <groupId>org.junit</groupId> + <artifactId>junit-bom</artifactId> + <version>${junit.version}</version> + <type>pom</type> + <scope>import</scope> </dependency> <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-engine</artifactId> - <version>${junit-jupiter.version}</version> + <groupId>org.junit-pioneer</groupId> + <artifactId>junit-pioneer</artifactId> + <version>${junit-pioneer.version}</version> <scope>test</scope> </dependency> <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-params</artifactId> - <version>${junit-jupiter.version}</version> + <groupId>org.aspectj</groupId> + <artifactId>aspectjweaver</artifactId> + <version>${aspectj.version}</version> <scope>test</scope> </dependency> <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> - <version>3.12.0</version> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <version>3.27.6</version> <scope>test</scope> + <exclusions> + <exclusion> + <groupId>net.bytebuddy</groupId> + <artifactId>byte-buddy</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> - <version>4.2.0</version> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <version>${slf4j.version}</version> <scope>test</scope> </dependency> <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-inline</artifactId> - <version>4.2.0</version> + <groupId>org.skyscreamer</groupId> + <artifactId>jsonassert</artifactId> + <version>1.5.3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.aspectj</groupId> - <artifactId>aspectjweaver</artifactId> + <artifactId>aspectjtools</artifactId> <version>${aspectj.version}</version> - <scope>test</scope> </dependency> <dependency> - <groupId>org.assertj</groupId> - <artifactId>assertj-core</artifactId> - <version>3.22.0</version> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>${mockito.version}</version> <scope>test</scope> </dependency> <dependency> - <groupId>org.skyscreamer</groupId> - <artifactId>jsonassert</artifactId> - <version>1.5.0</version> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <version>${mockito-junit-jupiter.version}</version> <scope>test</scope> </dependency> <dependency> - <groupId>org.aspectj</groupId> - <artifactId>aspectjtools</artifactId> - <version>${aspectj.version}</version> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <version>${mockito.version}</version> + <scope>test</scope> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-tests</artifactId> - <version>1.1.1</version> + <version>1.1.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.wiremock</groupId> + <artifactId>wiremock</artifactId> + <version>3.13.2</version> <scope>test</scope> </dependency> </dependencies> @@ -275,13 +397,21 @@ <build> <pluginManagement> <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>versions-maven-plugin</artifactId> + <version>${versions-maven-plugin.version}</version> + <configuration> + <generateBackupPoms>false</generateBackupPoms> + </configuration> + </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven-compiler-plugin.version}</version> </plugin> <plugin> - <groupId>org.codehaus.mojo</groupId> + <groupId>dev.aspectj</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>${aspectj-maven-plugin.version}</version> </plugin> @@ -295,11 +425,6 @@ <artifactId>jacoco-maven-plugin</artifactId> <version>${jacoco-maven-plugin.version}</version> </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>cobertura-maven-plugin</artifactId> - <version>${cobertura-maven-plugin.version}</version> - </plugin> <plugin> <groupId>org.sonatype.plugins</groupId> <artifactId>nexus-staging-maven-plugin</artifactId> @@ -320,6 +445,11 @@ <artifactId>maven-gpg-plugin</artifactId> <version>${maven-gpg-plugin.version}</version> </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>3.6.2</version> + </plugin> </plugins> </pluginManagement> <plugins> @@ -333,7 +463,15 @@ </configuration> </plugin> <plugin> - <groupId>org.codehaus.mojo</groupId> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-artifact-plugin</artifactId> + <version>3.6.1</version> + <configuration> + <reproducible>true</reproducible> + </configuration> + </plugin> + <plugin> + <groupId>dev.aspectj</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>${aspectj-maven-plugin.version}</version> <configuration> @@ -345,7 +483,6 @@ </configuration> <executions> <execution> - <phase>process-sources</phase> <goals> <goal>compile</goal> <goal>test-compile</goal> @@ -384,24 +521,12 @@ </executions> </plugin> <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>cobertura-maven-plugin</artifactId> - <configuration> - <formats> - <format>html</format> - <format>xml</format> - </formats> - <check /> - </configuration> - </plugin> - <plugin> - <groupId>org.sonatype.plugins</groupId> - <artifactId>nexus-staging-maven-plugin</artifactId> + <groupId>org.sonatype.central</groupId> + <artifactId>central-publishing-maven-plugin</artifactId> + <version>0.9.0</version> <extensions>true</extensions> <configuration> - <serverId>ossrh</serverId> - <nexusUrl>https://aws.oss.sonatype.org</nexusUrl> - <autoReleaseAfterClose>true</autoReleaseAfterClose> + <publishingServerId>central</publishingServerId> </configuration> </plugin> </plugins> @@ -409,7 +534,7 @@ <profiles> <profile> - <id>sign</id> + <id>release</id> <build> <plugins> <plugin> @@ -431,33 +556,6 @@ </execution> </executions> </plugin> - </plugins> - </build> - </profile> - <profile> - <id>build-with-spotbugs</id> - <activation> - <activeByDefault>true</activeByDefault> - </activation> - <build> - <plugins> - <plugin> - <groupId>com.github.spotbugs</groupId> - <artifactId>spotbugs-maven-plugin</artifactId> - <version>4.5.3.0</version> - <executions> - <execution> - <id>test</id> - <goals> - <goal>check</goal> - </goals> - </execution> - </executions> - <configuration> - <xmlOutput>true</xmlOutput> - <excludeFilterFile>../spotbugs-exclude.xml</excludeFilterFile> - </configuration> - </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> @@ -490,36 +588,49 @@ </build> </profile> <profile> - <id>build-without-spotbugs</id> + <id>build-with-spotbugs</id> + <activation> + <activeByDefault>false</activeByDefault> + </activation> <build> <plugins> <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-source-plugin</artifactId> + <groupId>com.github.spotbugs</groupId> + <artifactId>spotbugs-maven-plugin</artifactId> + <version>4.9.8.1</version> <executions> <execution> - <id>attach-sources</id> + <id>test</id> <goals> - <goal>jar-no-fork</goal> + <goal>check</goal> </goals> </execution> </executions> + <configuration> + <xmlOutput>true</xmlOutput> + <excludeFilterFile>${project.rootdir}/spotbugs-exclude.xml</excludeFilterFile> + </configuration> </plugin> + </plugins> + </build> + </profile> + <profile> + <id>jdk16</id> + <activation> + <jdk>[16,)</jdk> + </activation> + <build> + <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-javadoc-plugin</artifactId> + <artifactId>maven-surefire-plugin</artifactId> <configuration> - <doclint>none</doclint> - <detectJavaApiLink>false</detectJavaApiLink> + <argLine> + @{argLine} + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> </configuration> - <executions> - <execution> - <id>attach-javadocs</id> - <goals> - <goal>jar</goal> - </goals> - </execution> - </executions> </plugin> </plugins> </build> diff --git a/powertools-batch/pom.xml b/powertools-batch/pom.xml new file mode 100644 index 000000000..37cfdf7b2 --- /dev/null +++ b/powertools-batch/pom.xml @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parent</artifactId> + <version>2.9.0</version> + </parent> + + <description>A suite of utilities that makes batch message processing using AWS Lambda easier.</description> + <name>Powertools for AWS Lambda (Java) - Batch messages</name> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <!-- enforce multiple threads for parallel processing tests --> + <argLine> + -Djava.util.concurrent.ForkJoinPool.common.parallelism=4 + </argLine> + </configuration> + </plugin> + </plugins> + </build> + + <artifactId>powertools-batch</artifactId> + + <dependencies> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-serialization</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-tests</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/BatchMessageHandlerBuilder.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/BatchMessageHandlerBuilder.java new file mode 100644 index 000000000..4ed44453b --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/BatchMessageHandlerBuilder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch; + +import software.amazon.lambda.powertools.batch.builder.DynamoDbBatchMessageHandlerBuilder; +import software.amazon.lambda.powertools.batch.builder.KinesisBatchMessageHandlerBuilder; +import software.amazon.lambda.powertools.batch.builder.SqsBatchMessageHandlerBuilder; + +/** + * A builder-style interface we can use to build batch processing handlers for SQS, Kinesis Streams, + * and DynamoDB Streams batches. The batch processing handlers that are returned allow + * the user to easily process batches of messages, one-by-one, while offloading + * the common issues - failure handling, partial responses, deserialization - + * to the library. + * + * @see <a href="https://docs.powertools.aws.dev/lambda/java/utilities/batch/">Powertools for AWS Lambda (Java) Batch Documentation</a> + **/ +public class BatchMessageHandlerBuilder { + + /** + * Build an SQS-batch message handler. + * + * @return A fluent builder interface to continue the building + */ + public SqsBatchMessageHandlerBuilder withSqsBatchHandler() { + return new SqsBatchMessageHandlerBuilder(); + } + + /** + * Build a DynamoDB streams batch message handler. + * + * @return A fluent builder interface to continue the building + */ + public DynamoDbBatchMessageHandlerBuilder withDynamoDbBatchHandler() { + return new DynamoDbBatchMessageHandlerBuilder(); + } + + /** + * Builds a Kinesis streams batch message handler. + * + * @return a fluent builder interface to continue the building + */ + public KinesisBatchMessageHandlerBuilder withKinesisBatchHandler() { + return new KinesisBatchMessageHandlerBuilder(); + } +} diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/builder/AbstractBatchMessageHandlerBuilder.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/builder/AbstractBatchMessageHandlerBuilder.java new file mode 100644 index 000000000..9b0647770 --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/builder/AbstractBatchMessageHandlerBuilder.java @@ -0,0 +1,142 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch.builder; + +import com.amazonaws.services.lambda.runtime.Context; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; + +/** + * An abstract class to capture common arguments used across all the message-binding-specific batch processing + * builders. The builders provide a fluent interface to configure the batch processors. Any arguments specific + * to a particular batch binding can be added to the child builder. + * <p> + * We capture types for the various messages involved, so that we can provide an interface that makes + * sense for the concrete child. + * + * @param <T> The type of a single message in the batch + * @param <C> The type of the child builder. We need this to provide a fluent interface - see also getThis() + * @param <E> The type of the Lambda batch event + * @param <R> The type of the batch response we return to Lambda + */ +abstract class AbstractBatchMessageHandlerBuilder<T, C, E, R> { + protected BiConsumer<T, Throwable> failureHandler; + protected Consumer<T> successHandler; + + /** + * Provides an (Optional!) success handler. A success handler is invoked + * once for each message after it has been processed by the user-provided + * handler. + * <p> + * If the success handler throws, the item in the batch will be + * marked failed. + * + * @param handler The handler to invoke + */ + public C withSuccessHandler(Consumer<T> handler) { + this.successHandler = handler; + return getThis(); + } + + /** + * Provides an (Optional!) failure handler. A failure handler is invoked + * once for each message after it has failed to be processed by the + * user-provided handler. This gives the user's code a useful hook to do + * anything else that might have to be done in response to a failure - for + * instance, updating a metric, or writing a detailed log. + * <p> + * Please note that this method has nothing to do with the partial batch + * failure mechanism. Regardless of whether a failure handler is + * specified, partial batch failures and responses to the Lambda environment + * are handled by the batch utility separately. + * + * @param handler The handler to invoke on failure + */ + public C withFailureHandler(BiConsumer<T, Throwable> handler) { + this.failureHandler = handler; + return getThis(); + } + + /** + * Builds a BatchMessageHandler that can be used to process batches, given + * a user-defined handler to process each item in the batch. This variant + * takes a function that consumes a raw message and the Lambda context. This + * is useful for handlers that need access to the entire message object, not + * just the deserialized contents of the body. + * <p> + * Note: If you don't need the Lambda context, use the variant of this function + * that does not require it. + * + * @param handler Takes a raw message - the underlying AWS Events Library event - to process. + * For instance for SQS this would be an SQSMessage. + * @return A BatchMessageHandler for processing the batch + */ + public abstract BatchMessageHandler<E, R> buildWithRawMessageHandler(BiConsumer<T, Context> handler); + + /** + * Builds a BatchMessageHandler that can be used to process batches, given + * a user-defined handler to process each item in the batch. This variant + * takes a function that consumes a raw message and the Lambda context. This + * is useful for handlers that need access to the entire message object, not + * just the deserialized contents of the body. + * + * @param handler Takes a raw message - the underlying AWS Events Library event - to process. + * For instance for SQS this would be an SQSMessage. + * @return A BatchMessageHandler for processing the batch + */ + public BatchMessageHandler<E, R> buildWithRawMessageHandler(Consumer<T> handler) { + return buildWithRawMessageHandler((f, c) -> handler.accept(f)); + } + + /** + * Builds a BatchMessageHandler that can be used to process batches, given + * a user-defined handler to process each item in the batch. This variant + * takes a function that consumes the deserialized body of the given message + * and the lambda context. If deserialization fails, it will be treated as + * failure of the processing of that item in the batch. + * Note: If you don't need the Lambda context, use the variant of this function + * that does not require it. + * + * @param handler Processes the deserialized body of the message + * @return A BatchMessageHandler for processing the batch + */ + public abstract <M> BatchMessageHandler<E, R> buildWithMessageHandler(BiConsumer<M, Context> handler, + Class<M> messageClass); + + /** + * Builds a BatchMessageHandler that can be used to process batches, given + * a user-defined handler to process each item in the batch. This variant + * takes a function that consumes the deserialized body of the given message + * If deserialization fails, it will be treated as + * failure of the processing of that item in the batch. + * Note: If you don't need the Lambda context, use the variant of this function + * that does not require it. + * + * @param handler Processes the deserialized body of the message + * @return A BatchMessageHandler for processing the batch + */ + public <M> BatchMessageHandler<E, R> buildWithMessageHandler(Consumer<M> handler, Class<M> messageClass) { + return buildWithMessageHandler((f, c) -> handler.accept(f), messageClass); + } + + + /** + * Used to chain the fluent builder interface through the child classes. + * + * @return This + */ + protected abstract C getThis(); +} diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/builder/DynamoDbBatchMessageHandlerBuilder.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/builder/DynamoDbBatchMessageHandlerBuilder.java new file mode 100644 index 000000000..8513322b3 --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/builder/DynamoDbBatchMessageHandlerBuilder.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch.builder; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.DynamodbEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; +import java.util.function.BiConsumer; +import software.amazon.lambda.powertools.batch.exception.DeserializationNotSupportedException; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; +import software.amazon.lambda.powertools.batch.handler.DynamoDbBatchMessageHandler; + +/** + * Builds a batch processor for processing DynamoDB Streams batch events + **/ +public class DynamoDbBatchMessageHandlerBuilder + extends AbstractBatchMessageHandlerBuilder<DynamodbEvent.DynamodbStreamRecord, + DynamoDbBatchMessageHandlerBuilder, + DynamodbEvent, + StreamsEventResponse> { + + + @Override + public BatchMessageHandler<DynamodbEvent, StreamsEventResponse> buildWithRawMessageHandler( + BiConsumer<DynamodbEvent.DynamodbStreamRecord, Context> rawMessageHandler) { + return new DynamoDbBatchMessageHandler( + this.successHandler, + this.failureHandler, + rawMessageHandler); + } + + @Override + public <M> BatchMessageHandler<DynamodbEvent, StreamsEventResponse> buildWithMessageHandler( + BiConsumer<M, Context> handler, Class<M> messageClass) { + // The DDB provider streams DynamoDB changes, and therefore does not have a customizable payload + throw new DeserializationNotSupportedException(); + } + + @Override + protected DynamoDbBatchMessageHandlerBuilder getThis() { + return this; + } +} diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/builder/KinesisBatchMessageHandlerBuilder.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/builder/KinesisBatchMessageHandlerBuilder.java new file mode 100644 index 000000000..30bfcab65 --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/builder/KinesisBatchMessageHandlerBuilder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch.builder; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; +import java.util.function.BiConsumer; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; +import software.amazon.lambda.powertools.batch.handler.KinesisStreamsBatchMessageHandler; + +/** + * Builds a batch processor for processing Kinesis Streams batch events + */ +public class KinesisBatchMessageHandlerBuilder + extends AbstractBatchMessageHandlerBuilder<KinesisEvent.KinesisEventRecord, + KinesisBatchMessageHandlerBuilder, + KinesisEvent, + StreamsEventResponse> { + @Override + public BatchMessageHandler<KinesisEvent, StreamsEventResponse> buildWithRawMessageHandler( + BiConsumer<KinesisEvent.KinesisEventRecord, Context> rawMessageHandler) { + return new KinesisStreamsBatchMessageHandler<Void>( + rawMessageHandler, + null, + null, + successHandler, + failureHandler); + } + + @Override + public <M> BatchMessageHandler<KinesisEvent, StreamsEventResponse> buildWithMessageHandler( + BiConsumer<M, Context> messageHandler, Class<M> messageClass) { + return new KinesisStreamsBatchMessageHandler<>( + null, + messageHandler, + messageClass, + successHandler, + failureHandler); + } + + @Override + protected KinesisBatchMessageHandlerBuilder getThis() { + return this; + } +} diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/builder/SqsBatchMessageHandlerBuilder.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/builder/SqsBatchMessageHandlerBuilder.java new file mode 100644 index 000000000..ee2dc23f6 --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/builder/SqsBatchMessageHandlerBuilder.java @@ -0,0 +1,64 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch.builder; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import java.util.function.BiConsumer; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; +import software.amazon.lambda.powertools.batch.handler.SqsBatchMessageHandler; + +/** + * Builds a batch processor for the SQS event source. + */ +public class SqsBatchMessageHandlerBuilder extends AbstractBatchMessageHandlerBuilder<SQSEvent.SQSMessage, + SqsBatchMessageHandlerBuilder, + SQSEvent, + SQSBatchResponse> { + + + @Override + public BatchMessageHandler<SQSEvent, SQSBatchResponse> buildWithRawMessageHandler( + BiConsumer<SQSEvent.SQSMessage, Context> rawMessageHandler) { + return new SqsBatchMessageHandler<Void>( + null, + null, + rawMessageHandler, + successHandler, + failureHandler + ); + } + + @Override + public <M> BatchMessageHandler<SQSEvent, SQSBatchResponse> buildWithMessageHandler( + BiConsumer<M, Context> messageHandler, Class<M> messageClass) { + return new SqsBatchMessageHandler<>( + messageHandler, + messageClass, + null, + successHandler, + failureHandler + ); + } + + + @Override + protected SqsBatchMessageHandlerBuilder getThis() { + return this; + } + + +} diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/exception/DeserializationNotSupportedException.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/exception/DeserializationNotSupportedException.java new file mode 100644 index 000000000..6f3206c99 --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/exception/DeserializationNotSupportedException.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch.exception; + +/** + * Thrown by message handlers that do not support deserializing arbitrary payload + * contents. This is the case for instance with DynamoDB Streams, which stream + * changesets about user-defined data, but not the user-defined data models themselves. + */ +public class DeserializationNotSupportedException extends RuntimeException { + + public DeserializationNotSupportedException() { + super("This BatchMessageHandler has a fixed schema and does not support user-defined types"); + } + +} diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/BatchMessageHandler.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/BatchMessageHandler.java new file mode 100644 index 000000000..c63409e35 --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/BatchMessageHandler.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch.handler; + +import com.amazonaws.services.lambda.runtime.Context; + +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; + +/** + * The basic interface a batch message handler must meet. + * + * @param <E> The type of the Lambda batch event + * @param <R> The type of the lambda batch response + */ +public interface BatchMessageHandler<E, R> { + + /** + * Processes the given batch returning a partial batch + * response indicating the success and failure of individual + * messages within the batch. + * + * @param event The Lambda event containing the batch to process + * @param context The lambda context + * @return A partial batch response + */ + R processBatch(E event, Context context); + + /** + * Processes the given batch in parallel returning a partial batch + * response indicating the success and failure of individual + * messages within the batch. <br/> + * Note that parallel processing is not always better than sequential processing, + * and you should benchmark your code to determine the best approach for your use case. <br/> + * Also note that to get more threads available (more vCPUs), + * you need to increase the amount of memory allocated to your Lambda function. <br/> + + * + * @param event The Lambda event containing the batch to process + * @param context The lambda context + * @return A partial batch response + */ + R processBatchInParallel(E event, Context context); + + + /** + * Same as {@link #processBatchInParallel(Object, Context)} but with an option to provide custom {@link Executor} + * @param event The Lambda event containing the batch to process + * @param context The lambda context + * @param executor Custom executor to use for parallel processing + * @return A partial batch response + */ + R processBatchInParallel(E event, Context context, Executor executor); +} diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/DynamoDbBatchMessageHandler.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/DynamoDbBatchMessageHandler.java new file mode 100644 index 000000000..dbfdf63cd --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/DynamoDbBatchMessageHandler.java @@ -0,0 +1,152 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch.handler; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.DynamodbEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; + +import software.amazon.lambda.powertools.batch.internal.MultiThreadMDC; +import software.amazon.lambda.powertools.batch.internal.XRayTraceEntityPropagator; + +/** + * A batch message processor for DynamoDB Streams batches. + * + * @see <a href="https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-batchfailurereporting">DynamoDB Streams batch failure reporting</a> + */ +public class DynamoDbBatchMessageHandler implements BatchMessageHandler<DynamodbEvent, StreamsEventResponse> { + private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbBatchMessageHandler.class); + + private final Consumer<DynamodbEvent.DynamodbStreamRecord> successHandler; + private final BiConsumer<DynamodbEvent.DynamodbStreamRecord, Throwable> failureHandler; + private final BiConsumer<DynamodbEvent.DynamodbStreamRecord, Context> rawMessageHandler; + + public DynamoDbBatchMessageHandler(Consumer<DynamodbEvent.DynamodbStreamRecord> successHandler, + BiConsumer<DynamodbEvent.DynamodbStreamRecord, Throwable> failureHandler, + BiConsumer<DynamodbEvent.DynamodbStreamRecord, Context> rawMessageHandler) { + this.successHandler = successHandler; + this.failureHandler = failureHandler; + this.rawMessageHandler = rawMessageHandler; + } + + @Override + public StreamsEventResponse processBatch(DynamodbEvent event, Context context) { + List<StreamsEventResponse.BatchItemFailure> batchItemFailures = event.getRecords() + .stream() + .map(eventRecord -> processBatchItem(eventRecord, context)) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + + return StreamsEventResponse.builder().withBatchItemFailures(batchItemFailures).build(); + } + + @Override + public StreamsEventResponse processBatchInParallel(DynamodbEvent event, Context context) { + MultiThreadMDC multiThreadMDC = new MultiThreadMDC(); + Object capturedSubsegment = XRayTraceEntityPropagator.captureTraceEntity(); + + List<StreamsEventResponse.BatchItemFailure> batchItemFailures = event.getRecords() + .parallelStream() // Parallel processing + .map(eventRecord -> { + AtomicReference<Optional<StreamsEventResponse.BatchItemFailure>> result = new AtomicReference<>(); + + XRayTraceEntityPropagator.runWithEntity(capturedSubsegment, () -> { + multiThreadMDC.copyMDCToThread(Thread.currentThread().getName()); + try { + result.set(processBatchItem(eventRecord, context)); + } finally { + multiThreadMDC.removeThread(Thread.currentThread().getName()); + } + }); + + return result.get(); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + + return StreamsEventResponse.builder().withBatchItemFailures(batchItemFailures).build(); + } + + @Override + public StreamsEventResponse processBatchInParallel(DynamodbEvent event, Context context, Executor executor) { + MultiThreadMDC multiThreadMDC = new MultiThreadMDC(); + Object capturedSubsegment = XRayTraceEntityPropagator.captureTraceEntity(); + + List<StreamsEventResponse.BatchItemFailure> batchItemFailures = new ArrayList<>(); + List<CompletableFuture<Void>> futures = event.getRecords().stream() + .map(eventRecord -> CompletableFuture.runAsync(() -> { + XRayTraceEntityPropagator.runWithEntity(capturedSubsegment, () -> { + multiThreadMDC.copyMDCToThread(Thread.currentThread().getName()); + try { + Optional<StreamsEventResponse.BatchItemFailure> failureOpt = processBatchItem(eventRecord, + context); + failureOpt.ifPresent(batchItemFailures::add); + } finally { + multiThreadMDC.removeThread(Thread.currentThread().getName()); + } + }); + }, executor)) + .collect(Collectors.toList()); + futures.forEach(CompletableFuture::join); + return StreamsEventResponse.builder().withBatchItemFailures(batchItemFailures).build(); + } + + private Optional<StreamsEventResponse.BatchItemFailure> processBatchItem( + DynamodbEvent.DynamodbStreamRecord streamRecord, Context context) { + try { + LOGGER.debug("Processing item {}", streamRecord.getEventID()); + + rawMessageHandler.accept(streamRecord, context); + + // Report success if we have a handler + if (this.successHandler != null) { + this.successHandler.accept(streamRecord); + } + return Optional.empty(); + } catch (Exception e) { + String sequenceNumber = streamRecord.getDynamodb().getSequenceNumber(); + LOGGER.error("Error while processing record with id {}: {}, adding it to batch item failures", + sequenceNumber, e.getMessage()); + LOGGER.error("Error was", e); + + // Report failure if we have a handler + if (this.failureHandler != null) { + // A failing failure handler is no reason to fail the batch + try { + this.failureHandler.accept(streamRecord, e); + } catch (Exception e2) { + LOGGER.warn("failureHandler threw handling failure", e2); + } + } + return Optional + .of(StreamsEventResponse.BatchItemFailure.builder().withItemIdentifier(sequenceNumber).build()); + } + } +} diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/KinesisStreamsBatchMessageHandler.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/KinesisStreamsBatchMessageHandler.java new file mode 100644 index 000000000..f147578d4 --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/KinesisStreamsBatchMessageHandler.java @@ -0,0 +1,168 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch.handler; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; + +import software.amazon.lambda.powertools.batch.internal.MultiThreadMDC; +import software.amazon.lambda.powertools.batch.internal.XRayTraceEntityPropagator; +import software.amazon.lambda.powertools.utilities.EventDeserializer; + +/** + * A batch message processor for Kinesis Streams batch processing. + * <p> + * Refer to <a href="https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-batchfailurereporting">Kinesis Batch failure reporting</a> + * + * @param <M> The user-defined type of the Kinesis record payload + */ +public class KinesisStreamsBatchMessageHandler<M> implements BatchMessageHandler<KinesisEvent, StreamsEventResponse> { + private static final Logger LOGGER = LoggerFactory.getLogger(KinesisStreamsBatchMessageHandler.class); + + private final BiConsumer<KinesisEvent.KinesisEventRecord, Context> rawMessageHandler; + private final BiConsumer<M, Context> messageHandler; + private final Class<M> messageClass; + private final Consumer<KinesisEvent.KinesisEventRecord> successHandler; + private final BiConsumer<KinesisEvent.KinesisEventRecord, Throwable> failureHandler; + + public KinesisStreamsBatchMessageHandler(BiConsumer<KinesisEvent.KinesisEventRecord, Context> rawMessageHandler, + BiConsumer<M, Context> messageHandler, + Class<M> messageClass, + Consumer<KinesisEvent.KinesisEventRecord> successHandler, + BiConsumer<KinesisEvent.KinesisEventRecord, Throwable> failureHandler) { + + this.rawMessageHandler = rawMessageHandler; + this.messageHandler = messageHandler; + this.messageClass = messageClass; + this.successHandler = successHandler; + this.failureHandler = failureHandler; + } + + @Override + public StreamsEventResponse processBatch(KinesisEvent event, Context context) { + List<StreamsEventResponse.BatchItemFailure> batchItemFailures = event.getRecords() + .stream() + .map(eventRecord -> processBatchItem(eventRecord, context)) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + + return StreamsEventResponse.builder().withBatchItemFailures(batchItemFailures).build(); + } + + @Override + public StreamsEventResponse processBatchInParallel(KinesisEvent event, Context context) { + MultiThreadMDC multiThreadMDC = new MultiThreadMDC(); + Object capturedSubsegment = XRayTraceEntityPropagator.captureTraceEntity(); + + List<StreamsEventResponse.BatchItemFailure> batchItemFailures = event.getRecords() + .parallelStream() // Parallel processing + .map(eventRecord -> { + AtomicReference<Optional<StreamsEventResponse.BatchItemFailure>> result = new AtomicReference<>(); + + XRayTraceEntityPropagator.runWithEntity(capturedSubsegment, () -> { + multiThreadMDC.copyMDCToThread(Thread.currentThread().getName()); + try { + result.set(processBatchItem(eventRecord, context)); + } finally { + multiThreadMDC.removeThread(Thread.currentThread().getName()); + } + }); + + return result.get(); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + + return StreamsEventResponse.builder().withBatchItemFailures(batchItemFailures).build(); + } + + @Override + public StreamsEventResponse processBatchInParallel(KinesisEvent event, Context context, Executor executor) { + MultiThreadMDC multiThreadMDC = new MultiThreadMDC(); + Object capturedSubsegment = XRayTraceEntityPropagator.captureTraceEntity(); + + List<StreamsEventResponse.BatchItemFailure> batchItemFailures = new ArrayList<>(); + List<CompletableFuture<Void>> futures = event.getRecords().stream() + .map(eventRecord -> CompletableFuture.runAsync(() -> { + XRayTraceEntityPropagator.runWithEntity(capturedSubsegment, () -> { + multiThreadMDC.copyMDCToThread(Thread.currentThread().getName()); + try { + Optional<StreamsEventResponse.BatchItemFailure> failureOpt = processBatchItem(eventRecord, + context); + failureOpt.ifPresent(batchItemFailures::add); + } finally { + multiThreadMDC.removeThread(Thread.currentThread().getName()); + } + }); + }, executor)) + .collect(Collectors.toList()); + futures.forEach(CompletableFuture::join); + return StreamsEventResponse.builder().withBatchItemFailures(batchItemFailures).build(); + } + + private Optional<StreamsEventResponse.BatchItemFailure> processBatchItem( + KinesisEvent.KinesisEventRecord eventRecord, Context context) { + try { + LOGGER.debug("Processing item {}", eventRecord.getEventID()); + + if (this.rawMessageHandler != null) { + rawMessageHandler.accept(eventRecord, context); + } else { + M messageDeserialized = EventDeserializer.extractDataFrom(eventRecord).as(messageClass); + messageHandler.accept(messageDeserialized, context); + } + + // Report success if we have a handler + if (this.successHandler != null) { + this.successHandler.accept(eventRecord); + } + return Optional.empty(); + } catch (Exception e) { + String sequenceNumber = eventRecord.getEventID(); + LOGGER.error("Error while processing record with eventID {}: {}, adding it to batch item failures", + sequenceNumber, e.getMessage()); + LOGGER.error("Error was", e); + + // Report failure if we have a handler + if (this.failureHandler != null) { + // A failing failure handler is no reason to fail the batch + try { + this.failureHandler.accept(eventRecord, e); + } catch (Exception e2) { + LOGGER.warn("failureHandler threw handling failure", e2); + } + } + + return Optional.of(StreamsEventResponse.BatchItemFailure.builder() + .withItemIdentifier(eventRecord.getKinesis().getSequenceNumber()).build()); + } + } +} diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/SqsBatchMessageHandler.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/SqsBatchMessageHandler.java new file mode 100644 index 000000000..737c7cceb --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/SqsBatchMessageHandler.java @@ -0,0 +1,209 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch.handler; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; + +import software.amazon.lambda.powertools.batch.internal.MultiThreadMDC; +import software.amazon.lambda.powertools.batch.internal.XRayTraceEntityPropagator; +import software.amazon.lambda.powertools.utilities.EventDeserializer; + +/** + * A batch message processor for SQS batches. + * + * @param <M> The user-defined type of the message payload + * @see <a href="https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting">SQS Batch failure reporting</a> + */ +public class SqsBatchMessageHandler<M> implements BatchMessageHandler<SQSEvent, SQSBatchResponse> { + private static final Logger LOGGER = LoggerFactory.getLogger(SqsBatchMessageHandler.class); + + // The attribute on an SQS-FIFO message used to record the message group ID + // https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#sample-fifo-queues-message-event + private static final String MESSAGE_GROUP_ID_KEY = "MessageGroupId"; + + private final Class<M> messageClass; + private final BiConsumer<M, Context> messageHandler; + private final BiConsumer<SQSEvent.SQSMessage, Context> rawMessageHandler; + private final Consumer<SQSEvent.SQSMessage> successHandler; + private final BiConsumer<SQSEvent.SQSMessage, Throwable> failureHandler; + + public SqsBatchMessageHandler(BiConsumer<M, Context> messageHandler, Class<M> messageClass, + BiConsumer<SQSEvent.SQSMessage, Context> rawMessageHandler, + Consumer<SQSEvent.SQSMessage> successHandler, + BiConsumer<SQSEvent.SQSMessage, Throwable> failureHandler) { + this.messageHandler = messageHandler; + this.messageClass = messageClass; + this.rawMessageHandler = rawMessageHandler; + this.successHandler = successHandler; + this.failureHandler = failureHandler; + } + + @Override + public SQSBatchResponse processBatch(SQSEvent event, Context context) { + SQSBatchResponse response = SQSBatchResponse.builder().withBatchItemFailures(new ArrayList<>()).build(); + + // If we are working on a FIFO queue, when any message fails we should stop processing and return the + // rest of the batch as failed too. We use this variable to track when that has happened. + // https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting + final AtomicBoolean failWholeBatch = new AtomicBoolean(false); + + int messageCursor = 0; + for (; messageCursor < event.getRecords().size() && !failWholeBatch.get(); messageCursor++) { + SQSEvent.SQSMessage message = event.getRecords().get(messageCursor); + + String messageGroupId = message.getAttributes() != null ? message.getAttributes().get(MESSAGE_GROUP_ID_KEY) + : null; + + processBatchItem(message, context).ifPresent(batchItemFailure -> { + response.getBatchItemFailures().add(batchItemFailure); + if (messageGroupId != null) { + failWholeBatch.set(true); + LOGGER.info( + "A message in a batch with messageGroupId {} and messageId {} failed; failing the rest of the batch too", + messageGroupId, message.getMessageId()); + } + }); + } + + if (failWholeBatch.get()) { + // Add the remaining messages to the batch item failures + event.getRecords() + .subList(messageCursor, event.getRecords().size()) + .forEach(message -> response.getBatchItemFailures() + .add(SQSBatchResponse.BatchItemFailure.builder().withItemIdentifier(message.getMessageId()) + .build())); + } + return response; + } + + @Override + public SQSBatchResponse processBatchInParallel(SQSEvent event, Context context) { + if (isFIFOEnabled(event)) { + throw new UnsupportedOperationException( + "FIFO queues are not supported in parallel mode, use the processBatch method instead"); + } + + MultiThreadMDC multiThreadMDC = new MultiThreadMDC(); + Object capturedSubsegment = XRayTraceEntityPropagator.captureTraceEntity(); + + List<SQSBatchResponse.BatchItemFailure> batchItemFailures = event.getRecords() + .parallelStream() // Parallel processing + .map(sqsMessage -> { + AtomicReference<Optional<SQSBatchResponse.BatchItemFailure>> result = new AtomicReference<>(); + + XRayTraceEntityPropagator.runWithEntity(capturedSubsegment, () -> { + multiThreadMDC.copyMDCToThread(Thread.currentThread().getName()); + try { + result.set(processBatchItem(sqsMessage, context)); + } finally { + multiThreadMDC.removeThread(Thread.currentThread().getName()); + } + }); + + return result.get(); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + + return SQSBatchResponse.builder().withBatchItemFailures(batchItemFailures).build(); + } + + @Override + public SQSBatchResponse processBatchInParallel(SQSEvent event, Context context, Executor executor) { + if (isFIFOEnabled(event)) { + throw new UnsupportedOperationException( + "FIFO queues are not supported in parallel mode, use the processBatch method instead"); + } + + MultiThreadMDC multiThreadMDC = new MultiThreadMDC(); + Object capturedSubsegment = XRayTraceEntityPropagator.captureTraceEntity(); + + List<SQSBatchResponse.BatchItemFailure> batchItemFailures = new ArrayList<>(); + List<CompletableFuture<Void>> futures = event.getRecords().stream() + .map(eventRecord -> CompletableFuture.runAsync(() -> { + XRayTraceEntityPropagator.runWithEntity(capturedSubsegment, () -> { + multiThreadMDC.copyMDCToThread(Thread.currentThread().getName()); + try { + Optional<SQSBatchResponse.BatchItemFailure> failureOpt = processBatchItem(eventRecord, + context); + failureOpt.ifPresent(batchItemFailures::add); + } finally { + multiThreadMDC.removeThread(Thread.currentThread().getName()); + } + }); + }, executor)) + .collect(Collectors.toList()); + futures.forEach(CompletableFuture::join); + + return SQSBatchResponse.builder().withBatchItemFailures(batchItemFailures).build(); + } + + private Optional<SQSBatchResponse.BatchItemFailure> processBatchItem(SQSEvent.SQSMessage message, Context context) { + try { + LOGGER.debug("Processing message {}", message.getMessageId()); + + if (this.rawMessageHandler != null) { + rawMessageHandler.accept(message, context); + } else { + M messageDeserialized = EventDeserializer.extractDataFrom(message).as(messageClass); + messageHandler.accept(messageDeserialized, context); + } + + // Report success if we have a handler + if (this.successHandler != null) { + this.successHandler.accept(message); + } + return Optional.empty(); + } catch (Exception e) { + LOGGER.error("Error while processing message with messageId {}: {}, adding it to batch item failures", + message.getMessageId(), e.getMessage()); + LOGGER.error("Error was", e); + + // Report failure if we have a handler + if (this.failureHandler != null) { + // A failing failure handler is no reason to fail the batch + try { + this.failureHandler.accept(message, e); + } catch (Exception e2) { + LOGGER.warn("failureHandler threw handling failure", e2); + } + } + return Optional.of(SQSBatchResponse.BatchItemFailure.builder().withItemIdentifier(message.getMessageId()) + .build()); + } + } + + private boolean isFIFOEnabled(SQSEvent sqsEvent) { + return !sqsEvent.getRecords().isEmpty() + && sqsEvent.getRecords().get(0).getAttributes().get(MESSAGE_GROUP_ID_KEY) != null; + } +} diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/internal/BatchUserAgentInterceptor.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/internal/BatchUserAgentInterceptor.java new file mode 100644 index 000000000..f49dbe5c5 --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/internal/BatchUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.batch.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-batch module is on the classpath. + */ +public final class BatchUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("batch"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/internal/MultiThreadMDC.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/internal/MultiThreadMDC.java new file mode 100644 index 000000000..b2b85044b --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/internal/MultiThreadMDC.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch.internal; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +/** + * MDC (SLF4J) is not passed to other threads (ThreadLocal). + * This class permits to manually copy the MDC to a given thread. + */ +public class MultiThreadMDC { + + private static final Logger LOGGER = LoggerFactory.getLogger(MultiThreadMDC.class); + + private final List<String> mdcAwareThreads = new ArrayList<>(); + private final Map<String, String> contextMap; + + public MultiThreadMDC() { + mdcAwareThreads.add("main"); + contextMap = MDC.getCopyOfContextMap(); + } + + public void copyMDCToThread(String thread) { + if (!mdcAwareThreads.contains(thread)) { + LOGGER.debug("Copy MDC to thread {}", thread); + MDC.setContextMap(contextMap); + mdcAwareThreads.add(thread); + } + } + + public void removeThread(String thread) { + if (mdcAwareThreads.contains(thread)) { + LOGGER.debug("Removing thread {}", thread); + mdcAwareThreads.remove(thread); + } + } +} diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/internal/XRayTraceEntityPropagator.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/internal/XRayTraceEntityPropagator.java new file mode 100644 index 000000000..2858f4756 --- /dev/null +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/internal/XRayTraceEntityPropagator.java @@ -0,0 +1,83 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch.internal; + +import java.lang.reflect.Method; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility class to propagate X-Ray trace entity context to worker threads using reflection. + * Reflection is used to avoid taking a dependency on X-RAY SDK. + */ +public final class XRayTraceEntityPropagator { + private static final Logger LOGGER = LoggerFactory.getLogger(XRayTraceEntityPropagator.class); + private static final boolean XRAY_AVAILABLE; + private static final Method GET_TRACE_ENTITY_METHOD; + + // We do the more "expensive" Class.forName in this static block to detect exactly once at import time if X-RAY + // is available or not. Subsequent <method>.invoke() are very fast on modern JDKs. + static { + Method method = null; + boolean available = false; + + try { + Class<?> awsXRayClass = Class.forName("com.amazonaws.xray.AWSXRay"); + method = awsXRayClass.getMethod("getTraceEntity"); + available = true; + LOGGER.debug("X-Ray SDK detected. Trace context will be propagated to worker threads."); + } catch (ClassNotFoundException | NoSuchMethodException e) { + LOGGER.debug("X-Ray SDK not detected. Trace context propagation disabled"); + } + + GET_TRACE_ENTITY_METHOD = method; + XRAY_AVAILABLE = available; + } + + private XRayTraceEntityPropagator() { + // Utility class + } + + public static Object captureTraceEntity() { + if (!XRAY_AVAILABLE) { + return null; + } + + try { + return GET_TRACE_ENTITY_METHOD.invoke(null); + } catch (Exception e) { + // We don't want to break batch processing if this fails. + LOGGER.warn("Failed to capture trace entity.", e); + return null; + } + } + + // See https://docs.aws.amazon.com/xray/latest/devguide/scorekeep-workerthreads.html + public static void runWithEntity(Object traceEntity, Runnable runnable) { + if (!XRAY_AVAILABLE || traceEntity == null) { + runnable.run(); + return; + } + + try { + traceEntity.getClass().getMethod("run", Runnable.class).invoke(traceEntity, runnable); + } catch (Exception e) { + // We don't want to break batch processing if this fails. + LOGGER.warn("Failed to run with trace entity, falling back to direct execution.", e); + runnable.run(); + } + } +} diff --git a/powertools-batch/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-batch/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..51764f87f --- /dev/null +++ b/powertools-batch/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.batch.internal.BatchUserAgentInterceptor diff --git a/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/DdbBatchProcessorTest.java b/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/DdbBatchProcessorTest.java new file mode 100644 index 000000000..51131ae3f --- /dev/null +++ b/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/DdbBatchProcessorTest.java @@ -0,0 +1,241 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.DynamodbEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; +import com.amazonaws.services.lambda.runtime.tests.annotations.Event; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.mockito.Mock; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; + +class DdbBatchProcessorTest { + + @Mock + private Context context; + + private final List<String> threadList = Collections.synchronizedList(new ArrayList<>()); + + @AfterEach + public void clear() { + threadList.clear(); + } + + private void processMessageSucceeds(DynamodbEvent.DynamodbStreamRecord record, Context context) { + // Great success + // Printing to satisfy pmd_analyse + System.out.println("Great success, record: " + record + ", context: " + context); + } + + private void processMessageFailsForFixedMessage(DynamodbEvent.DynamodbStreamRecord record, Context context) { + if (record.getDynamodb().getSequenceNumber().equals("4421584500000000017450439091")) { + throw new RuntimeException("fake exception"); + } + } + + private void processMessageInParallelSucceeds(DynamodbEvent.DynamodbStreamRecord record, Context context) { + String thread = Thread.currentThread().getName(); + if (!threadList.contains(thread)) { + threadList.add(thread); + } + try { + Thread.sleep(500); // simulate some processing + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private void processMessageInParallelFailsForFixedMessage(DynamodbEvent.DynamodbStreamRecord record, Context context) { + String thread = Thread.currentThread().getName(); + if (!threadList.contains(thread)) { + threadList.add(thread); + } + try { + Thread.sleep(500); // simulate some processing + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + if (record.getDynamodb().getSequenceNumber().equals("4421584500000000017450439091")) { + throw new RuntimeException("fake exception"); + } + } + + private StreamsEventResponse testParallelBatchExecution(DynamodbEvent event, + BiConsumer<DynamodbEvent.DynamodbStreamRecord, Context> messageHandler, + Executor executor) { + // Arrange + BatchMessageHandler<DynamodbEvent, StreamsEventResponse> handler = new BatchMessageHandlerBuilder() + .withDynamoDbBatchHandler() + .buildWithRawMessageHandler(messageHandler); + + // Act + StreamsEventResponse dynamodbBatchResponse; + if (executor == null) { + dynamodbBatchResponse = handler.processBatchInParallel(event, context); + } else { + dynamodbBatchResponse = handler.processBatchInParallel(event, context, executor); + } + + return dynamodbBatchResponse; + } + + @ParameterizedTest + @Event(value = "dynamo_event.json", type = DynamodbEvent.class) + void batchProcessingSucceedsAndReturns(DynamodbEvent event) { + // Arrange + BatchMessageHandler<DynamodbEvent, StreamsEventResponse> handler = new BatchMessageHandlerBuilder() + .withDynamoDbBatchHandler() + .buildWithRawMessageHandler(this::processMessageSucceeds); + + // Act + StreamsEventResponse dynamodbBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(dynamodbBatchResponse.getBatchItemFailures()).isEmpty(); + } + + @ParameterizedTest + @Event(value = "dynamo_event_big.json", type = DynamodbEvent.class) + void parallelBatchProcessingSucceedsAndReturns(DynamodbEvent event) { + StreamsEventResponse dynamodbBatchResponse = testParallelBatchExecution(event, this::processMessageInParallelSucceeds, null); + + // Assert + assertThat(dynamodbBatchResponse.getBatchItemFailures()).isEmpty(); + assertThat(threadList).hasSizeGreaterThan(1); + } + + @ParameterizedTest + @Event(value = "dynamo_event.json", type = DynamodbEvent.class) + void shouldAddMessageToBatchFailure_whenException_withMessage(DynamodbEvent event) { + // Arrange + BatchMessageHandler<DynamodbEvent, StreamsEventResponse> handler = new BatchMessageHandlerBuilder() + .withDynamoDbBatchHandler() + .buildWithRawMessageHandler(this::processMessageFailsForFixedMessage); + + // Act + StreamsEventResponse dynamodbBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(dynamodbBatchResponse.getBatchItemFailures()).hasSize(1); + StreamsEventResponse.BatchItemFailure batchItemFailure = dynamodbBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("4421584500000000017450439091"); + } + + @ParameterizedTest + @Event(value = "dynamo_event_big.json", type = DynamodbEvent.class) + void parallelBatchProcessing_shouldAddMessageToBatchFailure_whenException_withMessage(DynamodbEvent event) { + StreamsEventResponse dynamodbBatchResponse = testParallelBatchExecution(event, this::processMessageInParallelFailsForFixedMessage, null); + + // Assert + assertThat(dynamodbBatchResponse.getBatchItemFailures()).hasSize(1); + StreamsEventResponse.BatchItemFailure batchItemFailure = dynamodbBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("4421584500000000017450439091"); + assertThat(threadList).hasSizeGreaterThan(1); + } + + @ParameterizedTest + @Event(value = "dynamo_event.json", type = DynamodbEvent.class) + void failingFailureHandlerShouldntFailBatch(DynamodbEvent event) { + // Arrange + AtomicBoolean wasCalledAndFailed = new AtomicBoolean(false); + BatchMessageHandler<DynamodbEvent, StreamsEventResponse> handler = new BatchMessageHandlerBuilder() + .withDynamoDbBatchHandler() + .withFailureHandler((m, e) -> { + if (m.getDynamodb().getSequenceNumber().equals("4421584500000000017450439091")) { + wasCalledAndFailed.set(true); + throw new RuntimeException("Success handler throws"); + } + }) + .buildWithRawMessageHandler(this::processMessageFailsForFixedMessage); + + // Act + StreamsEventResponse dynamodbBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(dynamodbBatchResponse).isNotNull(); + assertThat(dynamodbBatchResponse.getBatchItemFailures()).hasSize(1); + assertThat(wasCalledAndFailed.get()).isTrue(); + StreamsEventResponse.BatchItemFailure batchItemFailure = dynamodbBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("4421584500000000017450439091"); + } + + @ParameterizedTest + @Event(value = "dynamo_event.json", type = DynamodbEvent.class) + void failingSuccessHandlerShouldntFailBatchButShouldFailMessage(DynamodbEvent event) { + // Arrange + AtomicBoolean wasCalledAndFailed = new AtomicBoolean(false); + BatchMessageHandler<DynamodbEvent, StreamsEventResponse> handler = new BatchMessageHandlerBuilder() + .withDynamoDbBatchHandler() + .withSuccessHandler((e) -> { + if (e.getDynamodb().getSequenceNumber().equals("4421584500000000017450439091")) { + wasCalledAndFailed.set(true); + throw new RuntimeException("Success handler throws"); + } + }) + .buildWithRawMessageHandler(this::processMessageSucceeds); + + // Act + StreamsEventResponse dynamodbBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(dynamodbBatchResponse).isNotNull(); + assertThat(dynamodbBatchResponse.getBatchItemFailures()).hasSize(1); + assertThat(wasCalledAndFailed.get()).isTrue(); + StreamsEventResponse.BatchItemFailure batchItemFailure = dynamodbBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("4421584500000000017450439091"); + } + + @ParameterizedTest + @Event(value = "dynamo_event_big.json", type = DynamodbEvent.class) + void parallelBatchProcessingWithExecutorSucceedsAndReturns(DynamodbEvent event) { + ExecutorService executor = Executors.newFixedThreadPool(2); + + StreamsEventResponse dynamodbBatchResponse = testParallelBatchExecution(event, this::processMessageInParallelSucceeds, executor); + executor.shutdown(); + + // Assert + assertThat(dynamodbBatchResponse.getBatchItemFailures()).isEmpty(); + assertThat(threadList).hasSizeGreaterThan(1); + } + + @ParameterizedTest + @Event(value = "dynamo_event_big.json", type = DynamodbEvent.class) + void parallelBatchProcessingWithExecutor_shouldAddMessageToBatchFailure_whenException_withMessage(DynamodbEvent event) { + ExecutorService executor = Executors.newFixedThreadPool(2); + + StreamsEventResponse dynamodbBatchResponse = testParallelBatchExecution(event, this::processMessageInParallelFailsForFixedMessage, executor); + executor.shutdown(); + + // Assert + assertThat(dynamodbBatchResponse.getBatchItemFailures()).hasSize(1); + StreamsEventResponse.BatchItemFailure batchItemFailure = dynamodbBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("4421584500000000017450439091"); + assertThat(threadList).hasSizeGreaterThan(1); + } + +} diff --git a/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/KinesisBatchProcessorTest.java b/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/KinesisBatchProcessorTest.java new file mode 100644 index 000000000..32acde6f0 --- /dev/null +++ b/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/KinesisBatchProcessorTest.java @@ -0,0 +1,271 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; +import com.amazonaws.services.lambda.runtime.tests.annotations.Event; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.mockito.Mock; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; +import software.amazon.lambda.powertools.batch.model.Product; + +class KinesisBatchProcessorTest { + + @Mock + private Context context; + + private final List<String> threadList = Collections.synchronizedList(new ArrayList<>()); + + @AfterEach + public void clear() { + threadList.clear(); + } + + private void processMessageSucceeds(KinesisEvent.KinesisEventRecord record, Context context) { + // Great success + } + + private void processMessageFailsForFixedMessage(KinesisEvent.KinesisEventRecord record, Context context) { + if (record.getKinesis().getSequenceNumber() + .equals("49545115243490985018280067714973144582180062593244200961")) { + throw new RuntimeException("fake exception"); + } + } + + private void processMessageInParallelSucceeds(KinesisEvent.KinesisEventRecord record, Context context) { + String thread = Thread.currentThread().getName(); + if (!threadList.contains(thread)) { + threadList.add(thread); + } + try { + Thread.sleep(500); // simulate some processing + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private void processMessageInParallelFailsForFixedMessage(KinesisEvent.KinesisEventRecord record, Context context) { + String thread = Thread.currentThread().getName(); + if (!threadList.contains(thread)) { + threadList.add(thread); + } + try { + Thread.sleep(500); // simulate some processing + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + if (record.getKinesis().getSequenceNumber() + .equals("49545115243490985018280067714973144582180062593244200961")) { + throw new RuntimeException("fake exception"); + } + } + + private StreamsEventResponse testParallelBatchExecution(KinesisEvent event, + BiConsumer<KinesisEvent.KinesisEventRecord, Context> messageHandler, + Executor executor) { + // Arrange + BatchMessageHandler<KinesisEvent, StreamsEventResponse> handler = new BatchMessageHandlerBuilder() + .withKinesisBatchHandler() + .buildWithRawMessageHandler(messageHandler); + + // Act + StreamsEventResponse kinesisBatchResponse; + if (executor == null) { + kinesisBatchResponse = handler.processBatchInParallel(event, context); + } else { + kinesisBatchResponse = handler.processBatchInParallel(event, context, executor); + } + + return kinesisBatchResponse; + } + + // A handler that throws an exception for _one_ of the deserialized products in the same messages + public void processMessageFailsForFixedProduct(Product product, Context context) { + if (product.getId() == 1234) { + throw new RuntimeException("fake exception"); + } + } + + @ParameterizedTest + @Event(value = "kinesis_event.json", type = KinesisEvent.class) + void batchProcessingSucceedsAndReturns(KinesisEvent event) { + // Arrange + BatchMessageHandler<KinesisEvent, StreamsEventResponse> handler = new BatchMessageHandlerBuilder() + .withKinesisBatchHandler() + .buildWithRawMessageHandler(this::processMessageSucceeds); + + // Act + StreamsEventResponse kinesisBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(kinesisBatchResponse.getBatchItemFailures()).isEmpty(); + } + + @ParameterizedTest + @Event(value = "kinesis_event_big.json", type = KinesisEvent.class) + void batchProcessingInParallelSucceedsAndReturns(KinesisEvent event) { + StreamsEventResponse kinesisBatchResponse = testParallelBatchExecution(event, this::processMessageInParallelSucceeds, null); + + // Assert + assertThat(kinesisBatchResponse.getBatchItemFailures()).isEmpty(); + assertThat(threadList).hasSizeGreaterThan(1); + } + + @ParameterizedTest + @Event(value = "kinesis_event.json", type = KinesisEvent.class) + void shouldAddMessageToBatchFailure_whenException_withMessage(KinesisEvent event) { + // Arrange + BatchMessageHandler<KinesisEvent, StreamsEventResponse> handler = new BatchMessageHandlerBuilder() + .withKinesisBatchHandler() + .buildWithRawMessageHandler(this::processMessageFailsForFixedMessage); + + // Act + StreamsEventResponse kinesisBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(kinesisBatchResponse.getBatchItemFailures()).hasSize(1); + StreamsEventResponse.BatchItemFailure batchItemFailure = kinesisBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo( + "49545115243490985018280067714973144582180062593244200961"); + } + + @ParameterizedTest + @Event(value = "kinesis_event_big.json", type = KinesisEvent.class) + void batchProcessingInParallel_shouldAddMessageToBatchFailure_whenException_withMessage(KinesisEvent event) { + StreamsEventResponse kinesisBatchResponse = testParallelBatchExecution(event, this::processMessageInParallelFailsForFixedMessage, null); + + // Assert + assertThat(kinesisBatchResponse.getBatchItemFailures()).hasSize(1); + StreamsEventResponse.BatchItemFailure batchItemFailure = kinesisBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo( + "49545115243490985018280067714973144582180062593244200961"); + assertThat(threadList).hasSizeGreaterThan(1); + } + + @ParameterizedTest + @Event(value = "kinesis_event.json", type = KinesisEvent.class) + void shouldAddMessageToBatchFailure_whenException_withProduct(KinesisEvent event) { + // Arrange + BatchMessageHandler<KinesisEvent, StreamsEventResponse> handler = new BatchMessageHandlerBuilder() + .withKinesisBatchHandler() + .buildWithMessageHandler(this::processMessageFailsForFixedProduct, Product.class); + + // Act + StreamsEventResponse kinesisBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(kinesisBatchResponse.getBatchItemFailures()).hasSize(1); + StreamsEventResponse.BatchItemFailure batchItemFailure = kinesisBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo( + "49545115243490985018280067714973144582180062593244200961"); + } + + @ParameterizedTest + @Event(value = "kinesis_event.json", type = KinesisEvent.class) + void failingFailureHandlerShouldntFailBatch(KinesisEvent event) { + // Arrange + AtomicBoolean wasCalled = new AtomicBoolean(false); + BatchMessageHandler<KinesisEvent, StreamsEventResponse> handler = new BatchMessageHandlerBuilder() + .withKinesisBatchHandler() + .withFailureHandler((e, ex) -> { + wasCalled.set(true); + throw new RuntimeException("Well, this doesn't look great"); + }) + .buildWithMessageHandler(this::processMessageFailsForFixedProduct, Product.class); + + // Act + StreamsEventResponse kinesisBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(kinesisBatchResponse).isNotNull(); + assertThat(kinesisBatchResponse.getBatchItemFailures()).hasSize(1); + assertThat(wasCalled.get()).isTrue(); + StreamsEventResponse.BatchItemFailure batchItemFailure = kinesisBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo( + "49545115243490985018280067714973144582180062593244200961"); + } + + @ParameterizedTest + @Event(value = "kinesis_event.json", type = KinesisEvent.class) + void failingSuccessHandlerShouldntFailBatchButShouldFailMessage(KinesisEvent event) { + // Arrange + AtomicBoolean wasCalledAndFailed = new AtomicBoolean(false); + BatchMessageHandler<KinesisEvent, StreamsEventResponse> handler = new BatchMessageHandlerBuilder() + .withKinesisBatchHandler() + .withSuccessHandler((e) -> { + if (e.getKinesis().getSequenceNumber() + .equals("49545115243490985018280067714973144582180062593244200961")) { + wasCalledAndFailed.set(true); + throw new RuntimeException("Success handler throws"); + } + }) + .buildWithRawMessageHandler(this::processMessageSucceeds); + + // Act + StreamsEventResponse kinesisBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(kinesisBatchResponse).isNotNull(); + assertThat(kinesisBatchResponse.getBatchItemFailures()).hasSize(1); + assertThat(wasCalledAndFailed.get()).isTrue(); + StreamsEventResponse.BatchItemFailure batchItemFailure = kinesisBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo( + "49545115243490985018280067714973144582180062593244200961"); + } + + @ParameterizedTest + @Event(value = "kinesis_event_big.json", type = KinesisEvent.class) + void batchProcessingInParallelWithExecutorSucceedsAndReturns(KinesisEvent event) { + ExecutorService executor = Executors.newFixedThreadPool(2); + + StreamsEventResponse kinesisBatchResponse = testParallelBatchExecution(event, this::processMessageInParallelSucceeds, executor); + executor.shutdown(); + + // Assert + assertThat(kinesisBatchResponse.getBatchItemFailures()).isEmpty(); + assertThat(threadList).hasSizeGreaterThan(1); + } + + @ParameterizedTest + @Event(value = "kinesis_event_big.json", type = KinesisEvent.class) + void batchProcessingInParallelWithExecutor_shouldAddMessageToBatchFailure_whenException_withMessage(KinesisEvent event) { + ExecutorService executor = Executors.newFixedThreadPool(2); + + StreamsEventResponse kinesisBatchResponse = testParallelBatchExecution(event, this::processMessageInParallelFailsForFixedMessage, executor); + executor.shutdown(); + + // Assert + assertThat(kinesisBatchResponse.getBatchItemFailures()).hasSize(1); + StreamsEventResponse.BatchItemFailure batchItemFailure = kinesisBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo( + "49545115243490985018280067714973144582180062593244200961"); + assertThat(threadList).hasSizeGreaterThan(1); + } + +} diff --git a/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/SQSBatchProcessorTest.java b/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/SQSBatchProcessorTest.java new file mode 100644 index 000000000..f13196fc4 --- /dev/null +++ b/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/SQSBatchProcessorTest.java @@ -0,0 +1,280 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.tests.annotations.Event; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.mockito.Mock; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; +import software.amazon.lambda.powertools.batch.model.Product; + +class SQSBatchProcessorTest { + @Mock + private Context context; + + private final List<String> threadList = Collections.synchronizedList(new ArrayList<>()); + + @AfterEach + public void clear() { + threadList.clear(); + } + + // A handler that works + private void processMessageSucceeds(SQSEvent.SQSMessage sqsMessage) { + } + + private void processMessageInParallelSucceeds(SQSEvent.SQSMessage sqsMessage, Context context) { + String thread = Thread.currentThread().getName(); + if (!threadList.contains(thread)) { + threadList.add(thread); + } + try { + Thread.sleep(500); // simulate some processing + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + // A handler that throws an exception for _one_ of the sample messages + private void processMessageFailsForFixedMessage(SQSEvent.SQSMessage message, Context context) { + if (message.getMessageId().equals("e9144555-9a4f-4ec3-99a0-34ce359b4b54")) { + throw new RuntimeException("fake exception"); + } + } + + private void processMessageInParallelFailsForFixedMessage(SQSEvent.SQSMessage message, Context context) { + String thread = Thread.currentThread().getName(); + if (!threadList.contains(thread)) { + threadList.add(thread); + } + try { + Thread.sleep(500); // simulate some processing + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + if (message.getMessageId().equals("e9144555-9a4f-4ec3-99a0-34ce359b4b54")) { + throw new RuntimeException("fake exception"); + } + } + + // A handler that throws an exception for _one_ of the deserialized products in the same messages + private void processMessageFailsForFixedProduct(Product product, Context context) { + if (product.getId() == 12345) { + throw new RuntimeException("fake exception"); + } + } + + private SQSBatchResponse testParallelBatchExecution(SQSEvent event, + BiConsumer<SQSEvent.SQSMessage, Context> messageHandler, + Executor executor) { + // Arrange + BatchMessageHandler<SQSEvent, SQSBatchResponse> handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithRawMessageHandler(messageHandler); + + // Act + SQSBatchResponse sqsBatchResponse; + if (executor == null) { + sqsBatchResponse = handler.processBatchInParallel(event, context); + } else { + sqsBatchResponse = handler.processBatchInParallel(event, context, executor); + } + + return sqsBatchResponse; + } + + @ParameterizedTest + @Event(value = "sqs_event.json", type = SQSEvent.class) + void batchProcessingSucceedsAndReturns(SQSEvent event) { + // Arrange + BatchMessageHandler<SQSEvent, SQSBatchResponse> handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithRawMessageHandler(this::processMessageSucceeds); + + // Act + SQSBatchResponse sqsBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(sqsBatchResponse.getBatchItemFailures()).isEmpty(); + } + + @ParameterizedTest + @Event(value = "sqs_event_big.json", type = SQSEvent.class) + void parallelBatchProcessingSucceedsAndReturns(SQSEvent event) { + SQSBatchResponse sqsBatchResponse = testParallelBatchExecution(event, this::processMessageInParallelSucceeds, null); + + // Assert + assertThat(sqsBatchResponse.getBatchItemFailures()).isEmpty(); + assertThat(threadList).hasSizeGreaterThan(1); + } + + @ParameterizedTest + @Event(value = "sqs_event.json", type = SQSEvent.class) + void shouldAddMessageToBatchFailure_whenException_withMessage(SQSEvent event) { + // Arrange + BatchMessageHandler<SQSEvent, SQSBatchResponse> handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithRawMessageHandler(this::processMessageFailsForFixedMessage); + + // Act + SQSBatchResponse sqsBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(sqsBatchResponse.getBatchItemFailures()).hasSize(1); + SQSBatchResponse.BatchItemFailure batchItemFailure = sqsBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("e9144555-9a4f-4ec3-99a0-34ce359b4b54"); + } + + @ParameterizedTest + @Event(value = "sqs_event_big.json", type = SQSEvent.class) + void parallelBatchProcessing_shouldAddMessageToBatchFailure_whenException_withMessage(SQSEvent event) { + SQSBatchResponse sqsBatchResponse = testParallelBatchExecution(event, this::processMessageInParallelFailsForFixedMessage, null); + + // Assert + assertThat(sqsBatchResponse.getBatchItemFailures()).hasSize(1); + SQSBatchResponse.BatchItemFailure batchItemFailure = sqsBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("e9144555-9a4f-4ec3-99a0-34ce359b4b54"); + assertThat(threadList).hasSizeGreaterThan(1); + } + + @ParameterizedTest + @Event(value = "sqs_fifo_event.json", type = SQSEvent.class) + void shouldAddMessageToBatchFailure_whenException_withSQSFIFO(SQSEvent event) { + // Arrange + BatchMessageHandler<SQSEvent, SQSBatchResponse> handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithRawMessageHandler(this::processMessageFailsForFixedMessage); + + // Act + SQSBatchResponse sqsBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(sqsBatchResponse.getBatchItemFailures()).hasSize(2); + SQSBatchResponse.BatchItemFailure batchItemFailure = sqsBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("e9144555-9a4f-4ec3-99a0-34ce359b4b54"); + batchItemFailure = sqsBatchResponse.getBatchItemFailures().get(1); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("f9144555-9a4f-4ec3-99a0-34ce359b4b54"); + } + + + @ParameterizedTest + @Event(value = "sqs_event.json", type = SQSEvent.class) + void shouldAddMessageToBatchFailure_whenException_withProduct(SQSEvent event) { + + // Arrange + BatchMessageHandler<SQSEvent, SQSBatchResponse> handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithMessageHandler(this::processMessageFailsForFixedProduct, Product.class); + + // Act + SQSBatchResponse sqsBatchResponse = handler.processBatch(event, context); + assertThat(sqsBatchResponse.getBatchItemFailures()).hasSize(1); + + // Assert + SQSBatchResponse.BatchItemFailure batchItemFailure = sqsBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("e9144555-9a4f-4ec3-99a0-34ce359b4b54"); + } + + @ParameterizedTest + @Event(value = "sqs_event.json", type = SQSEvent.class) + void failingFailureHandlerShouldntFailBatch(SQSEvent event) { + // Arrange + AtomicBoolean wasCalled = new AtomicBoolean(false); + BatchMessageHandler<SQSEvent, SQSBatchResponse> handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .withFailureHandler((e, ex) -> { + wasCalled.set(true); + throw new RuntimeException("Well, this doesn't look great"); + }) + .buildWithMessageHandler(this::processMessageFailsForFixedProduct, Product.class); + + // Act + SQSBatchResponse sqsBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(sqsBatchResponse).isNotNull(); + assertThat(wasCalled.get()).isTrue(); + SQSBatchResponse.BatchItemFailure batchItemFailure = sqsBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("e9144555-9a4f-4ec3-99a0-34ce359b4b54"); + } + + @ParameterizedTest + @Event(value = "sqs_event.json", type = SQSEvent.class) + void failingSuccessHandlerShouldntFailBatchButShouldFailMessage(SQSEvent event) { + // Arrange + AtomicBoolean wasCalledAndFailed = new AtomicBoolean(false); + BatchMessageHandler<SQSEvent, SQSBatchResponse> handler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .withSuccessHandler((e) -> { + if (e.getMessageId().equals("e9144555-9a4f-4ec3-99a0-34ce359b4b54")) { + wasCalledAndFailed.set(true); + throw new RuntimeException("Success handler throws"); + } + }) + .buildWithRawMessageHandler(this::processMessageSucceeds); + + // Act + SQSBatchResponse sqsBatchResponse = handler.processBatch(event, context); + + // Assert + assertThat(sqsBatchResponse).isNotNull(); + assertThat(wasCalledAndFailed.get()).isTrue(); + SQSBatchResponse.BatchItemFailure batchItemFailure = sqsBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("e9144555-9a4f-4ec3-99a0-34ce359b4b54"); + } + + @ParameterizedTest + @Event(value = "sqs_event_big.json", type = SQSEvent.class) + void parallelBatchProcessingWithExecutorSucceedsAndReturns(SQSEvent event) { + ExecutorService executor = Executors.newFixedThreadPool(2); + SQSBatchResponse sqsBatchResponse = testParallelBatchExecution(event, this::processMessageInParallelSucceeds, executor); + executor.shutdown(); + + // Assert + assertThat(sqsBatchResponse.getBatchItemFailures()).isEmpty(); + assertThat(threadList).hasSizeGreaterThan(1); + } + + @ParameterizedTest + @Event(value = "sqs_event_big.json", type = SQSEvent.class) + void parallelBatchProcessingWithExecutor_shouldAddMessageToBatchFailure_whenException_withMessage(SQSEvent event) { + ExecutorService executor = Executors.newFixedThreadPool(2); + SQSBatchResponse sqsBatchResponse = testParallelBatchExecution(event, this::processMessageInParallelFailsForFixedMessage, executor); + executor.shutdown(); + + // Assert + assertThat(sqsBatchResponse.getBatchItemFailures()).hasSize(1); + SQSBatchResponse.BatchItemFailure batchItemFailure = sqsBatchResponse.getBatchItemFailures().get(0); + assertThat(batchItemFailure.getItemIdentifier()).isEqualTo("e9144555-9a4f-4ec3-99a0-34ce359b4b54"); + assertThat(threadList).hasSizeGreaterThan(1); + } + + +} diff --git a/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/internal/BatchUserAgentInterceptorTest.java b/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/internal/BatchUserAgentInterceptorTest.java new file mode 100644 index 000000000..2841a1724 --- /dev/null +++ b/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/internal/BatchUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.batch.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class BatchUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/BATCH/"); + } +} diff --git a/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/model/Basket.java b/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/model/Basket.java new file mode 100644 index 000000000..6009e79d6 --- /dev/null +++ b/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/model/Basket.java @@ -0,0 +1,67 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.batch.model; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class Basket { + private List<Product> products = new ArrayList<>(); + + public Basket() { + } + + public Basket(Product... p) { + products.addAll(Arrays.asList(p)); + } + + public List<Product> getProducts() { + return products; + } + + public void setProducts(List<Product> products) { + this.products = products; + } + + public void add(Product product) { + products.add(product); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Basket basket = (Basket) o; + return products.equals(basket.products); + } + + @Override + public String toString() { + return "Basket{" + + "products=" + products + + '}'; + } + + @Override + public int hashCode() { + return Objects.hash(products); + } +} diff --git a/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/model/Product.java b/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/model/Product.java new file mode 100644 index 000000000..2695578f9 --- /dev/null +++ b/powertools-batch/src/test/java/software/amazon/lambda/powertools/batch/model/Product.java @@ -0,0 +1,84 @@ +package software.amazon.lambda.powertools.batch.model; + +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +import java.util.Objects; + +public class Product { + private long id; + + private String name; + + private double price; + + public Product() { + } + + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Product product = (Product) o; + return id == product.id && Double.compare(product.price, price) == 0 && Objects.equals(name, product.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, price); + } + + @Override + public String toString() { + return "Product{" + + "id=" + id + + ", name='" + name + '\'' + + ", price=" + price + + '}'; + } +} diff --git a/powertools-batch/src/test/resources/dynamo_event.json b/powertools-batch/src/test/resources/dynamo_event.json new file mode 100644 index 000000000..f28ce0e6e --- /dev/null +++ b/powertools-batch/src/test/resources/dynamo_event.json @@ -0,0 +1,97 @@ +{ + "Records": [ + { + "eventID": "c4ca4238a0b923820dcc509a6f75849b", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439091", + "SizeBytes": 26, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", + "userIdentity": { + "principalId": "dynamodb.amazonaws.com", + "type": "Service" + } + }, + { + "eventID": "c81e728d9d4c2f636f067f89cc14862c", + "eventName": "MODIFY", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439092", + "SizeBytes": 59, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "eccbc87e4b5ce2fe28308fd9f2a7baf3", + "eventName": "REMOVE", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439093", + "SizeBytes": 38, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + } + ] +} \ No newline at end of file diff --git a/powertools-batch/src/test/resources/dynamo_event_big.json b/powertools-batch/src/test/resources/dynamo_event_big.json new file mode 100644 index 000000000..fa0a75c24 --- /dev/null +++ b/powertools-batch/src/test/resources/dynamo_event_big.json @@ -0,0 +1,376 @@ +{ + "Records": [ + { + "eventID": "c4ca4238a0b923820dcc509a6f75849b", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439001", + "SizeBytes": 26, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", + "userIdentity": { + "principalId": "dynamodb.amazonaws.com", + "type": "Service" + } + }, + { + "eventID": "c81e728d9d4c2f636f067f89cc14862c", + "eventName": "MODIFY", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439092", + "SizeBytes": 59, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "eccbc87e4b5ce2fe28308fd9f2a7baf3", + "eventName": "REMOVE", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439093", + "SizeBytes": 38, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "c4ca4238a0b923820dcc509a6f75849b", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439031", + "SizeBytes": 26, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", + "userIdentity": { + "principalId": "dynamodb.amazonaws.com", + "type": "Service" + } + }, + { + "eventID": "c81e728d9d4c2f636f067f89cc14862c", + "eventName": "MODIFY", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439092", + "SizeBytes": 59, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "eccbc87e4b5ce2fe28308fd9f2a7baf3", + "eventName": "REMOVE", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439093", + "SizeBytes": 38, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "c4ca4238a0b923820dcc509a6f75849b", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439001", + "SizeBytes": 26, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", + "userIdentity": { + "principalId": "dynamodb.amazonaws.com", + "type": "Service" + } + }, + { + "eventID": "c81e728d9d4c2f636f067f89cc14862c", + "eventName": "MODIFY", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439092", + "SizeBytes": 59, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "eccbc87e4b5ce2fe28308fd9f2a7baf3", + "eventName": "REMOVE", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439093", + "SizeBytes": 38, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "c4ca4238a0b923820dcc509a6f75849b", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439031", + "SizeBytes": 26, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", + "userIdentity": { + "principalId": "dynamodb.amazonaws.com", + "type": "Service" + } + }, + { + "eventID": "c81e728d9d4c2f636f067f89cc14862c", + "eventName": "MODIFY", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439091", + "SizeBytes": 59, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "eccbc87e4b5ce2fe28308fd9f2a7baf3", + "eventName": "REMOVE", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439093", + "SizeBytes": 38, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + } + ] +} \ No newline at end of file diff --git a/powertools-batch/src/test/resources/kinesis_event.json b/powertools-batch/src/test/resources/kinesis_event.json new file mode 100644 index 000000000..c9068da9b --- /dev/null +++ b/powertools-batch/src/test/resources/kinesis_event.json @@ -0,0 +1,38 @@ +{ + "Records": [ + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ==", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200962", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/powertools-batch/src/test/resources/kinesis_event_big.json b/powertools-batch/src/test/resources/kinesis_event_big.json new file mode 100644 index 000000000..57f702d27 --- /dev/null +++ b/powertools-batch/src/test/resources/kinesis_event_big.json @@ -0,0 +1,224 @@ +{ + "Records": [ + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200962", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200962", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ==", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200963", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200963", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200964", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200964", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ==", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200965", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200965", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + },{ + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200966", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200966", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ==", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200967", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200967", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200968", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200968", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ==", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200969", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200969", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200971", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200971", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200981", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200981", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ==", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200992", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200992", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244210961", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244210961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/powertools-batch/src/test/resources/sqs_event.json b/powertools-batch/src/test/resources/sqs_event.json new file mode 100644 index 000000000..7fdad096f --- /dev/null +++ b/powertools-batch/src/test/resources/sqs_event.json @@ -0,0 +1,55 @@ +{ + "Records": [ + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1234,\n \"name\": \"product\",\n \"price\": 42\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "e9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 12345,\n \"name\": \"product5\",\n \"price\": 45\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "f9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 123456,\n \"name\": \"product6\",\n \"price\": 46\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/powertools-batch/src/test/resources/sqs_event_big.json b/powertools-batch/src/test/resources/sqs_event_big.json new file mode 100644 index 000000000..f5c83f442 --- /dev/null +++ b/powertools-batch/src/test/resources/sqs_event_big.json @@ -0,0 +1,429 @@ +{ + "Records": [ + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1234,\n \"name\": \"product\",\n \"price\": 42\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "f9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "14e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1235,\n \"name\": \"product\",\n \"price\": 43\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "14e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "g9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "15e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1236,\n \"name\": \"product\",\n \"price\": 44\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "15e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "c9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "16e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1237,\n \"name\": \"product\",\n \"price\": 45\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "16e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "b4144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1238,\n \"name\": \"product\",\n \"price\": 486\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "a2144552-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "14e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1239,\n \"name\": \"product\",\n \"price\": 430\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "14e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "32144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "15e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1240,\n \"name\": \"product\",\n \"price\": 445\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "15e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "a9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "16e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1241,\n \"name\": \"product\",\n \"price\": 45\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "16e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b354", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1242,\n \"name\": \"product\",\n \"price\": 42\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "e9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "14e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1243,\n \"name\": \"product\",\n \"price\": 43\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "14e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "19144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "15e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1244,\n \"name\": \"product\",\n \"price\": 44\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "15e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "c9144555-9a4f-4ec3-99a0-34ce3a9b4b54", + "receiptHandle": "16e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1245,\n \"name\": \"product\",\n \"price\": 45\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "16e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "b4144555-9a4f-4ec3-99a5-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1247,\n \"name\": \"product\",\n \"price\": 486\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "a2144555-9a4f-4ec3-97a0-34ce359b4b54", + "receiptHandle": "14e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1248,\n \"name\": \"product\",\n \"price\": 430\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "14e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "3k144555-9a4f-4ec2-99a0-34ce359b4b54", + "receiptHandle": "15e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1249,\n \"name\": \"product\",\n \"price\": 445\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "15e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "h9144555-9aaf-4ec3-99a0-34ce359b4b54", + "receiptHandle": "16e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1250,\n \"name\": \"product\",\n \"price\": 45\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "16e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1234,\n \"name\": \"product\",\n \"price\": 42\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "f9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "14e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1235,\n \"name\": \"product\",\n \"price\": 43\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "14e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "g9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "15e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1236,\n \"name\": \"product\",\n \"price\": 44\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "15e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "c9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "16e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1237,\n \"name\": \"product\",\n \"price\": 45\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "16e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "b4144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1238,\n \"name\": \"product\",\n \"price\": 486\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "a2144552-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "14e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1239,\n \"name\": \"product\",\n \"price\": 430\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "14e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "32144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "15e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1240,\n \"name\": \"product\",\n \"price\": 445\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "15e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "a9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "16e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1241,\n \"name\": \"product\",\n \"price\": 45\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "16e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b354", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1242,\n \"name\": \"product\",\n \"price\": 42\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/powertools-batch/src/test/resources/sqs_fifo_event.json b/powertools-batch/src/test/resources/sqs_fifo_event.json new file mode 100644 index 000000000..e5abb1e5a --- /dev/null +++ b/powertools-batch/src/test/resources/sqs_fifo_event.json @@ -0,0 +1,58 @@ +{ + "Records": [ + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1234,\n \"name\": \"product\",\n \"price\": 42\n}", + "attributes": { + "MessageGroupId": "groupA", + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "e9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 12345,\n \"name\": \"product5\",\n \"price\": 45\n}", + "attributes": { + "MessageGroupId": "groupA", + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "f9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 123456,\n \"name\": \"product6\",\n \"price\": 46\n}", + "attributes": { + "MessageGroupId": "groupA", + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/powertools-cloudformation/pom.xml b/powertools-cloudformation/pom.xml index e4b96bc33..cb06dc1f3 100644 --- a/powertools-cloudformation/pom.xml +++ b/powertools-cloudformation/pom.xml @@ -1,7 +1,21 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + <project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>powertools-cloudformation</artifactId> @@ -10,36 +24,14 @@ <parent> <artifactId>powertools-parent</artifactId> <groupId>software.amazon.lambda</groupId> - <version>1.10.2</version> + <version>2.9.0</version> </parent> - <name>AWS Lambda Powertools Java library Cloudformation</name> + <name>Powertools for AWS Lambda (Java) - Cloudformation</name> <description> A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. </description> - <url>https://aws.amazon.com/lambda/</url> - <issueManagement> - <system>GitHub Issues</system> - <url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url> - </issueManagement> - <scm> - <url>https://github.com/awslabs/aws-lambda-powertools-java.git</url> - </scm> - <developers> - <developer> - <name>AWS Lambda Powertools team</name> - <organization>Amazon Web Services</organization> - <organizationUrl>https://aws.amazon.com/</organizationUrl> - </developer> - </developers> - - <distributionManagement> - <snapshotRepository> - <id>ossrh</id> - <url>https://aws.oss.sonatype.org/content/repositories/snapshots</url> - </snapshotRepository> - </distributionManagement> <dependencies> <dependency> @@ -50,6 +42,10 @@ <groupId>software.amazon.awssdk</groupId> <artifactId>url-connection-client</artifactId> </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-core</artifactId> @@ -66,6 +62,11 @@ <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + </dependency> <!-- Test dependencies --> <dependency> @@ -88,11 +89,112 @@ <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.wiremock</groupId> + <artifactId>wiremock</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> </dependencies> -</project> \ No newline at end of file + <profiles> + <profile> + <id>generate-graalvm-files</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-cloudformation</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--enable-url-protocols=http</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + + <build> + <resources> + <!-- GraalVM Native Image Configuration Files --> + <resource> + <directory>src/main/resources</directory> + </resource> + </resources> + </build> +</project> diff --git a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandler.java b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandler.java index 05f1a0f27..7f5b6bb24 100644 --- a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandler.java +++ b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandler.java @@ -1,16 +1,29 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.cloudformation; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import java.io.IOException; +import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; -import java.io.IOException; -import java.util.Objects; - /** * Handler base class providing core functionality for sending responses to custom CloudFormation resources after * receiving some event. Depending on the type of event, this class either invokes the crete, update, or delete method @@ -65,7 +78,12 @@ public final Response handleRequest(CloudFormationCustomResourceEvent event, Con } catch (CustomResourceResponseException rse) { LOG.error("Unable to generate response. Sending empty failure to {}", responseUrl, rse); try { - client.send(event, context, Response.failed()); + // If the customers code throws an exception, Powertools for AWS Lambda (Java) should respond in a way that doesn't + // change the CloudFormation resources. + // In the case of a Update or Delete, a failure is sent with the existing PhysicalResourceId + // indicating no change. + // In the case of a Create, null will be set and changed to the Lambda LogStreamName before sending. + client.send(event, context, Response.failed(event.getPhysicalResourceId())); } catch (Exception e) { // unable to generate response AND send the failure LOG.error("Unable to send failure response to {}.", responseUrl, e); diff --git a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponse.java b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponse.java index 33cc533d2..cf6fad827 100644 --- a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponse.java +++ b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponse.java @@ -1,3 +1,17 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.cloudformation; import com.amazonaws.services.lambda.runtime.Context; @@ -6,14 +20,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.node.ObjectNode; -import software.amazon.awssdk.http.Header; -import software.amazon.awssdk.http.HttpExecuteRequest; -import software.amazon.awssdk.http.HttpExecuteResponse; -import software.amazon.awssdk.http.SdkHttpClient; -import software.amazon.awssdk.http.SdkHttpMethod; -import software.amazon.awssdk.http.SdkHttpRequest; -import software.amazon.awssdk.utils.StringInputStream; - import java.io.IOException; import java.net.URI; import java.util.Collections; @@ -21,6 +27,16 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.http.Header; +import software.amazon.awssdk.http.HttpExecuteRequest; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.utils.StringInputStream; +import software.amazon.awssdk.utils.StringUtils; /** * Client for sending responses to AWS CloudFormation custom resources by way of a response URL, which is an Amazon S3 @@ -30,89 +46,9 @@ * <p> * This class is thread-safe provided the SdkHttpClient instance used is also thread-safe. */ -class CloudFormationResponse { - - /** - * Internal representation of the payload to be sent to the event target URL. Retains all properties of the payload - * except for "Data". This is done so that the serialization of the non-"Data" properties and the serialization of - * the value of "Data" can be handled by separate ObjectMappers, if need be. The former properties are dictated by - * the custom resource but the latter is dictated by the implementor of the custom resource handler. - */ - @SuppressWarnings("unused") - static class ResponseBody { - static final ObjectMapper MAPPER = new ObjectMapper() - .setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_CAMEL_CASE); - private static final String DATA_PROPERTY_NAME = "Data"; - - private final String status; - private final String reason; - private final String physicalResourceId; - private final String stackId; - private final String requestId; - private final String logicalResourceId; - private final boolean noEcho; - - ResponseBody(CloudFormationCustomResourceEvent event, - Context context, - Response.Status responseStatus, - String physicalResourceId, - boolean noEcho) { - Objects.requireNonNull(event, "CloudFormationCustomResourceEvent cannot be null"); - Objects.requireNonNull(context, "Context cannot be null"); - this.physicalResourceId = physicalResourceId != null ? physicalResourceId : context.getLogStreamName(); - this.reason = "See the details in CloudWatch Log Stream: " + context.getLogStreamName(); - this.status = responseStatus == null ? Response.Status.SUCCESS.name() : responseStatus.name(); - this.stackId = event.getStackId(); - this.requestId = event.getRequestId(); - this.logicalResourceId = event.getLogicalResourceId(); - this.noEcho = noEcho; - } - - public String getStatus() { - return status; - } - - public String getReason() { - return reason; - } - - public String getPhysicalResourceId() { - return physicalResourceId; - } - - public String getStackId() { - return stackId; - } - - public String getRequestId() { - return requestId; - } - - public String getLogicalResourceId() { - return logicalResourceId; - } - - public boolean isNoEcho() { - return noEcho; - } - - /** - * Returns this ResponseBody as an ObjectNode with the provided JsonNode as the value of its "Data" property. - * - * @param dataNode the value of the "Data" property for the returned node; may be null - * @return an ObjectNode representation of this ResponseBody and the provided dataNode - */ - ObjectNode toObjectNode(JsonNode dataNode) { - ObjectNode node = MAPPER.valueToTree(this); - if (dataNode == null) { - node.putNull(DATA_PROPERTY_NAME); - } else { - node.set(DATA_PROPERTY_NAME, dataNode); - } - return node; - } - } +public class CloudFormationResponse { + private static final Logger LOG = LoggerFactory.getLogger(CloudFormationResponse.class); private final SdkHttpClient client; /** @@ -194,6 +130,8 @@ protected Map<String, List<String>> headers(int contentLength) { /** * Returns the response body as an input stream, for supplying with the HTTP request to the custom resource. + * <p> + * If PhysicalResourceId is null at this point it will be replaced with the Lambda LogStreamName. * * @throws CustomResourceResponseException if unable to generate the response stream */ @@ -201,18 +139,127 @@ StringInputStream responseBodyStream(CloudFormationCustomResourceEvent event, Context context, Response resp) throws CustomResourceResponseException { try { + String reason = "See the details in CloudWatch Log Stream: " + context.getLogStreamName(); if (resp == null) { - ResponseBody body = new ResponseBody(event, context, Response.Status.SUCCESS, null, false); + String physicalResourceId = event.getPhysicalResourceId() != null ? event.getPhysicalResourceId() : + context.getLogStreamName(); + + ResponseBody body = new ResponseBody(event, Response.Status.SUCCESS, physicalResourceId, false, reason); + LOG.debug("ResponseBody: {}", body); ObjectNode node = body.toObjectNode(null); return new StringInputStream(node.toString()); } else { - ResponseBody body = new ResponseBody( - event, context, resp.getStatus(), resp.getPhysicalResourceId(), resp.isNoEcho()); + if (!StringUtils.isBlank(resp.getReason())) { + reason = resp.getReason(); + } + String physicalResourceId = resp.getPhysicalResourceId() != null ? resp.getPhysicalResourceId() : + event.getPhysicalResourceId() != null ? event.getPhysicalResourceId() : + context.getLogStreamName(); + + ResponseBody body = + new ResponseBody(event, resp.getStatus(), physicalResourceId, resp.isNoEcho(), reason); + LOG.debug("ResponseBody: {}", body); ObjectNode node = body.toObjectNode(resp.getJsonNode()); return new StringInputStream(node.toString()); } } catch (RuntimeException e) { + LOG.error(e.getMessage()); throw new CustomResourceResponseException("Unable to generate response body.", e); } } + + /** + * Internal representation of the payload to be sent to the event target URL. Retains all properties of the payload + * except for "Data". This is done so that the serialization of the non-"Data" properties and the serialization of + * the value of "Data" can be handled by separate ObjectMappers, if need be. The former properties are dictated by + * the custom resource but the latter is dictated by the implementor of the custom resource handler. + */ + @SuppressWarnings("unused") + static class ResponseBody { + static final ObjectMapper MAPPER = new ObjectMapper() + .setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_CAMEL_CASE); + private static final String DATA_PROPERTY_NAME = "Data"; + + private final String status; + private final String reason; + private final String physicalResourceId; + private final String stackId; + private final String requestId; + private final String logicalResourceId; + private final boolean noEcho; + + ResponseBody(CloudFormationCustomResourceEvent event, + Response.Status responseStatus, + String physicalResourceId, + boolean noEcho, + String reason) { + Objects.requireNonNull(event, "CloudFormationCustomResourceEvent cannot be null"); + + this.physicalResourceId = physicalResourceId; + this.reason = reason; + this.status = responseStatus == null ? Response.Status.SUCCESS.name() : responseStatus.name(); + this.stackId = event.getStackId(); + this.requestId = event.getRequestId(); + this.logicalResourceId = event.getLogicalResourceId(); + this.noEcho = noEcho; + } + + public String getStatus() { + return status; + } + + public String getReason() { + return reason; + } + + public String getPhysicalResourceId() { + return physicalResourceId; + } + + public String getStackId() { + return stackId; + } + + public String getRequestId() { + return requestId; + } + + public String getLogicalResourceId() { + return logicalResourceId; + } + + public boolean isNoEcho() { + return noEcho; + } + + /** + * Returns this ResponseBody as an ObjectNode with the provided JsonNode as the value of its "Data" property. + * + * @param dataNode the value of the "Data" property for the returned node; may be null + * @return an ObjectNode representation of this ResponseBody and the provided dataNode + */ + ObjectNode toObjectNode(JsonNode dataNode) { + ObjectNode node = MAPPER.valueToTree(this); + if (dataNode == null) { + node.putNull(DATA_PROPERTY_NAME); + } else { + node.set(DATA_PROPERTY_NAME, dataNode); + } + return node; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("ResponseBody{"); + sb.append("status='").append(status).append('\''); + sb.append(", reason='").append(reason).append('\''); + sb.append(", physicalResourceId='").append(physicalResourceId).append('\''); + sb.append(", stackId='").append(stackId).append('\''); + sb.append(", requestId='").append(requestId).append('\''); + sb.append(", logicalResourceId='").append(logicalResourceId).append('\''); + sb.append(", noEcho=").append(noEcho); + sb.append('}'); + return sb.toString(); + } + } } diff --git a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CustomResourceResponseException.java b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CustomResourceResponseException.java index ead912392..904ae9c3f 100644 --- a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CustomResourceResponseException.java +++ b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CustomResourceResponseException.java @@ -1,3 +1,17 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.cloudformation; /** diff --git a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/Response.java b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/Response.java index 3ae6b9296..8c782d957 100644 --- a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/Response.java +++ b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/Response.java @@ -1,11 +1,25 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.cloudformation; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; - import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; +import software.amazon.awssdk.utils.StringUtils; /** * Models the arbitrary data to be sent to the custom resource in response to a CloudFormation event. This object @@ -13,6 +27,132 @@ */ public class Response { + private final JsonNode jsonNode; + private final Status status; + private final String physicalResourceId; + private final boolean noEcho; + private final String reason; + + private Response(JsonNode jsonNode, Status status, String physicalResourceId, boolean noEcho) { + this.jsonNode = jsonNode; + this.status = status; + this.physicalResourceId = physicalResourceId; + this.noEcho = noEcho; + this.reason = null; + } + + private Response(JsonNode jsonNode, Status status, String physicalResourceId, boolean noEcho, String reason) { + this.jsonNode = jsonNode; + this.status = status; + this.physicalResourceId = physicalResourceId; + this.noEcho = noEcho; + this.reason = reason; + } + + /** + * Creates a builder for constructing a Response wrapping the provided value. + * + * @return a builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Creates a failed Response with a given physicalResourceId. + * + * @param physicalResourceId The value must be a non-empty string and must be identical for all responses for the + * same resource. + * The value returned for a PhysicalResourceId can change custom resource update + * operations. If the value returned is the same, it is considered a normal update. If the + * value returned is different, AWS CloudFormation recognizes the update as a replacement + * and sends a delete request to the old resource. For more information, + * see AWS::CloudFormation::CustomResource. + * @return a failed Response with physicalResourceId + */ + public static Response failed(String physicalResourceId) { + return new Response(null, Status.FAILED, physicalResourceId, false); + } + + /** + * Creates a successful Response with a given physicalResourceId. + * + * @param physicalResourceId The value must be a non-empty string and must be identical for all responses for the + * same resource. + * The value returned for a PhysicalResourceId can change custom resource update + * operations. If the value returned is the same, it is considered a normal update. If the + * value returned is different, AWS CloudFormation recognizes the update as a replacement + * and sends a delete request to the old resource. For more information, + * see AWS::CloudFormation::CustomResource. + * @return a success Response with physicalResourceId + */ + public static Response success(String physicalResourceId) { + return new Response(null, Status.SUCCESS, physicalResourceId, false); + } + + /** + * Returns a JsonNode representation of the Response. + * + * @return a non-null JsonNode representation + */ + JsonNode getJsonNode() { + return jsonNode; + } + + /** + * The success/failed status of the Response. + * + * @return a non-null Status + */ + public Status getStatus() { + return status; + } + + /** + * The physical resource ID. If null, the default physical resource ID will be provided to the custom resource. + * + * @return a potentially null physical resource ID + */ + public String getPhysicalResourceId() { + return physicalResourceId; + } + + /** + * Whether to mask custom resource output (true) or not (false). + * + * @return true if custom resource output is to be masked, false otherwise + */ + public boolean isNoEcho() { + return noEcho; + } + + /** + * The reason for the failure. + * + * @return a potentially null reason + */ + public String getReason() { + return reason; + } + + /** + * Includes all Response attributes, including its value in JSON format + * + * @return a full description of the Response + */ + @Override + public String toString() { + Map<String, Object> attributes = new HashMap<>(); + attributes.put("JSON", jsonNode == null ? null : jsonNode.toString()); + attributes.put("Status", status); + attributes.put("PhysicalResourceId", physicalResourceId); + attributes.put("NoEcho", noEcho); + attributes.put("Reason", reason); + return attributes.entrySet().stream() + .map(entry -> entry.getKey() + " = " + entry.getValue()) + .collect(Collectors.joining(",", "[", "]")); + } + /** * Indicates whether a response is a success or failure. */ @@ -29,6 +169,7 @@ public static class Builder { private Status status; private String physicalResourceId; private boolean noEcho; + private String reason; private Builder() { } @@ -110,6 +251,20 @@ public Builder noEcho(boolean noEcho) { return this; } + /** + * Reason for the response. + * Reason is optional for Success responses, but required for Failed responses. + * If not provided it will be replaced with cloudwatch log stream name. + * + * @param reason if null, the default reason will be used + * @return a reference to this builder + */ + + public Builder reason(String reason) { + this.reason = reason; + return this; + } + /** * Builds a Response object for the value. * @@ -124,99 +279,10 @@ public Response build() { node = mapper.valueToTree(value); } Status responseStatus = this.status != null ? this.status : Status.SUCCESS; + if (StringUtils.isNotBlank(this.reason)) { + return new Response(node, responseStatus, physicalResourceId, noEcho, reason); + } return new Response(node, responseStatus, physicalResourceId, noEcho); } } - - /** - * Creates a builder for constructing a Response wrapping the provided value. - * - * @return a builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Creates an empty, failed Response. - * - * @return a failed Response with no value. - */ - public static Response failed() { - return new Response(null, Status.FAILED, null, false); - } - - /** - * Creates an empty, successful Response. - * - * @return a failed Response with no value. - */ - public static Response success() { - return new Response(null, Status.SUCCESS, null, false); - } - - private final JsonNode jsonNode; - private final Status status; - private final String physicalResourceId; - private final boolean noEcho; - - private Response(JsonNode jsonNode, Status status, String physicalResourceId, boolean noEcho) { - this.jsonNode = jsonNode; - this.status = status; - this.physicalResourceId = physicalResourceId; - this.noEcho = noEcho; - } - - /** - * Returns a JsonNode representation of the Response. - * - * @return a non-null JsonNode representation - */ - JsonNode getJsonNode() { - return jsonNode; - } - - /** - * The success/failed status of the Response. - * - * @return a non-null Status - */ - public Status getStatus() { - return status; - } - - /** - * The physical resource ID. If null, the default physical resource ID will be provided to the custom resource. - * - * @return a potentially null physical resource ID - */ - public String getPhysicalResourceId() { - return physicalResourceId; - } - - /** - * Whether to mask custom resource output (true) or not (false). - * - * @return true if custom resource output is to be masked, false otherwise - */ - public boolean isNoEcho() { - return noEcho; - } - - /** - * Includes all Response attributes, including its value in JSON format - * - * @return a full description of the Response - */ - @Override - public String toString() { - Map<String, Object> attributes = new HashMap<>(); - attributes.put("JSON", jsonNode == null ? null : jsonNode.toString()); - attributes.put("Status", status); - attributes.put("PhysicalResourceId", physicalResourceId); - attributes.put("NoEcho", noEcho); - return attributes.entrySet().stream() - .map(entry -> entry.getKey() + " = " + entry.getValue()) - .collect(Collectors.joining(",", "[", "]")); - } } diff --git a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/internal/CloudformationUserAgentInterceptor.java b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/internal/CloudformationUserAgentInterceptor.java new file mode 100644 index 000000000..c225512d1 --- /dev/null +++ b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/internal/CloudformationUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.cloudformation.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-cloudformation module is on the classpath. + */ +public final class CloudformationUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("cloudformation"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-cloudformation/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation/reflect-config.json b/powertools-cloudformation/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation/reflect-config.json new file mode 100644 index 000000000..092ef9b54 --- /dev/null +++ b/powertools-cloudformation/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation/reflect-config.json @@ -0,0 +1,436 @@ +[ +{ + "name":"[Lcom.fasterxml.jackson.databind.deser.BeanDeserializerModifier;" +}, +{ + "name":"[Lcom.fasterxml.jackson.databind.deser.Deserializers;" +}, +{ + "name":"[Lcom.fasterxml.jackson.databind.deser.KeyDeserializers;" +}, +{ + "name":"[Lcom.fasterxml.jackson.databind.deser.ValueInstantiators;" +}, +{ + "name":"[Lcom.fasterxml.jackson.databind.ser.BeanSerializerModifier;" +}, +{ + "name":"[Lcom.fasterxml.jackson.databind.ser.Serializers;" +}, +{ + "name":"com.amazonaws.services.lambda.runtime.RequestHandler", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"canEqual","parameterTypes":["java.lang.Object"] }, {"name":"getLogicalResourceId","parameterTypes":[] }, {"name":"getOldResourceProperties","parameterTypes":[] }, {"name":"getPhysicalResourceId","parameterTypes":[] }, {"name":"getRequestId","parameterTypes":[] }, {"name":"getRequestType","parameterTypes":[] }, {"name":"getResourceProperties","parameterTypes":[] }, {"name":"getResourceType","parameterTypes":[] }, {"name":"getResponseUrl","parameterTypes":[] }, {"name":"getServiceToken","parameterTypes":[] }, {"name":"getStackId","parameterTypes":[] }, {"name":"setLogicalResourceId","parameterTypes":["java.lang.String"] }, {"name":"setOldResourceProperties","parameterTypes":["java.util.Map"] }, {"name":"setPhysicalResourceId","parameterTypes":["java.lang.String"] }, {"name":"setRequestId","parameterTypes":["java.lang.String"] }, {"name":"setRequestType","parameterTypes":["java.lang.String"] }, {"name":"setResourceProperties","parameterTypes":["java.util.Map"] }, {"name":"setResourceType","parameterTypes":["java.lang.String"] }, {"name":"setResponseUrl","parameterTypes":["java.lang.String"] }, {"name":"setServiceToken","parameterTypes":["java.lang.String"] }, {"name":"setStackId","parameterTypes":["java.lang.String"] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.jayway.jsonpath.spi.cache.CacheProvider", + "fields":[{"name":"cache"}] +}, +{ + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ARCFOURCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESedeCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.tools.attach.VirtualMachine" +}, +{ + "name":"java.io.FileNotFoundException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.io.Serializable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.AutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.Boolean", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Byte", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Class", + "methods":[{"name":"getAnnotatedInterfaces","parameterTypes":[] }, {"name":"getAnnotatedSuperclass","parameterTypes":[] }, {"name":"getDeclaredMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getModule","parameterTypes":[] }, {"name":"getNestHost","parameterTypes":[] }, {"name":"getNestMembers","parameterTypes":[] }, {"name":"getPackageName","parameterTypes":[] }, {"name":"getPermittedSubclasses","parameterTypes":[] }, {"name":"getRecordComponents","parameterTypes":[] }, {"name":"isNestmateOf","parameterTypes":["java.lang.Class"] }, {"name":"isRecord","parameterTypes":[] }, {"name":"isSealed","parameterTypes":[] }] +}, +{ + "name":"java.lang.ClassLoader", + "methods":[{"name":"getDefinedPackage","parameterTypes":["java.lang.String"] }, {"name":"getUnnamedModule","parameterTypes":[] }, {"name":"registerAsParallelCapable","parameterTypes":[] }] +}, +{ + "name":"java.lang.Cloneable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.Double", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Float", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Integer", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Long", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Module", + "methods":[{"name":"addExports","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"addOpens","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"addReads","parameterTypes":["java.lang.Module"] }, {"name":"canRead","parameterTypes":["java.lang.Module"] }, {"name":"getClassLoader","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPackages","parameterTypes":[] }, {"name":"getResourceAsStream","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"isNamed","parameterTypes":[] }, {"name":"isOpen","parameterTypes":["java.lang.String","java.lang.Module"] }] +}, +{ + "name":"java.lang.Object", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"clone","parameterTypes":[] }, {"name":"getClass","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"java.lang.ProcessHandle", + "methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime", + "methods":[{"name":"version","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime$Version", + "methods":[{"name":"feature","parameterTypes":[] }] +}, +{ + "name":"java.lang.Short", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.StackWalker" +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getSecurityManager","parameterTypes":[] }] +}, +{ + "name":"java.lang.Thread", + "fields":[{"name":"threadLocalRandomProbe"}], + "methods":[{"name":"isVirtual","parameterTypes":[] }] +}, +{ + "name":"java.lang.annotation.Retention", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.annotation.Target", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.invoke.MethodHandle", + "methods":[{"name":"bindTo","parameterTypes":["java.lang.Object"] }, {"name":"invokeWithArguments","parameterTypes":["java.lang.Object[]"] }] +}, +{ + "name":"java.lang.invoke.MethodHandles", + "methods":[{"name":"lookup","parameterTypes":[] }, {"name":"privateLookupIn","parameterTypes":["java.lang.Class","java.lang.invoke.MethodHandles$Lookup"] }] +}, +{ + "name":"java.lang.invoke.MethodHandles$Lookup", + "methods":[{"name":"findVirtual","parameterTypes":["java.lang.Class","java.lang.String","java.lang.invoke.MethodType"] }] +}, +{ + "name":"java.lang.invoke.MethodType", + "methods":[{"name":"methodType","parameterTypes":["java.lang.Class","java.lang.Class[]"] }] +}, +{ + "name":"java.lang.management.ManagementFactory", + "methods":[{"name":"getRuntimeMXBean","parameterTypes":[] }] +}, +{ + "name":"java.lang.management.RuntimeMXBean", + "methods":[{"name":"getInputArguments","parameterTypes":[] }, {"name":"getUptime","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AccessibleObject", + "methods":[{"name":"setAccessible","parameterTypes":["boolean"] }] +}, +{ + "name":"java.lang.reflect.AnnotatedArrayType", + "methods":[{"name":"getAnnotatedGenericComponentType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedParameterizedType", + "methods":[{"name":"getAnnotatedActualTypeArguments","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedType", + "methods":[{"name":"getType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Executable", + "methods":[{"name":"getAnnotatedExceptionTypes","parameterTypes":[] }, {"name":"getAnnotatedParameterTypes","parameterTypes":[] }, {"name":"getAnnotatedReceiverType","parameterTypes":[] }, {"name":"getParameterCount","parameterTypes":[] }, {"name":"getParameters","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"getAnnotatedReturnType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Parameter", + "methods":[{"name":"getModifiers","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"isNamePresent","parameterTypes":[] }] +}, +{ + "name":"java.security.AccessController", + "methods":[{"name":"doPrivileged","parameterTypes":["java.security.PrivilegedAction"] }, {"name":"doPrivileged","parameterTypes":["java.security.PrivilegedExceptionAction"] }] +}, +{ + "name":"java.security.AlgorithmParametersSpi" +}, +{ + "name":"java.security.KeyStoreSpi" +}, +{ + "name":"java.security.SecureRandomParameters" +}, +{ + "name":"java.util.HashSet", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"java.util.concurrent.Callable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.concurrent.Executors", + "methods":[{"name":"newVirtualThreadPerTaskExecutor","parameterTypes":[] }] +}, +{ + "name":"java.util.concurrent.ForkJoinTask", + "fields":[{"name":"aux"}, {"name":"status"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.Striped64", + "fields":[{"name":"base"}, {"name":"cellsBusy"}] +}, +{ + "name":"java.util.function.Consumer", + "queryAllPublicMethods":true +}, +{ + "name":"javax.security.auth.x500.X500Principal", + "fields":[{"name":"thisX500Name"}], + "methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }] +}, +{ + "name":"jdk.internal.misc.Unsafe" +}, +{ + "name":"kotlin.Metadata" +}, +{ + "name":"kotlin.jvm.JvmInline" +}, +{ + "name":"org.apiguardian.api.API", + "queryAllPublicMethods":true +}, +{ + "name":"org.eclipse.jetty.http.pathmap.PathSpecSet", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.eclipse.jetty.servlets.CrossOriginFilter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.eclipse.jetty.util.AsciiLowerCaseSet", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.eclipse.jetty.util.TypeUtil", + "methods":[{"name":"getClassLoaderLocation","parameterTypes":["java.lang.Class"] }, {"name":"getCodeSourceLocation","parameterTypes":["java.lang.Class"] }, {"name":"getModuleLocation","parameterTypes":["java.lang.Class"] }, {"name":"getSystemClassLoaderLocation","parameterTypes":["java.lang.Class"] }] +}, +{ + "name":"software.amazon.awssdk.http.Abortable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"abort","parameterTypes":[] }] +}, +{ + "name":"software.amazon.awssdk.http.ExecutableHttpRequest", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"call","parameterTypes":[] }] +}, +{ + "name":"software.amazon.awssdk.http.HttpExecuteResponse", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"httpResponse","parameterTypes":[] }, {"name":"responseBody","parameterTypes":[] }] +}, +{ + "name":"software.amazon.awssdk.http.SdkHttpClient", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"clientName","parameterTypes":[] }, {"name":"prepareRequest","parameterTypes":["software.amazon.awssdk.http.HttpExecuteRequest"] }] +}, +{ + "name":"software.amazon.awssdk.utils.SdkAutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"close","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"onSendFailure","parameterTypes":["com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent","com.amazonaws.services.lambda.runtime.Context","software.amazon.lambda.powertools.cloudformation.Response","java.lang.Exception"] }] +}, +{ + "name":"software.amazon.lambda.powertools.cloudformation.CloudFormationResponse", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"headers","parameterTypes":["int"] }, {"name":"send","parameterTypes":["com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent","com.amazonaws.services.lambda.runtime.Context"] }, {"name":"send","parameterTypes":["com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent","com.amazonaws.services.lambda.runtime.Context","software.amazon.lambda.powertools.cloudformation.Response"] }] +}, +{ + "name":"software.amazon.lambda.powertools.cloudformation.CloudFormationResponse$ResponseBody", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getLogicalResourceId","parameterTypes":[] }, {"name":"getPhysicalResourceId","parameterTypes":[] }, {"name":"getReason","parameterTypes":[] }, {"name":"getRequestId","parameterTypes":[] }, {"name":"getStackId","parameterTypes":[] }, {"name":"getStatus","parameterTypes":[] }, {"name":"isNoEcho","parameterTypes":[] }] +}, +{ + "name":"sun.misc.SharedSecrets" +}, +{ + "name":"sun.reflect.ReflectionFactory", + "methods":[{"name":"getReflectionFactory","parameterTypes":[] }, {"name":"newConstructorForSerialization","parameterTypes":["java.lang.Class","java.lang.reflect.Constructor"] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.SHA", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.X509Factory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAKeyFactory$Legacy", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.SSLContextImpl$TLSContext", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.x509.AuthorityInfoAccessExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.AuthorityKeyIdentifierExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.BasicConstraintsExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CRLDistributionPointsExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CertificatePoliciesExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.ExtendedKeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.KeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.NetscapeCertTypeExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.PrivateKeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectKeyIdentifierExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"software.amazon.lambda.powertools.cloudformation.internal.CloudformationUserAgentInterceptor", + "methods":[{"name":"<init>","parameterTypes":[] }] +} +] diff --git a/powertools-cloudformation/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation/resource-config.json b/powertools-cloudformation/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation/resource-config.json new file mode 100644 index 000000000..cbbfb270d --- /dev/null +++ b/powertools-cloudformation/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation/resource-config.json @@ -0,0 +1,43 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.InetAddressResolverProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.util.spi.ResourceBundleControlProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\Qassets/swagger-ui/index.html\\E" + }, { + "pattern":"\\Qassets\\E" + }, { + "pattern":"\\Qhelpers.nashorn.js\\E" + }, { + "pattern":"\\Qkeystore\\E" + }, { + "pattern":"\\Qorg/apache/hc/client5/version.properties\\E" + }, { + "pattern":"\\Qorg/eclipse/jetty/http/encoding.properties\\E" + }, { + "pattern":"\\Qorg/eclipse/jetty/http/mime.properties\\E" + }, { + "pattern":"\\Qorg/eclipse/jetty/version/build.properties\\E" + }, { + "pattern":"\\Qorg/publicsuffix/list/effective_tld_names.dat\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/global/handlers/execution.interceptors\\E" + }]}, + "bundles":[{ + "name":"jakarta.servlet.LocalStrings", + "locales":[""] + }, { + "name":"jakarta.servlet.http.LocalStrings", + "locales":[""] + }] +} diff --git a/powertools-cloudformation/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-cloudformation/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..172ca1d2c --- /dev/null +++ b/powertools-cloudformation/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.cloudformation.internal.CloudformationUserAgentInterceptor diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandlerTest.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandlerTest.java index d68b434d6..9d0669d43 100644 --- a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandlerTest.java +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandlerTest.java @@ -1,18 +1,21 @@ -package software.amazon.lambda.powertools.cloudformation; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import software.amazon.awssdk.http.SdkHttpClient; -import software.amazon.lambda.powertools.cloudformation.Response.Status; +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ -import java.io.IOException; +package software.amazon.lambda.powertools.cloudformation; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; @@ -21,95 +24,19 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class AbstractCustomResourceHandlerTest { - - /** - * Bare-bones implementation that returns null for abstract methods. - */ - static class NullCustomResourceHandler extends AbstractCustomResourceHandler { - NullCustomResourceHandler() { - } - - NullCustomResourceHandler(SdkHttpClient client) { - super(client); - } - - @Override - protected Response create(CloudFormationCustomResourceEvent event, Context context) { - return null; - } - - @Override - protected Response update(CloudFormationCustomResourceEvent event, Context context) { - return null; - } - - @Override - protected Response delete(CloudFormationCustomResourceEvent event, Context context) { - return null; - } - } - - /** - * Uses a mocked CloudFormationResponse to avoid sending actual HTTP requests. - */ - static class NoOpCustomResourceHandler extends NullCustomResourceHandler { +import java.io.IOException; - NoOpCustomResourceHandler() { - super(mock(SdkHttpClient.class)); - } +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; - @Override - protected CloudFormationResponse buildResponseClient() { - return mock(CloudFormationResponse.class); - } - } +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; - /** - * Creates a handler that will expect the Response to be sent with an expected status. Will throw an AssertionError - * if the method is sent with an unexpected status. - */ - static class ExpectedStatusResourceHandler extends NoOpCustomResourceHandler { - private final Status expectedStatus; - - ExpectedStatusResourceHandler(Status expectedStatus) { - this.expectedStatus = expectedStatus; - } - - @Override - protected CloudFormationResponse buildResponseClient() { - // create a CloudFormationResponse that fails if invoked with unexpected status - CloudFormationResponse cfnResponse = mock(CloudFormationResponse.class); - try { - when(cfnResponse.send(any(), any(), argThat(resp -> resp.getStatus() != expectedStatus))) - .thenThrow(new AssertionError("Expected response's status to be " + expectedStatus)); - } catch (IOException | CustomResourceResponseException e) { - // this should never happen - throw new RuntimeException("Unexpected mocking exception", e); - } - return cfnResponse; - } - } +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; - /** - * Always fails to send the response - */ - static class FailToSendResponseHandler extends NoOpCustomResourceHandler { - @Override - protected CloudFormationResponse buildResponseClient() { - CloudFormationResponse cfnResponse = mock(CloudFormationResponse.class); - try { - when(cfnResponse.send(any(), any())) - .thenThrow(new IOException("Intentional send failure")); - when(cfnResponse.send(any(), any(), any())) - .thenThrow(new IOException("Intentional send failure")); - } catch (IOException | CustomResourceResponseException e) { - // this should never happen - throw new RuntimeException("Unexpected mocking exception", e); - } - return cfnResponse; - } - } +public class AbstractCustomResourceHandlerTest { /** * Builds a valid Event with the provide request type. @@ -143,11 +70,11 @@ void defaultAndCustomSdkHttpClients() { } @ParameterizedTest - @CsvSource(value = {"Create,1,0,0", "Update,0,1,0", "Delete,0,0,1"}, delimiter = ',') + @CsvSource(value = { "Create,1,0,0", "Update,0,1,0", "Delete,0,0,1" }, delimiter = ',') void eventsDelegateToCorrectHandlerMethod(String eventType, int createCount, int updateCount, int deleteCount) { AbstractCustomResourceHandler handler = spy(new NoOpCustomResourceHandler()); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); handler.handleRequest(eventOfType(eventType), context); verify(handler, times(createCount)).create(any(), eq(context)); @@ -159,7 +86,7 @@ void eventsDelegateToCorrectHandlerMethod(String eventType, int createCount, int void eventOfUnknownRequestTypeSendEmptySuccess() { AbstractCustomResourceHandler handler = spy(new NoOpCustomResourceHandler()); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); CloudFormationCustomResourceEvent event = eventOfType("UNKNOWN"); handler.handleRequest(event, context); @@ -171,16 +98,9 @@ void eventOfUnknownRequestTypeSendEmptySuccess() { @Test void defaultStatusResponseSendsSuccess() { - ExpectedStatusResourceHandler handler = spy(new ExpectedStatusResourceHandler(Status.SUCCESS) { - @Override - protected Response create(CloudFormationCustomResourceEvent event, Context context) { - return Response.builder() - .value("whatever") - .build(); - } - }); - - Context context = mock(Context.class); + SuccessResponseHandler handler = spy(new SuccessResponseHandler()); + + Context context = new TestLambdaContext(); CloudFormationCustomResourceEvent event = eventOfType("Create"); Response response = handler.handleRequest(event, context); @@ -191,17 +111,9 @@ protected Response create(CloudFormationCustomResourceEvent event, Context conte @Test void explicitResponseWithStatusSuccessSendsSuccess() { - ExpectedStatusResourceHandler handler = spy(new ExpectedStatusResourceHandler(Status.SUCCESS) { - @Override - protected Response create(CloudFormationCustomResourceEvent event, Context context) { - return Response.builder() - .value("whatever") - .status(Status.SUCCESS) - .build(); - } - }); - - Context context = mock(Context.class); + ExplicitSuccessResponseHandler handler = spy(new ExplicitSuccessResponseHandler()); + + Context context = new TestLambdaContext(); CloudFormationCustomResourceEvent event = eventOfType("Create"); Response response = handler.handleRequest(event, context); @@ -212,17 +124,9 @@ protected Response create(CloudFormationCustomResourceEvent event, Context conte @Test void explicitResponseWithStatusFailedSendsFailure() { - ExpectedStatusResourceHandler handler = spy(new ExpectedStatusResourceHandler(Status.FAILED) { - @Override - protected Response create(CloudFormationCustomResourceEvent event, Context context) { - return Response.builder() - .value("whatever") - .status(Status.FAILED) - .build(); - } - }); - - Context context = mock(Context.class); + FailedResponseHandler handler = spy(new FailedResponseHandler()); + + Context context = new TestLambdaContext(); CloudFormationCustomResourceEvent event = eventOfType("Create"); Response response = handler.handleRequest(event, context); @@ -233,14 +137,9 @@ protected Response create(CloudFormationCustomResourceEvent event, Context conte @Test void exceptionWhenGeneratingResponseSendsFailure() { - ExpectedStatusResourceHandler handler = spy(new ExpectedStatusResourceHandler(Status.FAILED) { - @Override - protected Response create(CloudFormationCustomResourceEvent event, Context context) { - throw new RuntimeException("This exception is intentional for testing"); - } - }); - - Context context = mock(Context.class); + ExceptionThrowingHandler handler = spy(new ExceptionThrowingHandler()); + + Context context = new TestLambdaContext(); CloudFormationCustomResourceEvent event = eventOfType("Create"); Response response = handler.handleRequest(event, context); @@ -253,14 +152,9 @@ protected Response create(CloudFormationCustomResourceEvent event, Context conte @Test void exceptionWhenSendingResponseInvokesOnSendFailure() { // a custom handler that builds response successfully but fails to send it - FailToSendResponseHandler handler = spy(new FailToSendResponseHandler() { - @Override - protected Response create(CloudFormationCustomResourceEvent event, Context context) { - return Response.builder().value("Failure happens on send").build(); - } - }); - - Context context = mock(Context.class); + SuccessfulSendHandler handler = spy(new SuccessfulSendHandler()); + + Context context = new TestLambdaContext(); CloudFormationCustomResourceEvent event = eventOfType("Create"); Response response = handler.handleRequest(event, context); @@ -272,15 +166,11 @@ protected Response create(CloudFormationCustomResourceEvent event, Context conte @Test void bothResponseGenerationAndSendFail() { - // a custom handler that fails to build response _and_ fails to send a FAILED response - FailToSendResponseHandler handler = spy(new FailToSendResponseHandler() { - @Override - protected Response create(CloudFormationCustomResourceEvent event, Context context) { - throw new RuntimeException("This exception is intentional for testing"); - } - }); - - Context context = mock(Context.class); + // a custom handler that fails to build response _and_ fails to send a FAILED + // response + FailedSendHandler handler = spy(new FailedSendHandler()); + + Context context = new TestLambdaContext(); CloudFormationCustomResourceEvent event = eventOfType("Create"); Response response = handler.handleRequest(event, context); @@ -288,4 +178,5 @@ protected Response create(CloudFormationCustomResourceEvent event, Context conte verify(handler, times(1)) .onSendFailure(eq(event), eq(context), isNull(), any(IOException.class)); } + } diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationIntegrationTest.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationIntegrationTest.java new file mode 100644 index 000000000..316913bf2 --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationIntegrationTest.java @@ -0,0 +1,187 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation; + +import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.put; +import static com.github.tomakehurst.wiremock.client.WireMock.putRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; + +import software.amazon.lambda.powertools.cloudformation.handlers.NoPhysicalResourceIdSetHandler; +import software.amazon.lambda.powertools.cloudformation.handlers.PhysicalResourceIdSetHandler; +import software.amazon.lambda.powertools.cloudformation.handlers.RuntimeExceptionThrownHandler; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; + +@WireMockTest +public class CloudFormationIntegrationTest { + + public static final String PHYSICAL_RESOURCE_ID = UUID.randomUUID().toString(); + public static final String LOG_STREAM_NAME = "test-log-stream"; + + private static CloudFormationCustomResourceEvent.CloudFormationCustomResourceEventBuilder baseEvent(int httpPort) { + return CloudFormationCustomResourceEvent + .builder() + .withResponseUrl("http://localhost:" + httpPort + "/") + .withStackId("123") + .withRequestId("234") + .withLogicalResourceId("345"); + } + + @ParameterizedTest + @ValueSource(strings = { "Update", "Delete" }) + void physicalResourceIdTakenFromRequestForUpdateOrDeleteWhenUserSpecifiesNull(String requestType, + WireMockRuntimeInfo wmRuntimeInfo) { + stubFor(put("/").willReturn(ok())); + + NoPhysicalResourceIdSetHandler handler = new NoPhysicalResourceIdSetHandler(); + int httpPort = wmRuntimeInfo.getHttpPort(); + + CloudFormationCustomResourceEvent event = baseEvent(httpPort) + .withPhysicalResourceId(PHYSICAL_RESOURCE_ID) + .withRequestType(requestType) + .build(); + + handler.handleRequest(event, new TestLambdaContext()); + + verify(putRequestedFor(urlPathMatching("/")) + .withRequestBody(matchingJsonPath("[?(@.Status == 'SUCCESS')]")) + .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + PHYSICAL_RESOURCE_ID + "')]"))); + } + + @ParameterizedTest + @ValueSource(strings = { "Update", "Delete" }) + void physicalResourceIdDoesNotChangeWhenRuntimeExceptionThrownWhenUpdatingOrDeleting(String requestType, + WireMockRuntimeInfo wmRuntimeInfo) { + stubFor(put("/").willReturn(ok())); + + RuntimeExceptionThrownHandler handler = new RuntimeExceptionThrownHandler(); + int httpPort = wmRuntimeInfo.getHttpPort(); + + CloudFormationCustomResourceEvent event = baseEvent(httpPort) + .withPhysicalResourceId(PHYSICAL_RESOURCE_ID) + .withRequestType(requestType) + .build(); + + handler.handleRequest(event, new TestLambdaContext()); + + verify(putRequestedFor(urlPathMatching("/")) + .withRequestBody(matchingJsonPath("[?(@.Status == 'FAILED')]")) + .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + PHYSICAL_RESOURCE_ID + "')]"))); + } + + @Test + void runtimeExceptionThrownOnCreateSendsLogStreamNameAsPhysicalResourceId(WireMockRuntimeInfo wmRuntimeInfo) { + stubFor(put("/").willReturn(ok())); + + RuntimeExceptionThrownHandler handler = new RuntimeExceptionThrownHandler(); + CloudFormationCustomResourceEvent createEvent = baseEvent(wmRuntimeInfo.getHttpPort()) + .withRequestType("Create") + .build(); + handler.handleRequest(createEvent, new TestLambdaContext()); + + verify(putRequestedFor(urlPathMatching("/")) + .withRequestBody(matchingJsonPath("[?(@.Status == 'FAILED')]")) + .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + LOG_STREAM_NAME + "')]"))); + } + + @ParameterizedTest + @ValueSource(strings = { "Update", "Delete" }) + void physicalResourceIdSetFromRequestOnUpdateOrDeleteWhenCustomerDoesntProvideAPhysicalResourceId( + String requestType, WireMockRuntimeInfo wmRuntimeInfo) { + stubFor(put("/").willReturn(ok())); + + NoPhysicalResourceIdSetHandler handler = new NoPhysicalResourceIdSetHandler(); + int httpPort = wmRuntimeInfo.getHttpPort(); + + CloudFormationCustomResourceEvent event = baseEvent(httpPort) + .withPhysicalResourceId(PHYSICAL_RESOURCE_ID) + .withRequestType(requestType) + .build(); + + Response response = handler.handleRequest(event, new TestLambdaContext()); + + assertThat(response).isNotNull(); + verify(putRequestedFor(urlPathMatching("/")) + .withRequestBody(matchingJsonPath("[?(@.Status == 'SUCCESS')]")) + .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + PHYSICAL_RESOURCE_ID + "')]"))); + } + + @Test + void createNewResourceBecausePhysicalResourceIdNotSetByCustomerOnCreate(WireMockRuntimeInfo wmRuntimeInfo) { + stubFor(put("/").willReturn(ok())); + + NoPhysicalResourceIdSetHandler handler = new NoPhysicalResourceIdSetHandler(); + CloudFormationCustomResourceEvent createEvent = baseEvent(wmRuntimeInfo.getHttpPort()) + .withRequestType("Create") + .build(); + Response response = handler.handleRequest(createEvent, new TestLambdaContext()); + + assertThat(response).isNotNull(); + verify(putRequestedFor(urlPathMatching("/")) + .withRequestBody(matchingJsonPath("[?(@.Status == 'SUCCESS')]")) + .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + LOG_STREAM_NAME + "')]"))); + } + + @ParameterizedTest + @ValueSource(strings = { "Create", "Update", "Delete" }) + void physicalResourceIdReturnedFromSuccessToCloudformation(String requestType, WireMockRuntimeInfo wmRuntimeInfo) { + + String physicalResourceId = UUID.randomUUID().toString(); + + PhysicalResourceIdSetHandler handler = new PhysicalResourceIdSetHandler(physicalResourceId, true); + CloudFormationCustomResourceEvent createEvent = baseEvent(wmRuntimeInfo.getHttpPort()) + .withRequestType(requestType) + .build(); + Response response = handler.handleRequest(createEvent, new TestLambdaContext()); + + assertThat(response).isNotNull(); + verify(putRequestedFor(urlPathMatching("/")) + .withRequestBody(matchingJsonPath("[?(@.Status == 'SUCCESS')]")) + .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + physicalResourceId + "')]"))); + } + + @ParameterizedTest + @ValueSource(strings = { "Create", "Update", "Delete" }) + void physicalResourceIdReturnedFromFailedToCloudformation(String requestType, WireMockRuntimeInfo wmRuntimeInfo) { + + String physicalResourceId = UUID.randomUUID().toString(); + + PhysicalResourceIdSetHandler handler = new PhysicalResourceIdSetHandler(physicalResourceId, false); + CloudFormationCustomResourceEvent createEvent = baseEvent(wmRuntimeInfo.getHttpPort()) + .withRequestType(requestType) + .build(); + Response response = handler.handleRequest(createEvent, new TestLambdaContext()); + + assertThat(response).isNotNull(); + verify(putRequestedFor(urlPathMatching("/")) + .withRequestBody(matchingJsonPath("[?(@.Status == 'FAILED')]")) + .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + physicalResourceId + "')]"))); + } + +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponseTest.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponseTest.java index 207eb9b7f..0cc65f884 100644 --- a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponseTest.java +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponseTest.java @@ -1,9 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.cloudformation; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +import org.junit.jupiter.api.Test; + import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.junit.jupiter.api.Test; + import software.amazon.awssdk.http.AbortableInputStream; import software.amazon.awssdk.http.ExecutableHttpRequest; import software.amazon.awssdk.http.HttpExecuteRequest; @@ -12,23 +40,13 @@ import software.amazon.awssdk.utils.IoUtils; import software.amazon.awssdk.utils.StringInputStream; import software.amazon.lambda.powertools.cloudformation.CloudFormationResponse.ResponseBody; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; -import java.io.IOException; -import java.io.InputStream; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class CloudFormationResponseTest { +class CloudFormationResponseTest { /** - * Creates a mock CloudFormationCustomResourceEvent with a non-null response URL. + * Creates a mock CloudFormationCustomResourceEvent with a non-null response + * URL. */ static CloudFormationCustomResourceEvent mockCloudFormationCustomResourceEvent() { CloudFormationCustomResourceEvent event = mock(CloudFormationCustomResourceEvent.class); @@ -37,7 +55,8 @@ static CloudFormationCustomResourceEvent mockCloudFormationCustomResourceEvent() } /** - * Creates a CloudFormationResponse that does not make actual HTTP requests. The HTTP response body is the request + * Creates a CloudFormationResponse that does not make actual HTTP requests. The + * HTTP response body is the request * body. */ static CloudFormationResponse testableCloudFormationResponse() { @@ -75,7 +94,7 @@ void eventRequiredToSend() { SdkHttpClient client = mock(SdkHttpClient.class); CloudFormationResponse response = new CloudFormationResponse(client); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); assertThatThrownBy(() -> response.send(null, context)) .isInstanceOf(CustomResourceResponseException.class); } @@ -85,7 +104,7 @@ void contextRequiredToSend() { SdkHttpClient client = mock(SdkHttpClient.class); CloudFormationResponse response = new CloudFormationResponse(client); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); assertThatThrownBy(() -> response.send(null, context)) .isInstanceOf(CustomResourceResponseException.class); } @@ -96,52 +115,40 @@ void eventResponseUrlRequiredToSend() { CloudFormationResponse response = new CloudFormationResponse(client); CloudFormationCustomResourceEvent event = mock(CloudFormationCustomResourceEvent.class); - Context context = mock(Context.class); - // not a CustomResourceResponseException since the URL is not part of the response but + Context context = new TestLambdaContext(); + // not a CustomResourceResponseException since the URL is not part of the + // response but // rather the location the response is sent to assertThatThrownBy(() -> response.send(event, context)) .isInstanceOf(RuntimeException.class); } - @Test - void defaultPhysicalResponseIdIsLogStreamName() { - CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); - when(event.getPhysicalResourceId()).thenReturn("This-Is-Ignored"); - - String logStreamName = "My-Log-Stream-Name"; - Context context = mock(Context.class); - when(context.getLogStreamName()).thenReturn(logStreamName); - - ResponseBody body = new ResponseBody( - event, context, Response.Status.SUCCESS, null, false); - assertThat(body.getPhysicalResourceId()).isEqualTo(logStreamName); - } - @Test void customPhysicalResponseId() { CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); when(event.getPhysicalResourceId()).thenReturn("This-Is-Ignored"); - Context context = mock(Context.class); - when(context.getLogStreamName()).thenReturn("My-Log-Stream-Name"); + Context context = new TestLambdaContext(); String customPhysicalResourceId = "Custom-Physical-Resource-ID"; ResponseBody body = new ResponseBody( - event, context, Response.Status.SUCCESS, customPhysicalResourceId, false); + event, Response.Status.SUCCESS, customPhysicalResourceId, false, + "See the details in CloudWatch Log Stream: " + context.getLogStreamName()); assertThat(body.getPhysicalResourceId()).isEqualTo(customPhysicalResourceId); } @Test void responseBodyWithNullDataNode() { CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); - ResponseBody responseBody = new ResponseBody(event, context, Response.Status.FAILED, null, true); + ResponseBody responseBody = new ResponseBody(event, Response.Status.FAILED, null, true, + "See the details in CloudWatch Log Stream: " + context.getLogStreamName()); String actualJson = responseBody.toObjectNode(null).toString(); String expectedJson = "{" + "\"Status\":\"FAILED\"," + - "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," + + "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," + "\"PhysicalResourceId\":null," + "\"StackId\":null," + "\"RequestId\":null," + @@ -155,17 +162,18 @@ void responseBodyWithNullDataNode() { @Test void responseBodyWithNonNullDataNode() { CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); ObjectNode dataNode = ResponseBody.MAPPER.createObjectNode(); dataNode.put("foo", "bar"); dataNode.put("baz", 10); - ResponseBody responseBody = new ResponseBody(event, context, Response.Status.FAILED, null, true); + ResponseBody responseBody = new ResponseBody(event, Response.Status.FAILED, null, true, + "See the details in CloudWatch Log Stream: " + context.getLogStreamName()); String actualJson = responseBody.toObjectNode(dataNode).toString(); String expectedJson = "{" + "\"Status\":\"FAILED\"," + - "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," + + "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," + "\"PhysicalResourceId\":null," + "\"StackId\":null," + "\"RequestId\":null," + @@ -179,40 +187,39 @@ void responseBodyWithNonNullDataNode() { @Test void defaultStatusIsSuccess() { CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); ResponseBody body = new ResponseBody( - event, context, null, null, false); + event, null, null, false, "See the details in CloudWatch Log Stream: " + context.getLogStreamName()); assertThat(body.getStatus()).isEqualTo("SUCCESS"); } @Test void customStatus() { CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); ResponseBody body = new ResponseBody( - event, context, Response.Status.FAILED, null, false); + event, Response.Status.FAILED, null, false, + "See the details in CloudWatch Log Stream: " + context.getLogStreamName()); assertThat(body.getStatus()).isEqualTo("FAILED"); } @Test void reasonIncludesLogStreamName() { CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); - - String logStreamName = "My-Log-Stream-Name"; - Context context = mock(Context.class); - when(context.getLogStreamName()).thenReturn(logStreamName); + Context context = new TestLambdaContext(); ResponseBody body = new ResponseBody( - event, context, Response.Status.SUCCESS, null, false); - assertThat(body.getReason()).contains(logStreamName); + event, Response.Status.SUCCESS, null, false, + "See the details in CloudWatch Log Stream: " + context.getLogStreamName()); + assertThat(body.getReason()).contains(context.getLogStreamName()); } @Test - public void sendWithNoResponseData() throws Exception { + void sendWithNoResponseData() throws Exception { CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); CloudFormationResponse cfnResponse = testableCloudFormationResponse(); HttpExecuteResponse response = cfnResponse.send(event, context); @@ -220,8 +227,8 @@ public void sendWithNoResponseData() throws Exception { String actualJson = responseAsString(response); String expectedJson = "{" + "\"Status\":\"SUCCESS\"," + - "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," + - "\"PhysicalResourceId\":null," + + "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," + + "\"PhysicalResourceId\":\"test-log-stream\"," + "\"StackId\":null," + "\"RequestId\":null," + "\"LogicalResourceId\":null," + @@ -232,9 +239,9 @@ public void sendWithNoResponseData() throws Exception { } @Test - public void sendWithNonNullResponseData() throws Exception { + void sendWithNonNullResponseData() throws Exception { CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); CloudFormationResponse cfnResponse = testableCloudFormationResponse(); Map<String, String> responseData = new LinkedHashMap<>(); @@ -246,8 +253,8 @@ public void sendWithNonNullResponseData() throws Exception { String actualJson = responseAsString(response); String expectedJson = "{" + "\"Status\":\"SUCCESS\"," + - "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," + - "\"PhysicalResourceId\":null," + + "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," + + "\"PhysicalResourceId\":\"test-log-stream\"," + "\"StackId\":null," + "\"RequestId\":null," + "\"LogicalResourceId\":null," + @@ -260,15 +267,15 @@ public void sendWithNonNullResponseData() throws Exception { @Test void responseBodyStreamNullResponseDefaultsToSuccessStatus() throws Exception { CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); CloudFormationResponse cfnResponse = testableCloudFormationResponse(); StringInputStream stream = cfnResponse.responseBodyStream(event, context, null); String expectedJson = "{" + "\"Status\":\"SUCCESS\"," + - "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," + - "\"PhysicalResourceId\":null," + + "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," + + "\"PhysicalResourceId\":\"test-log-stream\"," + "\"StackId\":null," + "\"RequestId\":null," + "\"LogicalResourceId\":null," + @@ -281,15 +288,15 @@ void responseBodyStreamNullResponseDefaultsToSuccessStatus() throws Exception { @Test void responseBodyStreamSuccessResponse() throws Exception { CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); CloudFormationResponse cfnResponse = testableCloudFormationResponse(); - StringInputStream stream = cfnResponse.responseBodyStream(event, context, Response.success()); + StringInputStream stream = cfnResponse.responseBodyStream(event, context, Response.success(null)); String expectedJson = "{" + "\"Status\":\"SUCCESS\"," + - "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," + - "\"PhysicalResourceId\":null," + + "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," + + "\"PhysicalResourceId\":\"test-log-stream\"," + "\"StackId\":null," + "\"RequestId\":null," + "\"LogicalResourceId\":null," + @@ -302,15 +309,38 @@ void responseBodyStreamSuccessResponse() throws Exception { @Test void responseBodyStreamFailedResponse() throws Exception { CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); - Context context = mock(Context.class); + Context context = new TestLambdaContext(); CloudFormationResponse cfnResponse = testableCloudFormationResponse(); - StringInputStream stream = cfnResponse.responseBodyStream(event, context, Response.failed()); + StringInputStream stream = cfnResponse.responseBodyStream(event, context, Response.failed(null)); String expectedJson = "{" + "\"Status\":\"FAILED\"," + - "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," + - "\"PhysicalResourceId\":null," + + "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," + + "\"PhysicalResourceId\":\"test-log-stream\"," + + "\"StackId\":null," + + "\"RequestId\":null," + + "\"LogicalResourceId\":null," + + "\"NoEcho\":false," + + "\"Data\":null" + + "}"; + assertThat(stream.getString()).isEqualTo(expectedJson); + } + + @Test + void responseBodyStreamFailedResponseWithReason() throws Exception { + CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent(); + Context context = new TestLambdaContext(); + CloudFormationResponse cfnResponse = testableCloudFormationResponse(); + String failureReason = "Failed test reason"; + Response failedResponseWithReason = Response.builder().status(Response.Status.FAILED).reason(failureReason) + .build(); + StringInputStream stream = cfnResponse.responseBodyStream(event, context, failedResponseWithReason); + + String expectedJson = "{" + + "\"Status\":\"FAILED\"," + + "\"Reason\":\"" + failureReason + "\"," + + "\"PhysicalResourceId\":\"test-log-stream\"," + "\"StackId\":null," + "\"RequestId\":null," + "\"LogicalResourceId\":null," + diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExceptionThrowingHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExceptionThrowingHandler.java new file mode 100644 index 000000000..dd2d1c853 --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExceptionThrowingHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; + +import software.amazon.lambda.powertools.cloudformation.Response.Status; + +public class ExceptionThrowingHandler extends ExpectedStatusResourceHandler { + public ExceptionThrowingHandler() { + super(Status.FAILED); + } + + @Override + protected Response create(CloudFormationCustomResourceEvent event, Context context) { + throw new RuntimeException("This exception is intentional for testing"); + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExpectedStatusResourceHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExpectedStatusResourceHandler.java new file mode 100644 index 000000000..7992c712c --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExpectedStatusResourceHandler.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; + +import org.mockito.ArgumentMatcher; + +import software.amazon.lambda.powertools.cloudformation.Response.Status; + +/** + * Creates a handler that will expect the Response to be sent with an expected + * status. Will throw an AssertionError + * if the method is sent with an unexpected status. + */ +public class ExpectedStatusResourceHandler extends NoOpCustomResourceHandler { + private final Status expectedStatus; + + public ExpectedStatusResourceHandler(Status expectedStatus) { + this.expectedStatus = expectedStatus; + } + + @Override + CloudFormationResponse buildResponseClient() { + // create a CloudFormationResponse that fails if invoked with unexpected status + CloudFormationResponse cfnResponse = mock(CloudFormationResponse.class); + try { + when(cfnResponse.send(any(), any(), org.mockito.ArgumentMatchers.argThat(new ArgumentMatcher<Response>() { + @Override + public boolean matches(Response resp) { + return resp != null && resp.getStatus() != expectedStatus; + } + }))).thenThrow(new AssertionError("Expected response's status to be " + expectedStatus)); + } catch (IOException | CustomResourceResponseException e) { + // this should never happen + throw new RuntimeException("Unexpected mocking exception", e); + } + return cfnResponse; + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExplicitSuccessResponseHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExplicitSuccessResponseHandler.java new file mode 100644 index 000000000..2b11f8020 --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExplicitSuccessResponseHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; + +import software.amazon.lambda.powertools.cloudformation.Response.Status; + +public class ExplicitSuccessResponseHandler extends ExpectedStatusResourceHandler { + public ExplicitSuccessResponseHandler() { + super(Status.SUCCESS); + } + + @Override + protected Response create(CloudFormationCustomResourceEvent event, Context context) { + return Response.builder().value("whatever").status(Status.SUCCESS).build(); + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailToSendResponseHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailToSendResponseHandler.java new file mode 100644 index 000000000..218994c56 --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailToSendResponseHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; + +/** + * Always fails to send the response + */ +public class FailToSendResponseHandler extends NoOpCustomResourceHandler { + @Override + CloudFormationResponse buildResponseClient() { + CloudFormationResponse cfnResponse = mock(CloudFormationResponse.class); + try { + when(cfnResponse.send(any(), any())) + .thenThrow(new IOException("Intentional send failure")); + when(cfnResponse.send(any(), any(), any())) + .thenThrow(new IOException("Intentional send failure")); + } catch (IOException | CustomResourceResponseException e) { + // this should never happen + throw new RuntimeException("Unexpected mocking exception", e); + } + return cfnResponse; + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailedResponseHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailedResponseHandler.java new file mode 100644 index 000000000..787713535 --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailedResponseHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; + +import software.amazon.lambda.powertools.cloudformation.Response.Status; + +public class FailedResponseHandler extends ExpectedStatusResourceHandler { + public FailedResponseHandler() { + super(Status.FAILED); + } + + @Override + protected Response create(CloudFormationCustomResourceEvent event, Context context) { + return Response.builder().value("whatever").status(Status.FAILED).build(); + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailedSendHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailedSendHandler.java new file mode 100644 index 000000000..fe88d8895 --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailedSendHandler.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; + +public class FailedSendHandler extends FailToSendResponseHandler { + @Override + protected Response create(CloudFormationCustomResourceEvent event, Context context) { + throw new RuntimeException("This exception is intentional for testing"); + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/NoOpCustomResourceHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/NoOpCustomResourceHandler.java new file mode 100644 index 000000000..0271c36f5 --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/NoOpCustomResourceHandler.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation; + +import static org.mockito.Mockito.mock; + +import software.amazon.awssdk.http.SdkHttpClient; + +/** + * Uses a mocked CloudFormationResponse to avoid sending actual HTTP requests. + */ +public class NoOpCustomResourceHandler extends NullCustomResourceHandler { + + public NoOpCustomResourceHandler() { + super(mock(SdkHttpClient.class)); + } + + @Override + CloudFormationResponse buildResponseClient() { + return mock(CloudFormationResponse.class); + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/NullCustomResourceHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/NullCustomResourceHandler.java new file mode 100644 index 000000000..a44e2d57e --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/NullCustomResourceHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; + +import software.amazon.awssdk.http.SdkHttpClient; + +/** + * Bare-bones implementation that returns null for abstract methods. + */ +public class NullCustomResourceHandler extends AbstractCustomResourceHandler { + public NullCustomResourceHandler() { + } + + public NullCustomResourceHandler(SdkHttpClient client) { + super(client); + } + + @Override + protected Response create(CloudFormationCustomResourceEvent event, Context context) { + return null; + } + + @Override + protected Response update(CloudFormationCustomResourceEvent event, Context context) { + return null; + } + + @Override + protected Response delete(CloudFormationCustomResourceEvent event, Context context) { + return null; + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ResponseTest.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ResponseTest.java index e97a1a5ba..726bcbeee 100644 --- a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ResponseTest.java +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ResponseTest.java @@ -1,28 +1,30 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.cloudformation; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.HashMap; import java.util.Map; -import static org.assertj.core.api.Assertions.assertThat; - -public class ResponseTest { - - static class DummyBean { - private final Object propertyWithLongName; +import org.junit.jupiter.api.Test; - DummyBean(Object propertyWithLongName) { - this.propertyWithLongName = propertyWithLongName; - } +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; - @SuppressWarnings("unused") - public Object getPropertyWithLongName() { - return propertyWithLongName; - } - } +class ResponseTest { @Test void defaultValues() { @@ -33,11 +35,13 @@ void defaultValues() { assertThat(response.getStatus()).isEqualTo(Response.Status.SUCCESS); assertThat(response.getPhysicalResourceId()).isNull(); assertThat(response.isNoEcho()).isFalse(); + assertThat(response.getReason()).isNull(); assertThat(response.toString()).contains("JSON = null"); assertThat(response.toString()).contains("Status = SUCCESS"); assertThat(response.toString()).contains("PhysicalResourceId = null"); assertThat(response.toString()).contains("NoEcho = false"); + assertThat(response.toString()).contains("Reason = null"); } @Test @@ -61,6 +65,27 @@ void explicitNullValues() { assertThat(response.toString()).contains("NoEcho = false"); } + @Test + void explicitReasonWithDefaultValues() { + String reason = "test"; + Response response = Response.builder() + .reason(reason) + .build(); + assertThat(response).isNotNull(); + assertThat(response.getJsonNode()).isNull(); + assertThat(response.getStatus()).isEqualTo(Response.Status.SUCCESS); + assertThat(response.getPhysicalResourceId()).isNull(); + assertThat(response.isNoEcho()).isFalse(); + assertThat(response.getReason()).isNotNull(); + assertThat(response.getReason()).isEqualTo(reason); + + assertThat(response.toString()).contains("JSON = null"); + assertThat(response.toString()).contains("Status = SUCCESS"); + assertThat(response.toString()).contains("PhysicalResourceId = null"); + assertThat(response.toString()).contains("NoEcho = false"); + assertThat(response.toString()).contains("Reason = " + reason); + } + @Test void customNonJsonRelatedValues() { Response response = Response.builder() @@ -92,7 +117,7 @@ void jsonMapValueWithDefaultObjectMapper() { String expected = "{\"foo\":\"bar\"}"; assertThat(response.getJsonNode()).isNotNull(); - assertThat(response.getJsonNode().toString()).isEqualTo(expected); + assertThat(response.getJsonNode()).hasToString(expected); assertThat(response.toString()).contains("JSON = " + expected); } @@ -105,7 +130,7 @@ void jsonObjectValueWithDefaultObjectMapper() { .build(); String expected = "{\"PropertyWithLongName\":\"test\"}"; - assertThat(response.getJsonNode().toString()).isEqualTo(expected); + assertThat(response.getJsonNode()).hasToString(expected); assertThat(response.toString()).contains("JSON = " + expected); } @@ -119,7 +144,7 @@ void jsonObjectValueWithNullObjectMapper() { .build(); String expected = "{\"PropertyWithLongName\":\"test\"}"; - assertThat(response.getJsonNode().toString()).isEqualTo(expected); + assertThat(response.getJsonNode()).hasToString(expected); assertThat(response.toString()).contains("JSON = " + expected); } @@ -135,7 +160,7 @@ void jsonObjectValueWithCustomObjectMapper() { .build(); String expected = "{\"property-with-long-name\":10}"; - assertThat(response.getJsonNode().toString()).isEqualTo(expected); + assertThat(response.getJsonNode()).hasToString(expected); assertThat(response.toString()).contains("JSON = " + expected); } @@ -154,13 +179,13 @@ void jsonObjectValueWithPostConfiguredObjectMapper() { customMapper.setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_CAMEL_CASE); String expected = "{\"property-with-long-name\":10}"; - assertThat(response.getJsonNode().toString()).isEqualTo(expected); + assertThat(response.getJsonNode()).hasToString(expected); assertThat(response.toString()).contains("JSON = " + expected); } @Test void successFactoryMethod() { - Response response = Response.success(); + Response response = Response.success(null); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(Response.Status.SUCCESS); @@ -168,9 +193,22 @@ void successFactoryMethod() { @Test void failedFactoryMethod() { - Response response = Response.failed(); + Response response = Response.failed(null); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(Response.Status.FAILED); } + + static class DummyBean { + private final Object propertyWithLongName; + + DummyBean(Object propertyWithLongName) { + this.propertyWithLongName = propertyWithLongName; + } + + @SuppressWarnings("unused") + public Object getPropertyWithLongName() { + return propertyWithLongName; + } + } } diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/SuccessResponseHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/SuccessResponseHandler.java new file mode 100644 index 000000000..18538bc9d --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/SuccessResponseHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; + +import software.amazon.lambda.powertools.cloudformation.Response.Status; + +public class SuccessResponseHandler extends ExpectedStatusResourceHandler { + public SuccessResponseHandler() { + super(Status.SUCCESS); + } + + @Override + protected Response create(CloudFormationCustomResourceEvent event, Context context) { + return Response.builder().value("whatever").build(); + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/SuccessfulSendHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/SuccessfulSendHandler.java new file mode 100644 index 000000000..074d1499e --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/SuccessfulSendHandler.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; + +public class SuccessfulSendHandler extends FailToSendResponseHandler { + @Override + protected Response create(CloudFormationCustomResourceEvent event, Context context) { + return Response.builder().value("Failure happens on send").build(); + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/NoPhysicalResourceIdSetHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/NoPhysicalResourceIdSetHandler.java new file mode 100644 index 000000000..4fb14110c --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/NoPhysicalResourceIdSetHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; + +import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler; +import software.amazon.lambda.powertools.cloudformation.Response; + +public class NoPhysicalResourceIdSetHandler extends AbstractCustomResourceHandler { + + @Override + protected Response create(CloudFormationCustomResourceEvent event, Context context) { + return Response.success(null); + } + + @Override + protected Response update(CloudFormationCustomResourceEvent event, Context context) { + return Response.success(null); + } + + @Override + protected Response delete(CloudFormationCustomResourceEvent event, Context context) { + return Response.success(null); + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/PhysicalResourceIdSetHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/PhysicalResourceIdSetHandler.java new file mode 100644 index 000000000..a01319342 --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/PhysicalResourceIdSetHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; + +import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler; +import software.amazon.lambda.powertools.cloudformation.Response; + +public class PhysicalResourceIdSetHandler extends AbstractCustomResourceHandler { + + private final String physicalResourceId; + private final boolean callsSucceed; + + public PhysicalResourceIdSetHandler(String physicalResourceId, boolean callsSucceed) { + this.physicalResourceId = physicalResourceId; + this.callsSucceed = callsSucceed; + } + + @Override + protected Response create(CloudFormationCustomResourceEvent event, Context context) { + return callsSucceed ? Response.success(physicalResourceId) : Response.failed(physicalResourceId); + } + + @Override + protected Response update(CloudFormationCustomResourceEvent event, Context context) { + return callsSucceed ? Response.success(physicalResourceId) : Response.failed(physicalResourceId); + } + + @Override + protected Response delete(CloudFormationCustomResourceEvent event, Context context) { + return callsSucceed ? Response.success(physicalResourceId) : Response.failed(physicalResourceId); + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/RuntimeExceptionThrownHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/RuntimeExceptionThrownHandler.java new file mode 100644 index 000000000..10e3801c2 --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/RuntimeExceptionThrownHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.cloudformation.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; + +import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler; +import software.amazon.lambda.powertools.cloudformation.Response; + +public class RuntimeExceptionThrownHandler extends AbstractCustomResourceHandler { + + @Override + protected Response create(CloudFormationCustomResourceEvent event, Context context) { + throw new RuntimeException("failure"); + } + + @Override + protected Response update(CloudFormationCustomResourceEvent event, Context context) { + throw new RuntimeException("failure"); + } + + @Override + protected Response delete(CloudFormationCustomResourceEvent event, Context context) { + throw new RuntimeException("failure"); + } +} diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/internal/CloudformationUserAgentInterceptorTest.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/internal/CloudformationUserAgentInterceptorTest.java new file mode 100644 index 000000000..ee500d006 --- /dev/null +++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/internal/CloudformationUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.cloudformation.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class CloudformationUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/CLOUDFORMATION/"); + } +} diff --git a/powertools-cloudformation/src/test/resources/simplelogger.properties b/powertools-cloudformation/src/test/resources/simplelogger.properties new file mode 100644 index 000000000..e8ba3a5fa --- /dev/null +++ b/powertools-cloudformation/src/test/resources/simplelogger.properties @@ -0,0 +1,7 @@ +org.slf4j.simpleLogger.logFile=target/cloudformation-test.log +org.slf4j.simpleLogger.defaultLogLevel=warn +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS +org.slf4j.simpleLogger.showThreadName=false +org.slf4j.simpleLogger.showLogName=true +org.slf4j.simpleLogger.showShortLogName=false diff --git a/powertools-common/pom.xml b/powertools-common/pom.xml new file mode 100644 index 000000000..75ef10beb --- /dev/null +++ b/powertools-common/pom.xml @@ -0,0 +1,189 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <artifactId>powertools-common</artifactId> + <packaging>jar</packaging> + + <parent> + <artifactId>powertools-parent</artifactId> + <groupId>software.amazon.lambda</groupId> + <version>2.9.0</version> + </parent> + + <name>Powertools for AWS Lambda (Java) - Common Internal Utilities</name> + <description>Internal utilities shared by the Powertools for AWS Lambda (Java) modules. Do not use directly in your project.</description> + + <dependencies> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>utils-lite</artifactId> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit-pioneer</groupId> + <artifactId>junit-pioneer</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjweaver</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <profiles> + <profile> + <id>generate-graalvm-files</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-common</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + + <build> + <resources> + <resource> + <directory>src/main/resources-filtered</directory> + <filtering>true</filtering> + </resource> + <!-- GraalVM Native Image Configuration Files --> + <resource> + <directory>src/main/resources</directory> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>test-jar</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/ClassPreLoader.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/ClassPreLoader.java new file mode 100644 index 000000000..ca599429e --- /dev/null +++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/ClassPreLoader.java @@ -0,0 +1,91 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.common.internal; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.net.URL; +import java.net.URLConnection; +import java.util.Enumeration; + +/** + * Used to preload classes to support automatic priming for SnapStart + */ +public final class ClassPreLoader { + public static final String CLASSES_FILE = "classesloaded.txt"; + + private ClassPreLoader() { + // Hide default constructor + } + + /** + * Initializes the classes listed in the classesloaded resource + */ + public static void preloadClasses() { + try { + Enumeration<URL> files = ClassPreLoader.class.getClassLoader().getResources(CLASSES_FILE); + // If there are multiple files, preload classes from all of them + while (files.hasMoreElements()) { + URL url = files.nextElement(); + URLConnection conn = url.openConnection(); + conn.setUseCaches(false); + InputStream is = conn.getInputStream(); + preloadClassesFromStream(is); + } + } catch (IOException ignored) { + // No action is required if preloading fails for any reason + } + } + + /** + * Loads the list of classes passed as a stream + * + * @param is + */ + private static void preloadClassesFromStream(InputStream is) { + try (is; + InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8); + BufferedReader reader = new BufferedReader(isr)) { + String line; + while ((line = reader.readLine()) != null) { + int idx = line.indexOf('#'); + if (idx != -1) { + line = line.substring(0, idx); + } + final String className = line.stripTrailing(); + if (!className.isBlank()) { + loadClassIfFound(className); + } + } + } catch (Exception ignored) { + // No action is required if preloading fails for any reason + } + } + + /** + * Initializes the class with given name if found, ignores otherwise + * + * @param className + */ + private static void loadClassIfFound(String className) { + try { + Class.forName(className, true, ClassPreLoader.class.getClassLoader()); + } catch (ClassNotFoundException e) { + // No action is required if the class with given name cannot be found + } + } +} \ No newline at end of file diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java new file mode 100644 index 000000000..4c4e8e9db --- /dev/null +++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.common.internal; + +public final class LambdaConstants { + private LambdaConstants() { + // Constant holder class + } + + public static final String LAMBDA_FUNCTION_NAME_ENV = "AWS_LAMBDA_FUNCTION_NAME"; + public static final String AWS_REGION_ENV = "AWS_REGION"; + public static final String X_AMZN_TRACE_ID = "_X_AMZN_TRACE_ID"; + public static final String XRAY_TRACE_HEADER = "com.amazonaws.xray.traceHeader"; + public static final String AWS_LAMBDA_X_TRACE_ID = "AWS_LAMBDA_X_TRACE_ID"; + public static final String AWS_SAM_LOCAL = "AWS_SAM_LOCAL"; + public static final String ROOT_EQUALS = "Root="; + public static final String POWERTOOLS_SERVICE_NAME = "POWERTOOLS_SERVICE_NAME"; + public static final String SERVICE_UNDEFINED = "service_undefined"; + public static final String AWS_LAMBDA_INITIALIZATION_TYPE = "AWS_LAMBDA_INITIALIZATION_TYPE"; + public static final String ON_DEMAND_INVOCATION_TYPE = "on-demand"; +} diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java new file mode 100644 index 000000000..15bff15d6 --- /dev/null +++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java @@ -0,0 +1,132 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.common.internal; + +import static java.util.Optional.empty; +import static java.util.Optional.of; +import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getProperty; +import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Optional; + +import org.aspectj.lang.ProceedingJoinPoint; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; + +import software.amazon.awssdk.utilslite.SdkInternalThreadLocal; + +public final class LambdaHandlerProcessor { + + // serviceName cannot be final for testing purposes + private static String serviceName = calculateServiceName(); + + private static Boolean isColdStart = null; + + private LambdaHandlerProcessor() { + // Hide default constructor + } + + private static String calculateServiceName() { + return null != getenv(LambdaConstants.POWERTOOLS_SERVICE_NAME) + ? getenv(LambdaConstants.POWERTOOLS_SERVICE_NAME) + : LambdaConstants.SERVICE_UNDEFINED; + } + + public static boolean isHandlerMethod(final ProceedingJoinPoint pjp) { + return placedOnRequestHandler(pjp) || placedOnStreamHandler(pjp); + } + + /** + * The class needs to implement RequestHandler interface + * The function needs to have exactly two arguments + * The second argument needs to be of type com.amazonaws.services.lambda.runtime.Context + * @param pjp + * @return + */ + public static boolean placedOnRequestHandler(final ProceedingJoinPoint pjp) { + return RequestHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType()) + && pjp.getArgs().length == 2 + && pjp.getArgs()[1] instanceof Context; + } + + public static boolean placedOnStreamHandler(final ProceedingJoinPoint pjp) { + return RequestStreamHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType()) + && pjp.getArgs().length == 3 + && pjp.getArgs()[0] instanceof InputStream + && pjp.getArgs()[1] instanceof OutputStream + && pjp.getArgs()[2] instanceof Context; + } + + public static Context extractContext(final ProceedingJoinPoint pjp) { + if (placedOnRequestHandler(pjp)) { + return (Context) pjp.getArgs()[1]; + } else if (placedOnStreamHandler(pjp)) { + return (Context) pjp.getArgs()[2]; + } else { + return null; + } + } + + public static String serviceName() { + return serviceName; + } + + // Method used for testing purposes + protected static void resetServiceName() { + serviceName = calculateServiceName(); + } + + public static boolean isColdStart() { + if (isColdStart != null) { + return isColdStart; + } + + String initType = System.getenv(LambdaConstants.AWS_LAMBDA_INITIALIZATION_TYPE); + isColdStart = LambdaConstants.ON_DEMAND_INVOCATION_TYPE.equals(initType); + + return isColdStart; + } + + public static void coldStartDone() { + isColdStart = false; + } + + public static boolean isSamLocal() { + return "true".equals(getenv(LambdaConstants.AWS_SAM_LOCAL)); + } + + public static Optional<String> getXrayTraceId() { + // Try SdkInternalThreadLocal first + String traceId = SdkInternalThreadLocal.get(LambdaConstants.AWS_LAMBDA_X_TRACE_ID); + + // Fallback to environment based approach + if (traceId == null) { + traceId = getenv(LambdaConstants.X_AMZN_TRACE_ID); + } + // For the Java Lambda 17+ runtime, the Trace ID is set as a System Property + if (traceId == null) { + traceId = getProperty(LambdaConstants.XRAY_TRACE_HEADER); + } + + if (traceId != null) { + return of(traceId.split(";")[0].replace(LambdaConstants.ROOT_EQUALS, "")); + } + return empty(); + } +} diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/SystemWrapper.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/SystemWrapper.java new file mode 100644 index 000000000..6dc4e9d9f --- /dev/null +++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/SystemWrapper.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.common.internal; + +public class SystemWrapper { + private SystemWrapper() { + } + + public static String getenv(String name) { + return System.getenv(name); + } + + public static String getProperty(String name) { + return System.getProperty(name); + } +} diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/UserAgentConfigurator.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/UserAgentConfigurator.java new file mode 100644 index 000000000..27b69d5ad --- /dev/null +++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/UserAgentConfigurator.java @@ -0,0 +1,155 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.common.internal; + +import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Can be used to create a string that can server as a User-Agent suffix in requests made with the AWS SDK clients + */ +public final class UserAgentConfigurator { + public static final String NA = "NA"; + public static final String VERSION_KEY = "powertools.version"; + public static final String PT_FEATURE_VARIABLE = "${PT_FEATURE}"; + public static final String PT_EXEC_ENV_VARIABLE = "${PT_EXEC_ENV}"; + public static final String VERSION_PROPERTIES_FILENAME = "version.properties"; + public static final String AWS_EXECUTION_ENV = "AWS_EXECUTION_ENV"; + private static final String SDK_USER_AGENT_APP_ID = "sdk.ua.appId"; + private static final Logger LOG = LoggerFactory.getLogger(UserAgentConfigurator.class); + private static final String NO_OP = "no-op"; + private static final String POWERTOOLS_VERSION = getProjectVersion(); + private static final String USER_AGENT_PATTERN = "PT/" + PT_FEATURE_VARIABLE + "/" + POWERTOOLS_VERSION + " PTENV/" + + PT_EXEC_ENV_VARIABLE; + + private UserAgentConfigurator() { + throw new IllegalStateException("Utility class. Not meant to be instantiated"); + } + + /** + * Retrieves the project version from the version.properties file + * + * @return the project version + */ + static String getProjectVersion() { + return getVersionFromProperties(VERSION_PROPERTIES_FILENAME, VERSION_KEY); + } + + /** + * Retrieves the project version from a properties file. + * The file should be in the resources folder. + * The version is retrieved from the property with the given key. + * + * @param propertyFileName the name of the properties file + * @param versionKey the key of the property that contains the version + * @return the version of the project as configured in the given properties file + */ + static String getVersionFromProperties(String propertyFileName, String versionKey) { + try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(propertyFileName)) { + if (is != null) { + Properties properties = new Properties(); + properties.load(is); + String version = properties.getProperty(versionKey); + if (version != null && !version.isEmpty()) { + return version; + } + } + } catch (IOException e) { + LOG.warn("Unable to read {} file. Using default version.", propertyFileName); + LOG.debug("Exception:", e); + } + return NA; + } + + /** + * Configures the AWS SDK to use Powertools user agent by setting the sdk.ua.appId system property. + * Preserves any user-provided value and replaces any existing Powertools user agent. + * Enforces a 50 character limit to comply with AWS SDK recommendations. + * This should be called during library initialization to ensure the user agent is properly configured. + */ + public static void configureUserAgent(String ptFeature) { + try { + String existingValue = System.getProperty(SDK_USER_AGENT_APP_ID); + String powertoolsUserAgent = getUserAgent(ptFeature); + String newValue; + + if (existingValue == null || existingValue.isEmpty()) { + newValue = powertoolsUserAgent; + } else { + String userValue = extractUserValue(existingValue); + if (userValue.isEmpty()) { + newValue = powertoolsUserAgent; + } else { + newValue = userValue + "/" + powertoolsUserAgent; + } + } + + if (newValue.length() <= 50) { + System.setProperty(SDK_USER_AGENT_APP_ID, newValue); + } + } catch (Exception e) { + // We don't re-raise since we don't want to break the user if something in this logic doesn't work + LOG.warn("Unable to configure user agent system property", e); + } + } + + /** + * Extracts the user-provided value from the existing user agent string by removing any Powertools user agent. + * A Powertools user agent follows the pattern "PT/{FEATURE}/{VERSION} PTENV/{ENV}". + * + * @param existingValue the existing user agent string + * @return the user-provided value without Powertools user agent, or empty string if none exists + */ + static String extractUserValue(String existingValue) { + if (existingValue == null || existingValue.isEmpty()) { + return ""; + } + // Remove Powertools user agent pattern: PT/{FEATURE}/{VERSION} PTENV/{ENV} + String result = existingValue.replaceAll("/?PT/[^/]+/[^ ]+ PTENV/[^ ]+", ""); + return result.trim(); + } + + /** + * Retrieves the user agent string for the Powertools for AWS Lambda. + * It follows the pattern PT/{PT_FEATURE}/{PT_VERSION} PTENV/{PT_EXEC_ENV} + * The version of the project is automatically retrieved. + * The PT_EXEC_ENV is automatically retrieved from the AWS_EXECUTION_ENV environment variable. + * If it AWS_EXECUTION_ENV is not set, PT_EXEC_ENV defaults to "NA" + * + * @param ptFeature a custom feature to be added to the user agent string (e.g. idempotency). + * If null or empty, the default PT_FEATURE is used. + * The default PT_FEATURE is "no-op". + * @return the user agent string + */ + public static String getUserAgent(String ptFeature) { + String awsExecutionEnv = getenv(AWS_EXECUTION_ENV); + String ptExecEnv = awsExecutionEnv != null ? awsExecutionEnv : NA; + String userAgent = USER_AGENT_PATTERN.replace(PT_EXEC_ENV_VARIABLE, ptExecEnv); + + if (ptFeature == null || ptFeature.isEmpty()) { + ptFeature = NO_OP; + } + return userAgent + .replace(PT_FEATURE_VARIABLE, ptFeature.toUpperCase(Locale.ROOT)) + .replace(PT_EXEC_ENV_VARIABLE, ptExecEnv); + } +} diff --git a/powertools-common/src/main/resources-filtered/version.properties b/powertools-common/src/main/resources-filtered/version.properties new file mode 100644 index 000000000..5e95fb588 --- /dev/null +++ b/powertools-common/src/main/resources-filtered/version.properties @@ -0,0 +1,9 @@ +# The filtered properties can have variables that are filled in by system properties or project properties. +# See https://maven.apache.org/plugins/maven-resources-plugin/examples/filter.html +# +# The values are replaced before copying the resources to the main output directory. Therefore, as soon as the build phase is completed, +# the values should have been replaced if the properties are available and if 'filtering' is set to true in the pom.xml +# +# +# The ${project.version} is retrieved from the respective pom.xml property +powertools.version=${project.version} \ No newline at end of file diff --git a/powertools-common/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common/jni-config.json b/powertools-common/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common/jni-config.json new file mode 100644 index 000000000..8ea90d67f --- /dev/null +++ b/powertools-common/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common/jni-config.json @@ -0,0 +1,22 @@ +[ +{ + "name":"java.lang.String", + "methods":[{"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }] +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"org.apache.maven.surefire.booter.ForkedBooter", + "methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }] +}, +{ + "name":"sun.instrument.InstrumentationImpl", + "methods":[{"name":"<init>","parameterTypes":["long","boolean","boolean","boolean"] }, {"name":"loadClassAndCallAgentmain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"loadClassAndCallPremain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"transform","parameterTypes":["java.lang.Module","java.lang.ClassLoader","java.lang.String","java.lang.Class","java.security.ProtectionDomain","byte[]","boolean"] }] +}, +{ + "name":"sun.management.VMManagementImpl", + "fields":[{"name":"compTimeMonitoringSupport"}, {"name":"currentThreadCpuTimeSupport"}, {"name":"objectMonitorUsageSupport"}, {"name":"otherThreadCpuTimeSupport"}, {"name":"remoteDiagnosticCommandsSupport"}, {"name":"synchronizerUsageSupport"}, {"name":"threadAllocatedMemorySupport"}, {"name":"threadContentionMonitoringSupport"}] +} +] diff --git a/powertools-common/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common/reflect-config.json b/powertools-common/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common/reflect-config.json new file mode 100644 index 000000000..54afdb74e --- /dev/null +++ b/powertools-common/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common/reflect-config.json @@ -0,0 +1,189 @@ +[ +{ + "name":"com.amazonaws.services.lambda.runtime.Context", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getAwsRequestId","parameterTypes":[] }, {"name":"getClientContext","parameterTypes":[] }, {"name":"getFunctionName","parameterTypes":[] }, {"name":"getFunctionVersion","parameterTypes":[] }, {"name":"getIdentity","parameterTypes":[] }, {"name":"getInvokedFunctionArn","parameterTypes":[] }, {"name":"getLogGroupName","parameterTypes":[] }, {"name":"getLogStreamName","parameterTypes":[] }, {"name":"getLogger","parameterTypes":[] }, {"name":"getMemoryLimitInMB","parameterTypes":[] }, {"name":"getRemainingTimeInMillis","parameterTypes":[] }] +}, +{ + "name":"com.sun.tools.attach.VirtualMachine" +}, +{ + "name":"java.io.Closeable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.io.Flushable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.io.InputStream", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"available","parameterTypes":[] }, {"name":"close","parameterTypes":[] }, {"name":"mark","parameterTypes":["int"] }, {"name":"markSupported","parameterTypes":[] }, {"name":"read","parameterTypes":[] }, {"name":"read","parameterTypes":["byte[]"] }, {"name":"read","parameterTypes":["byte[]","int","int"] }, {"name":"readAllBytes","parameterTypes":[] }, {"name":"readNBytes","parameterTypes":["int"] }, {"name":"readNBytes","parameterTypes":["byte[]","int","int"] }, {"name":"reset","parameterTypes":[] }, {"name":"skip","parameterTypes":["long"] }, {"name":"skipNBytes","parameterTypes":["long"] }, {"name":"transferTo","parameterTypes":["java.io.OutputStream"] }] +}, +{ + "name":"java.io.OutputStream", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"close","parameterTypes":[] }, {"name":"flush","parameterTypes":[] }, {"name":"write","parameterTypes":["int"] }, {"name":"write","parameterTypes":["byte[]"] }, {"name":"write","parameterTypes":["byte[]","int","int"] }] +}, +{ + "name":"java.io.Serializable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.AutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.Class", + "methods":[{"name":"forName","parameterTypes":["java.lang.String"] }, {"name":"getAnnotatedInterfaces","parameterTypes":[] }, {"name":"getAnnotatedSuperclass","parameterTypes":[] }, {"name":"getDeclaredMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getModule","parameterTypes":[] }, {"name":"getNestHost","parameterTypes":[] }, {"name":"getNestMembers","parameterTypes":[] }, {"name":"getPermittedSubclasses","parameterTypes":[] }, {"name":"getRecordComponents","parameterTypes":[] }, {"name":"isNestmateOf","parameterTypes":["java.lang.Class"] }, {"name":"isRecord","parameterTypes":[] }, {"name":"isSealed","parameterTypes":[] }] +}, +{ + "name":"java.lang.ClassLoader", + "methods":[{"name":"getDefinedPackage","parameterTypes":["java.lang.String"] }, {"name":"getUnnamedModule","parameterTypes":[] }, {"name":"registerAsParallelCapable","parameterTypes":[] }] +}, +{ + "name":"java.lang.Module", + "methods":[{"name":"addExports","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"addReads","parameterTypes":["java.lang.Module"] }, {"name":"canRead","parameterTypes":["java.lang.Module"] }, {"name":"getClassLoader","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPackages","parameterTypes":[] }, {"name":"getResourceAsStream","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"isNamed","parameterTypes":[] }, {"name":"isOpen","parameterTypes":["java.lang.String","java.lang.Module"] }] +}, +{ + "name":"java.lang.Object", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"clone","parameterTypes":[] }, {"name":"getClass","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"java.lang.ProcessEnvironment", + "fields":[{"name":"theCaseInsensitiveEnvironment"}, {"name":"theEnvironment"}] +}, +{ + "name":"java.lang.ProcessHandle", + "methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime", + "methods":[{"name":"version","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime$Version", + "methods":[{"name":"feature","parameterTypes":[] }] +}, +{ + "name":"java.lang.StackWalker" +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getSecurityManager","parameterTypes":[] }] +}, +{ + "name":"java.lang.annotation.Retention", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.annotation.Target", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.invoke.MethodHandle", + "methods":[{"name":"bindTo","parameterTypes":["java.lang.Object"] }, {"name":"invokeWithArguments","parameterTypes":["java.lang.Object[]"] }] +}, +{ + "name":"java.lang.invoke.MethodHandles", + "methods":[{"name":"lookup","parameterTypes":[] }] +}, +{ + "name":"java.lang.invoke.MethodHandles$Lookup", + "methods":[{"name":"findVirtual","parameterTypes":["java.lang.Class","java.lang.String","java.lang.invoke.MethodType"] }] +}, +{ + "name":"java.lang.invoke.MethodType", + "methods":[{"name":"methodType","parameterTypes":["java.lang.Class","java.lang.Class[]"] }] +}, +{ + "name":"java.lang.reflect.AccessibleObject", + "methods":[{"name":"setAccessible","parameterTypes":["boolean"] }] +}, +{ + "name":"java.lang.reflect.AnnotatedArrayType", + "methods":[{"name":"getAnnotatedGenericComponentType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedType", + "methods":[{"name":"getType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Executable", + "methods":[{"name":"getAnnotatedExceptionTypes","parameterTypes":[] }, {"name":"getAnnotatedParameterTypes","parameterTypes":[] }, {"name":"getAnnotatedReceiverType","parameterTypes":[] }, {"name":"getParameterCount","parameterTypes":[] }, {"name":"getParameters","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"getAnnotatedReturnType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Parameter", + "methods":[{"name":"getModifiers","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"isNamePresent","parameterTypes":[] }] +}, +{ + "name":"java.security.AccessController", + "methods":[{"name":"doPrivileged","parameterTypes":["java.security.PrivilegedAction"] }, {"name":"doPrivileged","parameterTypes":["java.security.PrivilegedExceptionAction"] }] +}, +{ + "name":"java.util.Collections$UnmodifiableMap", + "fields":[{"name":"m"}] +}, +{ + "name":"java.util.concurrent.ForkJoinTask", + "fields":[{"name":"aux"}, {"name":"status"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"jdk.internal.misc.Unsafe" +}, +{ + "name":"kotlin.jvm.JvmInline" +}, +{ + "name":"org.apiguardian.api.API", + "queryAllPublicMethods":true +}, +{ + "name":"org.aspectj.lang.JoinPoint", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getArgs","parameterTypes":[] }, {"name":"getKind","parameterTypes":[] }, {"name":"getSignature","parameterTypes":[] }, {"name":"getSourceLocation","parameterTypes":[] }, {"name":"getStaticPart","parameterTypes":[] }, {"name":"getTarget","parameterTypes":[] }, {"name":"getThis","parameterTypes":[] }, {"name":"toLongString","parameterTypes":[] }, {"name":"toShortString","parameterTypes":[] }] +}, +{ + "name":"org.aspectj.lang.ProceedingJoinPoint", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"proceed","parameterTypes":[] }, {"name":"proceed","parameterTypes":["java.lang.Object[]"] }, {"name":"set$AroundClosure","parameterTypes":["org.aspectj.runtime.internal.AroundClosure"] }, {"name":"stack$AroundClosure","parameterTypes":["org.aspectj.runtime.internal.AroundClosure"] }] +}, +{ + "name":"org.aspectj.lang.Signature", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getDeclaringType","parameterTypes":[] }, {"name":"getDeclaringTypeName","parameterTypes":[] }, {"name":"getModifiers","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"toLongString","parameterTypes":[] }, {"name":"toShortString","parameterTypes":[] }] +}, +{ + "name":"sun.reflect.ReflectionFactory", + "methods":[{"name":"getReflectionFactory","parameterTypes":[] }, {"name":"newConstructorForSerialization","parameterTypes":["java.lang.Class","java.lang.reflect.Constructor"] }] +} +] diff --git a/powertools-common/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common/resource-config.json b/powertools-common/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common/resource-config.json new file mode 100644 index 000000000..6fb5eb95e --- /dev/null +++ b/powertools-common/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-common/resource-config.json @@ -0,0 +1,21 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory\\E" + }, { + "pattern":"\\QMETA-INF/services/org.assertj.core.configuration.Configuration\\E" + }, { + "pattern":"\\QMETA-INF/services/org.assertj.core.presentation.Representation\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" + }, { + "pattern":"\\Qversion.properties\\E" + }]}, + "bundles":[] +} diff --git a/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/ClassPreLoaderTest.java b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/ClassPreLoaderTest.java new file mode 100644 index 000000000..03991688e --- /dev/null +++ b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/ClassPreLoaderTest.java @@ -0,0 +1,34 @@ +package software.amazon.lambda.powertools.common.internal; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class ClassPreLoaderTest { + + // Making this volatile so the Thread Context doesn't need any special handling + static volatile boolean dummyClassLoaded = false; + + /** + * Dummy class to be loaded by ClassPreLoader in test. + * <b>The class name is referenced in <i>powertools-common/src/test/resources/classesloaded.txt</i></b> + * This class is used to verify that the ClassPreLoader can load valid classes. + * The static block sets a flag to indicate that the class has been loaded. + */ + static class DummyClass { + static { + dummyClassLoaded = true; + } + } + @Test + void preloadClasses_shouldIgnoreInvalidClassesAndLoadValidClasses() { + + dummyClassLoaded = false; + // powertools-common/src/test/resources/classesloaded.txt has a class that does not exist + // Verify that the missing class did not throw any exception + assertDoesNotThrow(ClassPreLoader::preloadClasses); + + // When the classloaded.txt is a mixed bag of valid and invalid classes, Valid class must load + assertTrue(dummyClassLoaded, "DummyClass should be loaded"); + } +} \ No newline at end of file diff --git a/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessorTest.java b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessorTest.java new file mode 100644 index 000000000..0726a9e77 --- /dev/null +++ b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessorTest.java @@ -0,0 +1,271 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.common.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Optional; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ClearEnvironmentVariable; +import org.junitpioneer.jupiter.ClearSystemProperty; +import org.junitpioneer.jupiter.SetEnvironmentVariable; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; + +import software.amazon.awssdk.utilslite.SdkInternalThreadLocal; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; + +class LambdaHandlerProcessorTest { + + @AfterEach + void cleanup() { + SdkInternalThreadLocal.clear(); + } + + @Test + void isHandlerMethod_shouldRecognizeRequestHandler() { + Context context = new TestLambdaContext(); + Object[] args = { new Object(), context }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestHandler.class, args); + + assertThat(LambdaHandlerProcessor.isHandlerMethod(pjpMock)).isTrue(); + } + + @Test + void isHandlerMethod_shouldRecognizeRequestStreamHandler() { + Object[] args = { mock(InputStream.class), mock(OutputStream.class), new TestLambdaContext() }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); + + assertThat(LambdaHandlerProcessor.isHandlerMethod(pjpMock)).isTrue(); + } + + @Test + void isHandlerMethod_shouldReturnFalse() { + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(Object.class, new Object[] {}); + + boolean isHandlerMethod = LambdaHandlerProcessor.isHandlerMethod(pjpMock); + + assertThat(isHandlerMethod).isFalse(); + } + + @Test + void placedOnRequestHandler_shouldRecognizeRequestHandler() { + Object[] args = { new Object(), new TestLambdaContext() }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestHandler.class, args); + + assertThat(LambdaHandlerProcessor.placedOnRequestHandler(pjpMock)).isTrue(); + } + + @Test + void placedOnStreamHandler_shouldRecognizeRequestStreamHandler() { + Object[] args = { mock(InputStream.class), mock(OutputStream.class), new TestLambdaContext() }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); + + assertThat(LambdaHandlerProcessor.placedOnStreamHandler(pjpMock)).isTrue(); + } + + @Test + void placedOnRequestHandler_shouldInvalidateOnWrongNoOfArgs() { + Object[] args = { new Object() }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestHandler.class, args); + + boolean isPlacedOnRequestHandler = LambdaHandlerProcessor.placedOnRequestHandler(pjpMock); + + assertThat(isPlacedOnRequestHandler).isFalse(); + } + + @Test + void placedOnRequestHandler_shouldInvalidateOnWrongTypeOfArgs() { + Object[] args = { new Object(), new Object() }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestHandler.class, args); + + boolean isPlacedOnRequestHandler = LambdaHandlerProcessor.placedOnRequestHandler(pjpMock); + + assertThat(isPlacedOnRequestHandler).isFalse(); + } + + @Test + void placedOnStreamHandler_shouldInvalidateOnWrongNoOfArgs() { + Object[] args = { new Object() }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); + + boolean isPlacedOnStreamHandler = LambdaHandlerProcessor.placedOnStreamHandler(pjpMock); + + assertThat(isPlacedOnStreamHandler).isFalse(); + } + + @Test + void placedOnStreamHandler_shouldInvalidateOnWrongTypeOfArgs() { + Object[] args = { new Object(), new Object(), new Object() }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); + + boolean isPlacedOnStreamHandler = LambdaHandlerProcessor.placedOnStreamHandler(pjpMock); + + assertThat(isPlacedOnStreamHandler).isFalse(); + } + + @Test + void placedOnStreamHandler_shouldInvalidateOnTypeOfArgs_invalidOutputStreamArg() { + Object[] args = { mock(InputStream.class), new Object(), new TestLambdaContext() }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); + + boolean isPlacedOnStreamHandler = LambdaHandlerProcessor.placedOnStreamHandler(pjpMock); + + assertThat(isPlacedOnStreamHandler).isFalse(); + } + + @Test + void placedOnStreamHandler_shouldInvalidateOnTypeOfArgs_invalidContextArg() { + Object[] args = { mock(InputStream.class), mock(OutputStream.class), new Object() }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); + + boolean isPlacedOnStreamHandler = LambdaHandlerProcessor.placedOnStreamHandler(pjpMock); + + assertThat(isPlacedOnStreamHandler).isFalse(); + } + + @Test + @SetEnvironmentVariable(key = LambdaConstants.X_AMZN_TRACE_ID, value = "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\"") + void getXrayTraceId_present() { + String traceID = "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""; + + Optional<String> xRayTraceId = LambdaHandlerProcessor.getXrayTraceId(); + + assertThat(xRayTraceId).isPresent(); + assertThat(traceID.split(";")[0].replace(LambdaConstants.ROOT_EQUALS, "")).isEqualTo(xRayTraceId.get()); + } + + @Test + @ClearEnvironmentVariable(key = LambdaConstants.X_AMZN_TRACE_ID) + void getXrayTraceId_notPresent() { + + boolean isXRayTraceIdPresent = LambdaHandlerProcessor.getXrayTraceId().isPresent(); + + assertThat(isXRayTraceIdPresent).isFalse(); + } + + @Test + @ClearEnvironmentVariable(key = LambdaConstants.X_AMZN_TRACE_ID) + @ClearSystemProperty(key = LambdaConstants.XRAY_TRACE_HEADER) + void getXrayTraceId_fromSdkInternalThreadLocal() { + // Verify no trace ID initially + assertThat(LambdaHandlerProcessor.getXrayTraceId()).isEmpty(); + + // Set trace ID in SdkInternalThreadLocal + String expectedTraceId = "1-5759e988-bd862e3fe1be46a994272793"; + SdkInternalThreadLocal.put(LambdaConstants.AWS_LAMBDA_X_TRACE_ID, + "Root=" + expectedTraceId + ";Parent=53995c3f42cd8ad8;Sampled=1"); + + // Verify trace ID is now present + Optional<String> traceId = LambdaHandlerProcessor.getXrayTraceId(); + assertThat(traceId).isPresent(); + assertThat(traceId.get()).isEqualTo(expectedTraceId); + } + + @Test + void extractContext_fromRequestHandler() { + Object[] args = { new Object(), new TestLambdaContext() }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestHandler.class, args); + + Context context = LambdaHandlerProcessor.extractContext(pjpMock); + + assertThat(context).isNotNull(); + } + + @Test + void extractContext_fromStreamRequestHandler() { + Object[] args = { mock(InputStream.class), mock(OutputStream.class), new TestLambdaContext() }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); + + Context context = LambdaHandlerProcessor.extractContext(pjpMock); + + assertNotNull(context); + } + + @Test + void extractContext_notKnownHandler() { + Object[] args = { new Object() }; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(Object.class, args); + + Context context = LambdaHandlerProcessor.extractContext(pjpMock); + + assertThat(context).isNull(); + } + + @Test + @SetEnvironmentVariable(key = LambdaConstants.AWS_LAMBDA_INITIALIZATION_TYPE, value = LambdaConstants.ON_DEMAND_INVOCATION_TYPE) + void isColdStart() { + boolean isColdStart = LambdaHandlerProcessor.isColdStart(); + + assertThat(isColdStart).isTrue(); + } + + @Test + void isColdStart_coldStartDone() { + LambdaHandlerProcessor.coldStartDone(); + + boolean isColdStart = LambdaHandlerProcessor.isColdStart(); + + assertThat(isColdStart).isFalse(); + } + + @Test + @SetEnvironmentVariable(key = LambdaConstants.AWS_SAM_LOCAL, value = "true") + void isSamLocal() { + + boolean isSamLocal = LambdaHandlerProcessor.isSamLocal(); + + assertThat(isSamLocal).isTrue(); + } + + @Test + @SetEnvironmentVariable(key = LambdaConstants.POWERTOOLS_SERVICE_NAME, value = "MyService") + void serviceName() { + String expectedServiceName = "MyService"; + String actualServiceName = LambdaHandlerProcessor.serviceName(); + + assertThat(actualServiceName).isEqualTo(expectedServiceName); + } + + @Test + @ClearEnvironmentVariable(key = LambdaConstants.POWERTOOLS_SERVICE_NAME) + void serviceName_Undefined() { + LambdaHandlerProcessor.resetServiceName(); + assertThat(LambdaHandlerProcessor.serviceName()).isEqualTo(LambdaConstants.SERVICE_UNDEFINED); + } + + private ProceedingJoinPoint mockRequestHandlerPjp(Class<?> handlerClass, Object[] handlerArgs) { + ProceedingJoinPoint pjp = mock(ProceedingJoinPoint.class); + Signature signature = mock(Signature.class); + + when(signature.getDeclaringType()).thenReturn(handlerClass); + when(signature.getName()).thenReturn("handleRequest"); + when(pjp.getSignature()).thenReturn(signature); + when(pjp.getArgs()).thenReturn(handlerArgs); + + return pjp; + } +} diff --git a/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/UserAgentConfiguratorTest.java b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/UserAgentConfiguratorTest.java new file mode 100644 index 000000000..33050d8b4 --- /dev/null +++ b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/UserAgentConfiguratorTest.java @@ -0,0 +1,230 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.common.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.lambda.powertools.common.internal.UserAgentConfigurator.AWS_EXECUTION_ENV; +import static software.amazon.lambda.powertools.common.internal.UserAgentConfigurator.VERSION_KEY; +import static software.amazon.lambda.powertools.common.internal.UserAgentConfigurator.VERSION_PROPERTIES_FILENAME; +import static software.amazon.lambda.powertools.common.internal.UserAgentConfigurator.getVersionFromProperties; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; + +class UserAgentConfiguratorTest { + + private static final String SEM_VER_PATTERN = "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"; + private static final String VERSION = UserAgentConfigurator.getProjectVersion(); + + @Test + void testGetVersion() { + + assertThat(VERSION) + .isNotNull() + .isNotEmpty(); + assertThat(Pattern.matches(SEM_VER_PATTERN, VERSION)).isTrue(); + } + + @Test + void testGetVersionFromProperties_WrongKey() { + String version = getVersionFromProperties(VERSION_PROPERTIES_FILENAME, "some invalid key"); + + assertThat(version) + .isNotNull() + .isEqualTo("NA"); + } + + @Test + void testGetVersionFromProperties_FileNotExist() { + String version = getVersionFromProperties("some file", VERSION_KEY); + + assertThat(version) + .isNotNull() + .isEqualTo("NA"); + } + + @Test + void testGetVersionFromProperties_InvalidFile() throws IOException { + Path tempFile = Files.createTempFile("unreadable", ".properties"); + File f = tempFile.toFile(); + f.setReadable(false); + + String version = getVersionFromProperties(f.getName(), VERSION_KEY); + + assertThat(version).isEqualTo("NA"); + + // Cleanup + f.setReadable(true); + Files.deleteIfExists(tempFile); + } + + @Test + void testGetVersionFromProperties_EmptyVersion() { + String version = getVersionFromProperties("test.properties", VERSION_KEY); + + assertThat(version).isEqualTo("NA"); + } + + @Test + void testGetUserAgent() { + String userAgent = UserAgentConfigurator.getUserAgent("test-feature"); + + assertThat(userAgent) + .isNotNull() + .isEqualTo("PT/TEST-FEATURE/" + VERSION + " PTENV/NA"); + + } + + @Test + void testGetUserAgent_NoFeature() { + String userAgent = UserAgentConfigurator.getUserAgent(""); + + assertThat(userAgent) + .isNotNull() + .isEqualTo("PT/NO-OP/" + VERSION + " PTENV/NA"); + } + + @Test + void testGetUserAgent_NullFeature() { + String userAgent = UserAgentConfigurator.getUserAgent(null); + + assertThat(userAgent) + .isNotNull() + .isEqualTo("PT/NO-OP/" + VERSION + " PTENV/NA"); + } + + @Test + @SetEnvironmentVariable(key = AWS_EXECUTION_ENV, value = "AWS_Lambda_java8") + void testGetUserAgent_SetAWSExecutionEnv() { + String userAgent = UserAgentConfigurator.getUserAgent("test-feature"); + + assertThat(userAgent) + .isNotNull() + .isEqualTo("PT/TEST-FEATURE/" + VERSION + " PTENV/AWS_Lambda_java8"); + } + + @Test + void testConfigureUserAgent() { + System.clearProperty("sdk.ua.appId"); + UserAgentConfigurator.configureUserAgent("test-feature"); + + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("PT/TEST-FEATURE/" + VERSION + " PTENV/NA"); + } + + @Test + void testConfigureUserAgent_WithExistingUserValue() { + System.setProperty("sdk.ua.appId", "UserValueABC123"); + UserAgentConfigurator.configureUserAgent("test-feature"); + + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("UserValueABC123/PT/TEST-FEATURE/" + VERSION + " PTENV/NA"); + } + + @Test + void testConfigureUserAgent_ReplacePowertoolsUserAgent() { + System.setProperty("sdk.ua.appId", "PT/BATCH/" + VERSION + " PTENV/NA"); + UserAgentConfigurator.configureUserAgent("logging-log4j"); + + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("PT/LOGGING-LOG4J/" + VERSION + " PTENV/NA"); + } + + @Test + void testConfigureUserAgent_PreserveUserValueAndReplacePowertools() { + System.setProperty("sdk.ua.appId", "UserValue/PT/BATCH/" + VERSION + " PTENV/NA"); + UserAgentConfigurator.configureUserAgent("tracing"); + + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("UserValue/PT/TRACING/" + VERSION + " PTENV/NA"); + } + + @Test + void testConfigureUserAgent_ExceedsLimit() { + System.setProperty("sdk.ua.appId", "VeryLongUserValueThatExceedsTheLimitWhenCombined"); + UserAgentConfigurator.configureUserAgent("test-feature"); + + // Should not update if it would exceed 50 characters + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("VeryLongUserValueThatExceedsTheLimitWhenCombined"); + } + + @Test + void testExtractUserValue_NoUserValue() { + String result = UserAgentConfigurator.extractUserValue("PT/BATCH/" + VERSION + " PTENV/NA"); + assertThat(result).isEmpty(); + } + + @Test + void testExtractUserValue_WithUserValue() { + String result = UserAgentConfigurator.extractUserValue("UserValue/PT/BATCH/" + VERSION + " PTENV/NA"); + assertThat(result).isEqualTo("UserValue"); + } + + @Test + void testExtractUserValue_EmptyString() { + String result = UserAgentConfigurator.extractUserValue(""); + assertThat(result).isEmpty(); + } + + @Test + void testExtractUserValue_NullString() { + String result = UserAgentConfigurator.extractUserValue(null); + assertThat(result).isEmpty(); + } + + @Test + void testExtractUserValue_OnlyUserValue() { + String result = UserAgentConfigurator.extractUserValue("MyCustomValue"); + assertThat(result).isEqualTo("MyCustomValue"); + } + + @Test + void testConfigureUserAgent_WithEmptyExistingValue() { + System.setProperty("sdk.ua.appId", ""); + UserAgentConfigurator.configureUserAgent("test-feature"); + + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("PT/TEST-FEATURE/" + VERSION + " PTENV/NA"); + } + + @Test + @SetEnvironmentVariable(key = AWS_EXECUTION_ENV, value = "AWS_Lambda_java11") + void testConfigureUserAgent_MultipleUtilities() { + System.clearProperty("sdk.ua.appId"); + + // First utility + UserAgentConfigurator.configureUserAgent("batch"); + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("PT/BATCH/" + VERSION + " PTENV/AWS_Lambda_java11"); + + // Second utility - should replace, not append + UserAgentConfigurator.configureUserAgent("logging-log4j"); + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("PT/LOGGING-LOG4J/" + VERSION + " PTENV/AWS_Lambda_java11"); + + // Third utility - should replace again + UserAgentConfigurator.configureUserAgent("tracing"); + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("PT/TRACING/" + VERSION + " PTENV/AWS_Lambda_java11"); + } + +} diff --git a/powertools-common/src/test/java/software/amazon/lambda/powertools/common/stubs/TestLambdaContext.java b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/stubs/TestLambdaContext.java new file mode 100644 index 000000000..6b66b66b7 --- /dev/null +++ b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/stubs/TestLambdaContext.java @@ -0,0 +1,77 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.common.stubs; + +import com.amazonaws.services.lambda.runtime.ClientContext; +import com.amazonaws.services.lambda.runtime.CognitoIdentity; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.LambdaLogger; + +public class TestLambdaContext implements Context { + @Override + public String getAwsRequestId() { + return "test-request-id"; + } + + @Override + public String getLogGroupName() { + return "test-log-group"; + } + + @Override + public String getLogStreamName() { + return "test-log-stream"; + } + + @Override + public String getFunctionName() { + return "test-function"; + } + + @Override + public String getFunctionVersion() { + return "1"; + } + + @Override + public String getInvokedFunctionArn() { + return "arn:aws:lambda:us-east-1:123456789012:function:test"; + } + + @Override + public CognitoIdentity getIdentity() { + return null; + } + + @Override + public ClientContext getClientContext() { + return null; + } + + @Override + public int getRemainingTimeInMillis() { + return 30000; + } + + @Override + public int getMemoryLimitInMB() { + return 128; + } + + @Override + public LambdaLogger getLogger() { + return null; + } +} diff --git a/powertools-common/src/test/resources/classesloaded.txt b/powertools-common/src/test/resources/classesloaded.txt new file mode 100644 index 000000000..498ffdf69 --- /dev/null +++ b/powertools-common/src/test/resources/classesloaded.txt @@ -0,0 +1,2 @@ +software.amazon.lambda.powertools.common.internal.NonExistingClass +software.amazon.lambda.powertools.common.internal.ClassPreLoaderTest$DummyClass \ No newline at end of file diff --git a/powertools-common/src/test/resources/test.properties b/powertools-common/src/test/resources/test.properties new file mode 100644 index 000000000..65756b8dd --- /dev/null +++ b/powertools-common/src/test/resources/test.properties @@ -0,0 +1 @@ +powertools.version= \ No newline at end of file diff --git a/powertools-core/pom.xml b/powertools-core/pom.xml deleted file mode 100644 index addb29c51..000000000 --- a/powertools-core/pom.xml +++ /dev/null @@ -1,87 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - - <artifactId>powertools-core</artifactId> - <packaging>jar</packaging> - - <parent> - <artifactId>powertools-parent</artifactId> - <groupId>software.amazon.lambda</groupId> - <version>1.10.2</version> - </parent> - - <name>AWS Lambda Powertools Java library Core</name> - <description> - A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. - </description> - <url>https://aws.amazon.com/lambda/</url> - <issueManagement> - <system>GitHub Issues</system> - <url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url> - </issueManagement> - - <scm> - <url>https://github.com/awslabs/aws-lambda-powertools-java.git</url> - </scm> - <developers> - <developer> - <name>AWS Lambda Powertools team</name> - <organization>Amazon Web Services</organization> - <organizationUrl>https://aws.amazon.com/</organizationUrl> - </developer> - </developers> - - <distributionManagement> - <snapshotRepository> - <id>ossrh</id> - <url>https://aws.oss.sonatype.org/content/repositories/snapshots</url> - </snapshotRepository> - </distributionManagement> - - <dependencies> - <dependency> - <groupId>com.amazonaws</groupId> - <artifactId>aws-lambda-java-core</artifactId> - </dependency> - <dependency> - <groupId>org.aspectj</groupId> - <artifactId>aspectjrt</artifactId> - </dependency> - - <!-- Test dependencies --> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-api</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-engine</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.aspectj</groupId> - <artifactId>aspectjweaver</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.assertj</groupId> - <artifactId>assertj-core</artifactId> - <scope>test</scope> - </dependency> - </dependencies> - -</project> \ No newline at end of file diff --git a/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessor.java b/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessor.java deleted file mode 100644 index ffe889db9..000000000 --- a/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessor.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. - * 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 - * http://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. - * - */ -package software.amazon.lambda.powertools.core.internal; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import org.aspectj.lang.ProceedingJoinPoint; - -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Optional; - -import static java.util.Optional.empty; -import static java.util.Optional.of; -import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv; - -public final class LambdaHandlerProcessor { - // SERVICE_NAME cannot be final for testing purposes - private static String SERVICE_NAME = null != System.getenv("POWERTOOLS_SERVICE_NAME") - ? System.getenv("POWERTOOLS_SERVICE_NAME") : "service_undefined"; - private static Boolean IS_COLD_START = null; - - private LambdaHandlerProcessor() { - // Hide default constructor - } - - public static boolean isHandlerMethod(final ProceedingJoinPoint pjp) { - return "handleRequest".equals(pjp.getSignature().getName()) || - // https://docs.aws.amazon.com/codeguru/latest/profiler-ug/lambda-custom.html - "requestHandler".equals(pjp.getSignature().getName()); - } - - public static boolean placedOnRequestHandler(final ProceedingJoinPoint pjp) { - return RequestHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType()) - && pjp.getArgs().length == 2 - && pjp.getArgs()[1] instanceof Context; - } - - public static boolean placedOnStreamHandler(final ProceedingJoinPoint pjp) { - return RequestStreamHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType()) - && pjp.getArgs().length == 3 - && pjp.getArgs()[0] instanceof InputStream - && pjp.getArgs()[1] instanceof OutputStream - && pjp.getArgs()[2] instanceof Context; - } - - public static Context extractContext(final ProceedingJoinPoint pjp) { - - if (isHandlerMethod(pjp)) { - if (placedOnRequestHandler(pjp)) { - return (Context) pjp.getArgs()[1]; - } - - if (placedOnStreamHandler(pjp)) { - return (Context) pjp.getArgs()[2]; - } - } - - return null; - } - - public static String serviceName() { - return SERVICE_NAME; - } - - public static boolean isColdStart() { - return IS_COLD_START == null; - } - - public static void coldStartDone() { - IS_COLD_START = false; - } - - public static boolean isSamLocal() { - return "true".equals(System.getenv("AWS_SAM_LOCAL")); - } - - public static Optional<String> getXrayTraceId() { - final String X_AMZN_TRACE_ID = getenv("_X_AMZN_TRACE_ID"); - if(X_AMZN_TRACE_ID != null) { - return of(X_AMZN_TRACE_ID.split(";")[0].replace("Root=", "")); - } - return empty(); - } -} diff --git a/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/SystemWrapper.java b/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/SystemWrapper.java deleted file mode 100644 index aef64378f..000000000 --- a/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/SystemWrapper.java +++ /dev/null @@ -1,10 +0,0 @@ -package software.amazon.lambda.powertools.core.internal; - -public class SystemWrapper { - private SystemWrapper() { - } - - public static String getenv(String name) { - return System.getenv(name); - } -} diff --git a/powertools-core/src/test/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessorTest.java b/powertools-core/src/test/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessorTest.java deleted file mode 100644 index 18adae32e..000000000 --- a/powertools-core/src/test/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessorTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package software.amazon.lambda.powertools.core.internal; - -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.Signature; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class LambdaHandlerProcessorTest { - - @Test - void shouldTreatProfilerHandlerMethodAsValid() { - ProceedingJoinPoint pjpMock = mock(ProceedingJoinPoint.class); - Signature signature = mock(Signature.class); - when(signature.getName()).thenReturn("requestHandler"); - when(pjpMock.getSignature()).thenReturn(signature); - - assertThat(LambdaHandlerProcessor.isHandlerMethod(pjpMock)) - .isTrue(); - } - - @Test - void shouldTreatDefaultHandlerMethodAsValid() { - ProceedingJoinPoint pjpMock = mock(ProceedingJoinPoint.class); - Signature signature = mock(Signature.class); - when(signature.getName()).thenReturn("handleRequest"); - when(pjpMock.getSignature()).thenReturn(signature); - - assertThat(LambdaHandlerProcessor.isHandlerMethod(pjpMock)) - .isTrue(); - } - - @Test - void shouldNotTreatOtherMethodNamesAsValidHandlerMethod() { - ProceedingJoinPoint pjpMock = mock(ProceedingJoinPoint.class); - Signature signature = mock(Signature.class); - when(signature.getName()).thenReturn("handleRequestInvalid"); - when(pjpMock.getSignature()).thenReturn(signature); - - assertThat(LambdaHandlerProcessor.isHandlerMethod(pjpMock)) - .isFalse(); - } -} \ No newline at end of file diff --git a/powertools-e2e-tests/README.md b/powertools-e2e-tests/README.md new file mode 100644 index 000000000..61799e6f7 --- /dev/null +++ b/powertools-e2e-tests/README.md @@ -0,0 +1,16 @@ +## End-to-end tests +This module is internal and meant to be used for end-to-end (E2E) testing of Powertools for AWS Lambda (Java). + +__Prerequisites__: +- An AWS account is needed as well as a local environment able to reach this account +([credentials](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials.html)). +- [Java 11+](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html) +- [Docker](https://docs.docker.com/engine/install/) + +To execute the E2E tests, use the following command: `export JAVA_VERSION=11 && mvn clean verify -Pe2e` + +### Under the hood +This module leverages the following components: +- AWS CDK to define the infrastructure and synthesize a CloudFormation template and the assets (lambda function packages) +- The AWS S3 SDK to push the assets on S3 +- The AWS CloudFormation SDK to deploy the template \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/batch/pom.xml b/powertools-e2e-tests/handlers/batch/pom.xml new file mode 100644 index 000000000..3e89aadd2 --- /dev/null +++ b/powertools-e2e-tests/handlers/batch/pom.xml @@ -0,0 +1,72 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-batch</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Batch</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-batch</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-serialization</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>dynamodb</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-e2e-tests/handlers/batch/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/batch/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..36142d3f5 --- /dev/null +++ b/powertools-e2e-tests/handlers/batch/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,155 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.DynamodbEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; +import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; +import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers; + +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; +import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; +import software.amazon.lambda.powertools.e2e.model.Product; + +public class Function implements RequestHandler<InputStream, Object> { + + private static final Logger LOGGER = LoggerFactory.getLogger(Function.class); + + private final BatchMessageHandler<SQSEvent, SQSBatchResponse> sqsHandler; + private final BatchMessageHandler<KinesisEvent, StreamsEventResponse> kinesisHandler; + private final BatchMessageHandler<DynamodbEvent, StreamsEventResponse> ddbHandler; + private final String ddbOutputTable; + + public Function() { + sqsHandler = new BatchMessageHandlerBuilder() + .withSqsBatchHandler() + .buildWithMessageHandler(this::processProductMessage, Product.class); + + kinesisHandler = new BatchMessageHandlerBuilder() + .withKinesisBatchHandler() + .buildWithMessageHandler(this::processProductMessage, Product.class); + + ddbHandler = new BatchMessageHandlerBuilder() + .withDynamoDbBatchHandler() + .buildWithRawMessageHandler(this::processDdbMessage); + + this.ddbOutputTable = System.getenv("TABLE_FOR_ASYNC_TESTS"); + } + + private void processProductMessage(Product p, Context c) { + LOGGER.info("Processing product " + p); + + DynamoDbClient ddbClient = DynamoDbClient.builder() + .build(); + Map<String, AttributeValue> results = new HashMap<>(); + results.put("functionName", AttributeValue.builder() + .s(c.getFunctionName()) + .build()); + results.put("id", AttributeValue.builder() + .s(Long.toString(p.getId())) + .build()); + results.put("name", AttributeValue.builder() + .s(p.getName()) + .build()); + results.put("price", AttributeValue.builder() + .n(Double.toString(p.getPrice())) + .build()); + ddbClient.putItem(PutItemRequest.builder() + .tableName(ddbOutputTable) + .item(results) + .build()); + } + + private void processDdbMessage(DynamodbEvent.DynamodbStreamRecord dynamodbStreamRecord, Context context) { + LOGGER.info("Processing DynamoDB Stream Record" + dynamodbStreamRecord); + + DynamoDbClient ddbClient = DynamoDbClient.builder() + .build(); + + String id = dynamodbStreamRecord.getDynamodb().getKeys().get("id").getS(); + LOGGER.info("Incoming ID is " + id); + + Map<String, AttributeValue> results = new HashMap<>(); + results.put("functionName", AttributeValue.builder() + .s(context.getFunctionName()) + .build()); + results.put("id", AttributeValue.builder() + .s(id) + .build()); + + ddbClient.putItem(PutItemRequest.builder() + .tableName(ddbOutputTable) + .item(results) + .build()); + } + + public Object createResult(String input, Context context) { + + LOGGER.info(input); + + PojoSerializer<SQSEvent> serializer = LambdaEventSerializers.serializerFor(SQSEvent.class, + this.getClass().getClassLoader()); + SQSEvent event = serializer.fromJson(input); + if ("aws:sqs".equals(event.getRecords().get(0).getEventSource())) { + LOGGER.info("Running for SQS"); + return sqsHandler.processBatch(event, context); + } + + PojoSerializer<KinesisEvent> kinesisSerializer = LambdaEventSerializers.serializerFor(KinesisEvent.class, + this.getClass().getClassLoader()); + KinesisEvent kinesisEvent = kinesisSerializer.fromJson(input); + if ("aws:kinesis".equals(kinesisEvent.getRecords().get(0).getEventSource())) { + LOGGER.info("Running for Kinesis"); + return kinesisHandler.processBatch(kinesisEvent, context); + } + + // Well, let's try dynamo + PojoSerializer<DynamodbEvent> ddbSerializer = LambdaEventSerializers.serializerFor(DynamodbEvent.class, + this.getClass().getClassLoader()); + LOGGER.info("Running for DynamoDB"); + DynamodbEvent ddbEvent = ddbSerializer.fromJson(input); + return ddbHandler.processBatch(ddbEvent, context); + } + + @Override + public Object handleRequest(InputStream inputStream, Context context) { + + String input = new BufferedReader( + new InputStreamReader(inputStream, StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + + return createResult(input, context); + } +} diff --git a/powertools-e2e-tests/handlers/batch/src/main/java/software/amazon/lambda/powertools/e2e/model/Product.java b/powertools-e2e-tests/handlers/batch/src/main/java/software/amazon/lambda/powertools/e2e/model/Product.java new file mode 100644 index 000000000..74bb5ff9f --- /dev/null +++ b/powertools-e2e-tests/handlers/batch/src/main/java/software/amazon/lambda/powertools/e2e/model/Product.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e.model; + +public class Product { + private long id; + + private String name; + + private double price; + + public Product() { + } + + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } +} diff --git a/powertools-e2e-tests/handlers/batch/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/batch/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/batch/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="JsonAppender"/> + </Root> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency-functional/pom.xml b/powertools-e2e-tests/handlers/idempotency-functional/pom.xml new file mode 100644 index 000000000..b5669b21f --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/pom.xml @@ -0,0 +1,60 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-idempotency-functional</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Idempotency Functional</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-dynamodb</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-e2e-tests/handlers/idempotency-functional/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/idempotency-functional/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..fec7459c1 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.TimeZone; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; +import software.amazon.lambda.powertools.idempotency.persistence.dynamodb.DynamoDBPersistenceStore; + +public class Function implements RequestHandler<Input, String> { + + public Function() { + this(DynamoDbClient + .builder() + .httpClient(UrlConnectionHttpClient.builder().build()) + .region(Region.of(System.getenv("AWS_REGION"))) + .build()); + } + + public Function(DynamoDbClient client) { + Idempotency.config().withConfig( + IdempotencyConfig.builder() + .withExpiration(Duration.of(10, ChronoUnit.SECONDS)) + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withDynamoDbClient(client) + .withTableName(System.getenv("IDEMPOTENCY_TABLE")) + .build()) + .configure(); + } + + public String handleRequest(Input input, Context context) { + Idempotency.registerLambdaContext(context); + + return Idempotency.makeIdempotent(this::processRequest, input, String.class); + } + + private String processRequest(Input input) { + DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME.withZone(TimeZone.getTimeZone("UTC").toZoneId()); + return dtf.format(Instant.now()); + } +} diff --git a/powertools-e2e-tests/handlers/idempotency-functional/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/idempotency-functional/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..0d14b749e --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +public class Input { + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..467af67a0 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,62 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields": [{ "name": "logger" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogLevel", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogFormat", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "java.lang.Void", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "java.util.Collections$UnmodifiableMap", + "fields": [{ "name": "m" }] + }, + { + "name": "jdk.internal.module.IllegalAccessLogger", + "fields": [{ "name": "logger" }] + }, + { + "name": "sun.misc.Unsafe", + "fields": [{ "name": "theUnsafe" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json new file mode 100644 index 000000000..9ddd235e2 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "software.amazon.lambda.powertools.e2e.Function", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "software.amazon.lambda.powertools.e2e.Input", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-functional/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="JsonAppender"/> + </Root> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency-generics/pom.xml b/powertools-e2e-tests/handlers/idempotency-generics/pom.xml new file mode 100644 index 000000000..21a658e6c --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/pom.xml @@ -0,0 +1,60 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-idempotency-generics</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Idempotency Generics</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-dynamodb</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-e2e-tests/handlers/idempotency-generics/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/idempotency-generics/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..09e39d1eb --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.fasterxml.jackson.core.type.TypeReference; + +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; +import software.amazon.lambda.powertools.idempotency.persistence.dynamodb.DynamoDBPersistenceStore; + +public class Function implements RequestHandler<Input, String> { + + public Function() { + this(DynamoDbClient + .builder() + .httpClient(UrlConnectionHttpClient.builder().build()) + .region(Region.of(System.getenv("AWS_REGION"))) + .build()); + } + + public Function(DynamoDbClient client) { + Idempotency.config().withConfig( + IdempotencyConfig.builder() + .withExpiration(Duration.of(10, ChronoUnit.SECONDS)) + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withDynamoDbClient(client) + .withTableName(System.getenv("IDEMPOTENCY_TABLE")) + .build()) + .configure(); + } + + public String handleRequest(Input input, Context context) { + Idempotency.registerLambdaContext(context); + + // This is just to test the generic type support using TypeReference. + // We return the same String to run the same assertions as other idempotency E2E handlers. + Map<String, String> result = Idempotency.makeIdempotent( + this::processRequest, + input, + new TypeReference<Map<String, String>>() {}); + + return result.get("timestamp"); + } + + private Map<String, String> processRequest(Input input) { + DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME.withZone(TimeZone.getTimeZone("UTC").toZoneId()); + Map<String, String> result = new HashMap<>(); + result.put("timestamp", dtf.format(Instant.now())); + return result; + } +} diff --git a/powertools-e2e-tests/handlers/idempotency-generics/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/idempotency-generics/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..0d14b749e --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +public class Input { + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..467af67a0 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,62 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields": [{ "name": "logger" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogLevel", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogFormat", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "java.lang.Void", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "java.util.Collections$UnmodifiableMap", + "fields": [{ "name": "m" }] + }, + { + "name": "jdk.internal.module.IllegalAccessLogger", + "fields": [{ "name": "logger" }] + }, + { + "name": "sun.misc.Unsafe", + "fields": [{ "name": "theUnsafe" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json new file mode 100644 index 000000000..9ddd235e2 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "software.amazon.lambda.powertools.e2e.Function", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "software.amazon.lambda.powertools.e2e.Input", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency-generics/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="JsonAppender"/> + </Root> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency/pom.xml b/powertools-e2e-tests/handlers/idempotency/pom.xml new file mode 100644 index 000000000..921599bdb --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/pom.xml @@ -0,0 +1,90 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-idempotency</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Idempotency</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-dynamodb</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-core</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..038704931 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.TimeZone; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.persistence.dynamodb.DynamoDBPersistenceStore; +import software.amazon.lambda.powertools.logging.Logging; + + +public class Function implements RequestHandler<Input, String> { + + public Function() { + this(DynamoDbClient + .builder() + .httpClient(UrlConnectionHttpClient.builder().build()) + .region(Region.of(System.getenv("AWS_REGION"))) + .build()); + } + + public Function(DynamoDbClient client) { + Idempotency.config().withConfig( + IdempotencyConfig.builder() + .withExpiration(Duration.of(10, ChronoUnit.SECONDS)) + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withDynamoDbClient(client) + .withTableName(System.getenv("IDEMPOTENCY_TABLE")) + .build() + ).configure(); + } + + @Logging(logEvent = true) + @Idempotent + public String handleRequest(Input input, Context context) { + DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME.withZone(TimeZone.getTimeZone("UTC").toZoneId()); + return dtf.format(Instant.now()); + } +} diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..e0e4c27c9 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +public class Input { + private String message; + + public Input(String message) { + this.message = message; + } + + public Input() { + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..467af67a0 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,62 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields": [{ "name": "logger" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogLevel", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogFormat", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "java.lang.Void", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "java.util.Collections$UnmodifiableMap", + "fields": [{ "name": "m" }] + }, + { + "name": "jdk.internal.module.IllegalAccessLogger", + "fields": [{ "name": "logger" }] + }, + { + "name": "sun.misc.Unsafe", + "fields": [{ "name": "theUnsafe" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json new file mode 100644 index 000000000..9ddd235e2 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "software.amazon.lambda.powertools.e2e.Function", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "software.amazon.lambda.powertools.e2e.Input", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/idempotency/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="JsonAppender"/> + </Root> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/largemessage-functional/pom.xml b/powertools-e2e-tests/handlers/largemessage-functional/pom.xml new file mode 100644 index 000000000..ddfe39a5e --- /dev/null +++ b/powertools-e2e-tests/handlers/largemessage-functional/pom.xml @@ -0,0 +1,46 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-largemessage-functional</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Large message functional</name> + + <dependencies> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>dynamodb</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-large-messages</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-e2e-tests/handlers/largemessage-functional/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/largemessage-functional/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..05a336500 --- /dev/null +++ b/powertools-e2e-tests/handlers/largemessage-functional/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,85 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import static software.amazon.lambda.powertools.logging.PowertoolsLogging.withLogging; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; + +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.utils.BinaryUtils; +import software.amazon.awssdk.utils.Md5Utils; +import software.amazon.lambda.powertools.largemessages.LargeMessages; + +public class Function implements RequestHandler<SQSEvent, SQSBatchResponse> { + + private static final String TABLE_FOR_ASYNC_TESTS = System.getenv("TABLE_FOR_ASYNC_TESTS"); + private DynamoDbClient client; + + public Function() { + if (client == null) { + client = DynamoDbClient.builder() + .httpClient(UrlConnectionHttpClient.builder().build()) + .region(Region.of(System.getenv("AWS_REGION"))) + .build(); + } + } + + public SQSBatchResponse handleRequest(SQSEvent event, Context context) { + return withLogging(context, () -> { + for (SQSMessage message : event.getRecords()) { + LargeMessages.processLargeMessage(message, msg -> processRawMessage(msg, context)); + } + return SQSBatchResponse.builder().build(); + }); + } + + private Void processRawMessage(SQSMessage sqsMessage, Context context) { + String bodyMD5 = md5(sqsMessage.getBody()); + if (!sqsMessage.getMd5OfBody().equals(bodyMD5)) { + throw new SecurityException( + String.format("message digest does not match, expected %s, got %s", sqsMessage.getMd5OfBody(), + bodyMD5)); + } + + Map<String, AttributeValue> item = new HashMap<>(); + item.put("functionName", AttributeValue.builder().s(context.getFunctionName()).build()); + item.put("id", AttributeValue.builder().s(sqsMessage.getMessageId()).build()); + item.put("bodyMD5", AttributeValue.builder().s(bodyMD5).build()); + item.put("bodySize", + AttributeValue.builder().n(String.valueOf(sqsMessage.getBody().getBytes(StandardCharsets.UTF_8).length)) + .build()); + + client.putItem(PutItemRequest.builder().tableName(TABLE_FOR_ASYNC_TESTS).item(item).build()); + + return null; + } + + private String md5(String message) { + return BinaryUtils.toHex(Md5Utils.computeMD5Hash(message.getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/powertools-e2e-tests/handlers/largemessage-functional/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/largemessage-functional/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/largemessage-functional/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="JsonAppender"/> + </Root> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/largemessage/pom.xml b/powertools-e2e-tests/handlers/largemessage/pom.xml new file mode 100644 index 000000000..bee253988 --- /dev/null +++ b/powertools-e2e-tests/handlers/largemessage/pom.xml @@ -0,0 +1,72 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-largemessage</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Large message</name> + + <dependencies> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>dynamodb</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-large-messages</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-large-messages</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-e2e-tests/handlers/largemessage/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/largemessage/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..36cb9fcd2 --- /dev/null +++ b/powertools-e2e-tests/handlers/largemessage/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,80 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.utils.BinaryUtils; +import software.amazon.awssdk.utils.Md5Utils; +import software.amazon.lambda.powertools.largemessages.LargeMessage; +import software.amazon.lambda.powertools.logging.Logging; + +public class Function implements RequestHandler<SQSEvent, SQSBatchResponse> { + + private static final String TABLE_FOR_ASYNC_TESTS = System.getenv("TABLE_FOR_ASYNC_TESTS"); + private DynamoDbClient client; + + public Function() { + if (client == null) { + client = DynamoDbClient.builder() + .httpClient(UrlConnectionHttpClient.builder().build()) + .region(Region.of(System.getenv("AWS_REGION"))) + .build(); + } + } + + @Logging(logEvent = true) + public SQSBatchResponse handleRequest(SQSEvent event, Context context) { + for (SQSMessage message : event.getRecords()) { + processRawMessage(message, context); + } + return SQSBatchResponse.builder().build(); + } + + @LargeMessage + private void processRawMessage(SQSMessage sqsMessage, Context context) { + String bodyMD5 = md5(sqsMessage.getBody()); + if (!sqsMessage.getMd5OfBody().equals(bodyMD5)) { + throw new SecurityException( + String.format("message digest does not match, expected %s, got %s", sqsMessage.getMd5OfBody(), + bodyMD5)); + } + + Map<String, AttributeValue> item = new HashMap<>(); + item.put("functionName", AttributeValue.builder().s(context.getFunctionName()).build()); + item.put("id", AttributeValue.builder().s(sqsMessage.getMessageId()).build()); + item.put("bodyMD5", AttributeValue.builder().s(bodyMD5).build()); + item.put("bodySize", + AttributeValue.builder().n(String.valueOf(sqsMessage.getBody().getBytes(StandardCharsets.UTF_8).length)) + .build()); + + client.putItem(PutItemRequest.builder().tableName(TABLE_FOR_ASYNC_TESTS).item(item).build()); + } + + private String md5(String message) { + return BinaryUtils.toHex(Md5Utils.computeMD5Hash(message.getBytes(StandardCharsets.UTF_8))); + } +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/largemessage/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/largemessage/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/largemessage/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="JsonAppender"/> + </Root> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml b/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml new file mode 100644 index 000000000..5ef7e1963 --- /dev/null +++ b/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml @@ -0,0 +1,76 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-large-msg-idempotent</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Large message idempotent</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-dynamodb</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-large-messages</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-core</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-large-messages</artifactId> + </aspectLibrary> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-e2e-tests/handlers/largemessage_idempotent/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/largemessage_idempotent/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..6c9d4e8cf --- /dev/null +++ b/powertools-e2e-tests/handlers/largemessage_idempotent/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,109 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.utils.BinaryUtils; +import software.amazon.awssdk.utils.Md5Utils; +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; +import software.amazon.lambda.powertools.idempotency.IdempotencyKey; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.persistence.dynamodb.DynamoDBPersistenceStore; +import software.amazon.lambda.powertools.largemessages.LargeMessage; +import software.amazon.lambda.powertools.logging.Logging; + +public class Function implements RequestHandler<SQSEvent, SQSBatchResponse> { + + private static final String TABLE_FOR_ASYNC_TESTS = System.getenv("TABLE_FOR_ASYNC_TESTS"); + private final DynamoDbClient client; + + public Function() { + this(DynamoDbClient + .builder() + .httpClient(UrlConnectionHttpClient.builder().build()) + .region(Region.of(System.getenv("AWS_REGION"))) + .build()); + } + + public Function(DynamoDbClient client) { + this.client = client; + Idempotency.config().withConfig( + IdempotencyConfig.builder() + .withExpiration(Duration.of(22, ChronoUnit.SECONDS)) + .withEventKeyJMESPath("body") // get the body of the message + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withDynamoDbClient(client) + .withTableName(System.getenv("IDEMPOTENCY_TABLE")) + .build() + ).configure(); + } + + @Logging(logEvent = true) + public SQSBatchResponse handleRequest(SQSEvent event, Context context) { + for (SQSEvent.SQSMessage message : event.getRecords()) { + processRawMessage(message, context); + } + return SQSBatchResponse.builder().build(); + } + + @Idempotent + @LargeMessage(deleteS3Object = false) + private String processRawMessage(@IdempotencyKey SQSEvent.SQSMessage sqsMessage, Context context) { + String bodyMD5 = md5(sqsMessage.getBody()); + if (!sqsMessage.getMd5OfBody().equals(bodyMD5)) { + throw new SecurityException( + String.format("message digest does not match, expected %s, got %s", sqsMessage.getMd5OfBody(), + bodyMD5)); + } + + Instant now = Instant.now(); + Map<String, AttributeValue> item = new HashMap<>(); + item.put("functionName", AttributeValue.builder().s(context.getFunctionName()).build()); + item.put("id", AttributeValue.builder().s(sqsMessage.getMessageId()).build()); + item.put("bodyMD5", AttributeValue.builder().s(bodyMD5).build()); + item.put("now", AttributeValue.builder().n(String.valueOf(now.getEpochSecond())).build()); + item.put("bodySize", + AttributeValue.builder().n(String.valueOf(sqsMessage.getBody().getBytes(StandardCharsets.UTF_8).length)) + .build()); + + client.putItem(PutItemRequest.builder().tableName(TABLE_FOR_ASYNC_TESTS).item(item).build()); + + DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME.withZone(TimeZone.getTimeZone("UTC").toZoneId()); + return dtf.format(now); + } + + private String md5(String message) { + return BinaryUtils.toHex(Md5Utils.computeMD5Hash(message.getBytes(StandardCharsets.UTF_8))); + } +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/largemessage_idempotent/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/largemessage_idempotent/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/largemessage_idempotent/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="JsonAppender"/> + </Root> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-functional/pom.xml b/powertools-e2e-tests/handlers/logging-functional/pom.xml new file mode 100644 index 000000000..4ec6e5008 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/pom.xml @@ -0,0 +1,60 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-logging-functional</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Logging Functional</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-e2e-tests/handlers/logging-functional/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/logging-functional/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..78ab9ba4b --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.logging.PowertoolsLogging; + +public class Function implements RequestHandler<Input, String> { + private static final Logger LOG = LoggerFactory.getLogger(Function.class); + + public String handleRequest(Input input, Context context) { + return PowertoolsLogging.withLogging(context, () -> { + input.getKeys().forEach(MDC::put); + LOG.info(input.getMessage()); + + // Flush buffer manually since we buffer at INFO level to test log buffering + PowertoolsLogging.flushBuffer(); + + return "OK"; + }); + } +} diff --git a/powertools-e2e-tests/handlers/logging-functional/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/logging-functional/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..66fd49ddc --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import java.util.Map; + +public class Input { + private String message; + private Map<String, String> keys; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Map<String, String> getKeys() { + return keys; + } + + public void setKeys(Map<String, String> keys) { + this.keys = keys; + } +} diff --git a/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..467af67a0 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,62 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields": [{ "name": "logger" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogLevel", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogFormat", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "java.lang.Void", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "java.util.Collections$UnmodifiableMap", + "fields": [{ "name": "m" }] + }, + { + "name": "jdk.internal.module.IllegalAccessLogger", + "fields": [{ "name": "logger" }] + }, + { + "name": "sun.misc.Unsafe", + "fields": [{ "name": "theUnsafe" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json new file mode 100644 index 000000000..9ddd235e2 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "software.amazon.lambda.powertools.e2e.Function", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "software.amazon.lambda.powertools.e2e.Input", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/powertools-e2e-tests/handlers/logging-functional/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/log4j2.xml new file mode 100644 index 000000000..28e03a9e0 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-functional/src/main/resources/log4j2.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + <!-- We buffer everything to implicitly test buffer flushing --> + <BufferingAppender name="BufferedAppender" bufferAtVerbosity="INFO"> + <AppenderRef ref="JsonAppender" /> + </BufferingAppender> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="BufferedAppender" /> + </Root> + </Loggers> +</Configuration> diff --git a/powertools-e2e-tests/handlers/logging-log4j/pom.xml b/powertools-e2e-tests/handlers/logging-log4j/pom.xml new file mode 100644 index 000000000..022f029e6 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/pom.xml @@ -0,0 +1,82 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-logging-log4j</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Logging Log4j</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-e2e-tests/handlers/logging-log4j/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/logging-log4j/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..94520c447 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.PowertoolsLogging; + +public class Function implements RequestHandler<Input, String> { + private static final Logger LOG = LoggerFactory.getLogger(Function.class); + + @Logging + public String handleRequest(Input input, Context context) { + input.getKeys().forEach(MDC::put); + LOG.info(input.getMessage()); + + // Flush buffer manually since we buffer at INFO level to test log buffering + PowertoolsLogging.flushBuffer(); + + return "OK"; + } +} diff --git a/powertools-e2e-tests/handlers/logging-log4j/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/logging-log4j/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..66fd49ddc --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import java.util.Map; + +public class Input { + private String message; + private Map<String, String> keys; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Map<String, String> getKeys() { + return keys; + } + + public void setKeys(Map<String, String> keys) { + this.keys = keys; + } +} diff --git a/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..467af67a0 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,62 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields": [{ "name": "logger" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogLevel", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogFormat", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "java.lang.Void", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "java.util.Collections$UnmodifiableMap", + "fields": [{ "name": "m" }] + }, + { + "name": "jdk.internal.module.IllegalAccessLogger", + "fields": [{ "name": "logger" }] + }, + { + "name": "sun.misc.Unsafe", + "fields": [{ "name": "theUnsafe" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json new file mode 100644 index 000000000..9ddd235e2 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "software.amazon.lambda.powertools.e2e.Function", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "software.amazon.lambda.powertools.e2e.Input", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/log4j2.xml new file mode 100644 index 000000000..28e03a9e0 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j/src/main/resources/log4j2.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + <!-- We buffer everything to implicitly test buffer flushing --> + <BufferingAppender name="BufferedAppender" bufferAtVerbosity="INFO"> + <AppenderRef ref="JsonAppender" /> + </BufferingAppender> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="BufferedAppender" /> + </Root> + </Loggers> +</Configuration> diff --git a/powertools-e2e-tests/handlers/logging-logback/pom.xml b/powertools-e2e-tests/handlers/logging-logback/pom.xml new file mode 100644 index 000000000..f8458db25 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/pom.xml @@ -0,0 +1,82 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-logging-logback</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Logging Logback</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-logback</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-e2e-tests/handlers/logging-logback/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/logging-logback/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..94520c447 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.PowertoolsLogging; + +public class Function implements RequestHandler<Input, String> { + private static final Logger LOG = LoggerFactory.getLogger(Function.class); + + @Logging + public String handleRequest(Input input, Context context) { + input.getKeys().forEach(MDC::put); + LOG.info(input.getMessage()); + + // Flush buffer manually since we buffer at INFO level to test log buffering + PowertoolsLogging.flushBuffer(); + + return "OK"; + } +} diff --git a/powertools-e2e-tests/handlers/logging-logback/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/logging-logback/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..66fd49ddc --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import java.util.Map; + +public class Input { + private String message; + private Map<String, String> keys; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Map<String, String> getKeys() { + return keys; + } + + public void setKeys(Map<String, String> keys) { + this.keys = keys; + } +} diff --git a/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..467af67a0 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,62 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields": [{ "name": "logger" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogLevel", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogFormat", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "java.lang.Void", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "java.util.Collections$UnmodifiableMap", + "fields": [{ "name": "m" }] + }, + { + "name": "jdk.internal.module.IllegalAccessLogger", + "fields": [{ "name": "logger" }] + }, + { + "name": "sun.misc.Unsafe", + "fields": [{ "name": "theUnsafe" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json new file mode 100644 index 000000000..9ddd235e2 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "software.amazon.lambda.powertools.e2e.Function", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "software.amazon.lambda.powertools.e2e.Input", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json new file mode 100644 index 000000000..a603a9398 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlogback.xml\\E" + }]}, + "bundles":[] +} diff --git a/powertools-e2e-tests/handlers/logging-logback/src/main/resources/logback.xml b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/logback.xml new file mode 100644 index 000000000..0a5e4d146 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback/src/main/resources/logback.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + <appender name="JsonAppender" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"/> + </appender> + + <!-- We buffer everything to implicitly test buffer flushing --> + <appender name="BufferedAppender" class="software.amazon.lambda.powertools.logging.logback.BufferingAppender"> + <bufferAtVerbosity>INFO</bufferAtVerbosity> + <appender-ref ref="JsonAppender"/> + </appender> + + <root level="INFO"> + <appender-ref ref="BufferedAppender"/> + </root> +</configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/metrics/pom.xml b/powertools-e2e-tests/handlers/metrics/pom.xml new file mode 100644 index 000000000..ddc6ae1bd --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/pom.xml @@ -0,0 +1,82 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-metrics</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Metrics</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..7244b3212 --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.metrics.FlushMetrics; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricResolution; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.time.Instant; + +public class Function implements RequestHandler<Input, String> { + + Metrics metrics = MetricsFactory.getMetricsInstance(); + + @FlushMetrics(captureColdStart = true) + public String handleRequest(Input input, Context context) { + + Instant currentTimeTruncatedPlusThirty = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES) + .toInstant(ZoneOffset.UTC).plusSeconds(30); + metrics.setTimestamp(currentTimeTruncatedPlusThirty); + + DimensionSet dimensionSet = new DimensionSet(); + input.getDimensions().forEach((key, value) -> dimensionSet.addDimension(key, value)); + metrics.addDimension(dimensionSet); + + input.getMetrics().forEach((key, value) -> metrics.addMetric(key, value, MetricUnit.COUNT, + input.getHighResolution().equalsIgnoreCase("true") ? MetricResolution.HIGH + : MetricResolution.STANDARD)); + + return "OK"; + } +} diff --git a/powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..054c2867a --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import java.util.Map; + +public class Input { + private Map<String, Double> metrics; + + private Map<String, String> dimensions; + + private String highResolution; + + public Map<String, Double> getMetrics() { + return metrics; + } + + public void setMetrics(Map<String, Double> metrics) { + this.metrics = metrics; + } + + public String getHighResolution() { + return highResolution; + } + + public void setHighResolution(String highResolution) { + this.highResolution = highResolution; + } + + public Map<String, String> getDimensions() { + return dimensions; + } + + public void setDimensions(Map<String, String> dimensions) { + this.dimensions = dimensions; + } +} diff --git a/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..467af67a0 --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,62 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields": [{ "name": "logger" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogLevel", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogFormat", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "java.lang.Void", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "java.util.Collections$UnmodifiableMap", + "fields": [{ "name": "m" }] + }, + { + "name": "jdk.internal.module.IllegalAccessLogger", + "fields": [{ "name": "logger" }] + }, + { + "name": "sun.misc.Unsafe", + "fields": [{ "name": "theUnsafe" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json new file mode 100644 index 000000000..9ddd235e2 --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "software.amazon.lambda.powertools.e2e.Function", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "software.amazon.lambda.powertools.e2e.Input", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/parameters/pom.xml b/powertools-e2e-tests/handlers/parameters/pom.xml new file mode 100644 index 000000000..fb2deb2aa --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/pom.xml @@ -0,0 +1,86 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-parameters</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Parameters</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters-appconfig</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-e2e-tests/handlers/parameters/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/parameters/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..1e151825e --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.parameters.appconfig.AppConfigProvider; + +public class Function implements RequestHandler<Input, String> { + + @Logging + public String handleRequest(Input input, Context context) { + AppConfigProvider provider = AppConfigProvider.builder() + .withApplication(input.getApp()) + .withEnvironment(input.getEnvironment()) + .build(); + + //(input.getEnvironment(), input.getApp()); + return provider.get(input.getKey()); + + } +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/parameters/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/parameters/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..b481d25e1 --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +public class Input { + + private String app; + private String environment; + private String key; + + public String getApp() { + return app; + } + + public void setApp(String app) { + this.app = app; + } + + public String getEnvironment() { + return environment; + } + + public void setEnvironment(String environment) { + this.environment = environment; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + +} diff --git a/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..467af67a0 --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,62 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields": [{ "name": "logger" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogLevel", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogFormat", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "java.lang.Void", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "java.util.Collections$UnmodifiableMap", + "fields": [{ "name": "m" }] + }, + { + "name": "jdk.internal.module.IllegalAccessLogger", + "fields": [{ "name": "logger" }] + }, + { + "name": "sun.misc.Unsafe", + "fields": [{ "name": "theUnsafe" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json new file mode 100644 index 000000000..9ddd235e2 --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "software.amazon.lambda.powertools.e2e.Function", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "software.amazon.lambda.powertools.e2e.Input", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/powertools-e2e-tests/handlers/parameters/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/parameters/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/parameters/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="JsonAppender"/> + </Root> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/pom.xml b/powertools-e2e-tests/handlers/pom.xml new file mode 100644 index 000000000..477b49dc0 --- /dev/null +++ b/powertools-e2e-tests/handlers/pom.xml @@ -0,0 +1,275 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + <packaging>pom</packaging> + <name>Handlers for End-to-End tests</name> + <description>Fake handlers that use Powertools for AWS Lambda (Java).</description> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <lambda.java.core>1.4.0</lambda.java.core> + <lambda.java.serialization>1.1.6</lambda.java.serialization> + <lambda.java.events>3.16.1</lambda.java.events> + <maven.shade.version>3.6.1</maven.shade.version> + <aspectj.plugin.version>1.14.1</aspectj.plugin.version> + <maven.compiler.version>3.14.1</maven.compiler.version> + <aws.sdk.version>2.40.9</aws.sdk.version> + <aspectj.version>1.9.20.1</aspectj.version> + <maven.deploy.skip>true</maven.deploy.skip> + </properties> + + <modules> + <module>batch</module> + <module>largemessage</module> + <module>largemessage-functional</module> + <module>largemessage_idempotent</module> + <module>logging-log4j</module> + <module>logging-logback</module> + <module>logging-functional</module> + <module>tracing</module> + <module>metrics</module> + <module>idempotency</module> + <module>idempotency-functional</module> + <module>idempotency-generics</module> + <module>parameters</module> + <module>validation-alb-event</module> + <module>validation-apigw-event</module> + </modules> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>bom</artifactId> + <version>${aws.sdk.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <version>${aspectj.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-log4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging-logback</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-metrics</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-dynamodb</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters-appconfig</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-large-messages</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-batch</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-validation</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + <version>${lambda.java.core}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <version>${lambda.java.events}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-serialization</artifactId> + <version>${lambda.java.serialization}</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + <version>2.8.7</version> + </dependency> + </dependencies> + </dependencyManagement> + + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>${maven.shade.version}</version> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <finalName>function</finalName> + </configuration> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <transformers> + <transformer + implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer" /> + </transformers> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId> + <version>0.2.0</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>${maven.compiler.version}</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <useIncrementalCompilation>false</useIncrementalCompilation> + </configuration> + </plugin> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>${aspectj.plugin.version}</version> + <configuration> + <verbose>true</verbose> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <Xlint>ignore</Xlint> + <encoding>${project.build.sourceEncoding}</encoding> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + <goal>test-compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>build-native</id> + <goals> + <goal>build</goal> + </goals> + <phase>package</phase> + </execution> + </executions> + <configuration> + <imageName>handler</imageName> + <mainClass>com.amazonaws.services.lambda.runtime.api.client.AWSLambda</mainClass> + <buildArgs> + <arg>--enable-url-protocols=http</arg> + <arg>--add-opens java.base/java.util=ALL-UNNAMED</arg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> + + <profiles> + <!-- https://github.com/eclipse-aspectj/aspectj/blob/master/docs/dist/doc/JavaVersionCompatibility.md --> + <profile> + <id>jdk11to16</id> + <activation> + <jdk>[11,16]</jdk> + </activation> + <properties> + <aspectj.version>1.9.7</aspectj.version> + </properties> + </profile> + <profile> + <id>jdk17to20</id> + <activation> + <jdk>[17,20]</jdk> + </activation> + <properties> + <aspectj.version>1.9.20.1</aspectj.version> + </properties> + </profile> + <profile> + <id>jdk21</id> + <activation> + <jdk>[21,)</jdk> + </activation> + <properties> + <aspectj.version>1.9.21</aspectj.version> + </properties> + </profile> + <profile> + <id>jdk25</id> + <activation> + <jdk>[25,)</jdk> + </activation> + <properties> + <aspectj.version>1.9.25</aspectj.version> + </properties> + </profile> + </profiles> + +</project> diff --git a/powertools-e2e-tests/handlers/tracing/pom.xml b/powertools-e2e-tests/handlers/tracing/pom.xml new file mode 100644 index 000000000..9874ce986 --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/pom.xml @@ -0,0 +1,82 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-tracing</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Tracing</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-runtime-interface-client</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-tracing</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>native-image</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..0f140a20d --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.tracing.Tracing; +import software.amazon.lambda.powertools.tracing.TracingUtils; + +public class Function implements RequestHandler<Input, String> { + + @Tracing + public String handleRequest(Input input, Context context) { + try { + Thread.sleep(100); // simulate stuff + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + String message = buildMessage(input.getMessage(), context.getFunctionName()); + + TracingUtils.withSubsegment("internal_stuff", subsegment -> + { + try { + Thread.sleep(100); // simulate stuff + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + + return message; + } + + @Tracing + private String buildMessage(String message, String funcName) { + TracingUtils.putAnnotation("message", message); + try { + Thread.sleep(150); // simulate other stuff + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return String.format("%s (%s)", message, funcName); + } +} diff --git a/powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..ed89f4498 --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +public class Input { + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"<init>","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"<init>","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..467af67a0 --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,62 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields": [{ "name": "logger" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogLevel", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogFormat", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "java.lang.Void", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "java.util.Collections$UnmodifiableMap", + "fields": [{ "name": "m" }] + }, + { + "name": "jdk.internal.module.IllegalAccessLogger", + "fields": [{ "name": "logger" }] + }, + { + "name": "sun.misc.Unsafe", + "fields": [{ "name": "theUnsafe" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json new file mode 100644 index 000000000..9ddd235e2 --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "software.amazon.lambda.powertools.e2e.Function", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "software.amazon.lambda.powertools.e2e.Input", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/powertools-e2e-tests/handlers/tracing/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/tracing/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="JsonAppender"/> + </Root> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/validation-alb-event/pom.xml b/powertools-e2e-tests/handlers/validation-alb-event/pom.xml new file mode 100644 index 000000000..14dbb9b13 --- /dev/null +++ b/powertools-e2e-tests/handlers/validation-alb-event/pom.xml @@ -0,0 +1,60 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-validation-alb-event</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Validation ALB event</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-validation</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-validation</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-e2e-tests/handlers/validation-alb-event/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/validation-alb-event/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..d221ee153 --- /dev/null +++ b/powertools-e2e-tests/handlers/validation-alb-event/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; + +import software.amazon.lambda.powertools.validation.Validation; + +public class Function + implements RequestHandler<ApplicationLoadBalancerRequestEvent, ApplicationLoadBalancerResponseEvent> { + @Validation(inboundSchema = "classpath:/validation/inbound_schema.json", outboundSchema = "classpath:/validation/outbound_schema.json") + public ApplicationLoadBalancerResponseEvent handleRequest(ApplicationLoadBalancerRequestEvent input, + Context context) { + ApplicationLoadBalancerResponseEvent response = new ApplicationLoadBalancerResponseEvent(); + response.setBody(input.getBody()); + response.setStatusCode(200); + response.setIsBase64Encoded(false); + return response; + } +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/validation-alb-event/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/validation-alb-event/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/validation-alb-event/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="JsonAppender"/> + </Root> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/validation-alb-event/src/main/resources/validation/inbound_schema.json b/powertools-e2e-tests/handlers/validation-alb-event/src/main/resources/validation/inbound_schema.json new file mode 100644 index 000000000..3665879eb --- /dev/null +++ b/powertools-e2e-tests/handlers/validation-alb-event/src/main/resources/validation/inbound_schema.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/product.json", + "type": "object", + "title": "Product schema", + "description": "JSON schema to validate Products", + "default": {}, + "examples": [ + { + "id": 43242, + "name": "FooBar XY", + "price": 258 + } + ], + "required": [ + "price" + ], + "properties": { + "price": { + "$id": "#/properties/price", + "type": "number", + "title": "Price of the product", + "description": "Positive price of the product", + "default": 0, + "exclusiveMinimum": 0, + "examples": [ + 258.99 + ] + } + }, + "additionalProperties": true +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/validation-alb-event/src/main/resources/validation/outbound_schema.json b/powertools-e2e-tests/handlers/validation-alb-event/src/main/resources/validation/outbound_schema.json new file mode 100644 index 000000000..b1f14d025 --- /dev/null +++ b/powertools-e2e-tests/handlers/validation-alb-event/src/main/resources/validation/outbound_schema.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/product.json", + "type": "object", + "title": "Product schema", + "description": "JSON schema to validate Products", + "default": {}, + "examples": [ + { + "id": 43242, + "name": "FooBar XY", + "price": 258 + } + ], + "required": [ + "price" + ], + "properties": { + "price": { + "$id": "#/properties/price", + "type": "number", + "title": "Price of the product", + "description": "Positive price of the product", + "default": 0, + "exclusiveMaximum": 1000, + "examples": [ + 258.99 + ] + } + }, + "additionalProperties": true +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/validation-apigw-event/pom.xml b/powertools-e2e-tests/handlers/validation-apigw-event/pom.xml new file mode 100644 index 000000000..290e47b13 --- /dev/null +++ b/powertools-e2e-tests/handlers/validation-apigw-event/pom.xml @@ -0,0 +1,60 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>e2e-test-handlers-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>e2e-test-handler-validation-apigw-event</artifactId> + <packaging>jar</packaging> + <name>E2E test handler – Validation API Gateway event</name> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-validation</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-validation</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-e2e-tests/handlers/validation-apigw-event/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/validation-apigw-event/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..5ac8951d8 --- /dev/null +++ b/powertools-e2e-tests/handlers/validation-apigw-event/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + +import software.amazon.lambda.powertools.validation.Validation; + +public class Function implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + @Validation(inboundSchema = "classpath:/validation/inbound_schema.json", outboundSchema = "classpath:/validation/outbound_schema.json") + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); + response.setBody(input.getBody()); + response.setStatusCode(200); + response.setIsBase64Encoded(false); + return response; + } +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/validation-apigw-event/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/validation-apigw-event/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/validation-apigw-event/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="JsonAppender" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + </Appenders> + <Loggers> + <Root level="INFO"> + <AppenderRef ref="JsonAppender"/> + </Root> + <Logger name="JsonLogger" level="INFO" additivity="false"> + <AppenderRef ref="JsonAppender"/> + </Logger> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/validation-apigw-event/src/main/resources/validation/inbound_schema.json b/powertools-e2e-tests/handlers/validation-apigw-event/src/main/resources/validation/inbound_schema.json new file mode 100644 index 000000000..3665879eb --- /dev/null +++ b/powertools-e2e-tests/handlers/validation-apigw-event/src/main/resources/validation/inbound_schema.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/product.json", + "type": "object", + "title": "Product schema", + "description": "JSON schema to validate Products", + "default": {}, + "examples": [ + { + "id": 43242, + "name": "FooBar XY", + "price": 258 + } + ], + "required": [ + "price" + ], + "properties": { + "price": { + "$id": "#/properties/price", + "type": "number", + "title": "Price of the product", + "description": "Positive price of the product", + "default": 0, + "exclusiveMinimum": 0, + "examples": [ + 258.99 + ] + } + }, + "additionalProperties": true +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/validation-apigw-event/src/main/resources/validation/outbound_schema.json b/powertools-e2e-tests/handlers/validation-apigw-event/src/main/resources/validation/outbound_schema.json new file mode 100644 index 000000000..b1f14d025 --- /dev/null +++ b/powertools-e2e-tests/handlers/validation-apigw-event/src/main/resources/validation/outbound_schema.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/product.json", + "type": "object", + "title": "Product schema", + "description": "JSON schema to validate Products", + "default": {}, + "examples": [ + { + "id": 43242, + "name": "FooBar XY", + "price": 258 + } + ], + "required": [ + "price" + ], + "properties": { + "price": { + "$id": "#/properties/price", + "type": "number", + "title": "Price of the product", + "description": "Positive price of the product", + "default": 0, + "exclusiveMaximum": 1000, + "examples": [ + 258.99 + ] + } + }, + "additionalProperties": true +} \ No newline at end of file diff --git a/powertools-e2e-tests/pom.xml b/powertools-e2e-tests/pom.xml new file mode 100644 index 000000000..fec4dec92 --- /dev/null +++ b/powertools-e2e-tests/pom.xml @@ -0,0 +1,271 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>powertools-parent</artifactId> + <groupId>software.amazon.lambda</groupId> + <version>2.9.0</version> + </parent> + + <artifactId>powertools-e2e-tests</artifactId> + <name>Powertools for AWS Lambda (Java) - End-to-end tests</name> + <description>Powertools for AWS Lambda (Java) – End-To-End Tests</description> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <constructs.version>10.4.3</constructs.version> + <cdk.version>2.224.0</cdk.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j2-impl</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>lambda</artifactId> + <version>${aws.sdk.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>dynamodb</artifactId> + <version>${aws.sdk.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>kinesis</artifactId> + <version>${aws.sdk.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>cloudwatch</artifactId> + <version>${aws.sdk.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>xray</artifactId> + <version>${aws.sdk.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sqs</artifactId> + <version>${aws.sdk.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>amazon-sqs-java-extended-client-lib</artifactId> + <version>2.1.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>url-connection-client</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.21.0</version> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.github.resilience4j</groupId> + <artifactId>resilience4j-retry</artifactId> + <!-- 2.x. not compatible with Java 11 anymore --> + <version>1.7.1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awscdk</groupId> + <artifactId>aws-cdk-lib</artifactId> + <version>${cdk.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.constructs</groupId> + <artifactId>constructs</artifactId> + <version>${constructs.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <version>${aws.sdk.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>cloudformation</artifactId> + <version>${aws.sdk.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sts</artifactId> + <version>${aws.sdk.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.yaml</groupId> + <artifactId>snakeyaml</artifactId> + <version>2.5</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.datatype</groupId> + <artifactId>jackson-datatype-jsr310</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-serialization</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>default</id> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + <build> + <plugins> + <!-- Don't deploy the e2e tests --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <encoding>UTF-8</encoding> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>e2e</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>integration-test</goal> + <goal>verify</goal> + </goals> + </execution> + </executions> + <configuration> + <skipAfterFailureCount>1</skipAfterFailureCount> <!-- no need to continue / deploy more + resources and lose time --> + <includes> + <include>**/*E2ET.java</include> + </includes> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>e2e-graal</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>integration-test</goal> + <goal>verify</goal> + </goals> + </execution> + </executions> + <configuration> + <skipAfterFailureCount>1</skipAfterFailureCount> + <includes> + <!-- Add additional E2E tests here when adding new GraalVM support to utilities. --> + <include>**/MetricsE2ET.java</include> + <include>**/LoggingE2ET.java</include> + <include>**/ParametersE2ET.java</include> + <include>**/TracingE2ET.java</include> + <include>**/IdempotencyE2ET.java</include> + </includes> + <systemPropertyVariables> + <graalvm.enabled>true</graalvm.enabled> + </systemPropertyVariables> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + +</project> diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/BatchE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/BatchE2ET.java new file mode 100644 index 000000000..181d5e583 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/BatchE2ET.java @@ -0,0 +1,293 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.dynamodb.model.ScanRequest; +import software.amazon.awssdk.services.dynamodb.model.ScanResponse; +import software.amazon.awssdk.services.kinesis.KinesisClient; +import software.amazon.awssdk.services.kinesis.model.PutRecordsRequest; +import software.amazon.awssdk.services.kinesis.model.PutRecordsRequestEntry; +import software.amazon.awssdk.services.sqs.SqsClient; +import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequest; +import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequestEntry; +import software.amazon.lambda.powertools.testutils.DataNotReadyException; +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.RetryUtils; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +class BatchE2ET { + private static final SdkHttpClient httpClient = UrlConnectionHttpClient.builder().build(); + private static final Region region = Region.of(System.getProperty("AWS_DEFAULT_REGION", "eu-west-1")); + private static Infrastructure infrastructure; + private static String queueUrl; + private static String kinesisStreamName; + + private static ObjectMapper objectMapper; + private static String outputTable; + private static DynamoDbClient ddbClient; + private static SqsClient sqsClient; + private static KinesisClient kinesisClient; + private static String ddbStreamsTestTable; + private final List<Product> testProducts; + + public BatchE2ET() { + testProducts = Arrays.asList( + new Product(1, "product1", 1.23), + new Product(2, "product2", 4.56), + new Product(3, "product3", 6.78)); + } + + @BeforeAll + @Timeout(value = 5, unit = TimeUnit.MINUTES) + static void setup() { + String random = UUID.randomUUID().toString().substring(0, 6); + String queueName = "batchqueue" + random; + kinesisStreamName = "batchstream" + random; + ddbStreamsTestTable = "ddbstreams" + random; + + objectMapper = JsonConfig.get().getObjectMapper(); + + infrastructure = Infrastructure.builder() + .testName(BatchE2ET.class.getSimpleName()) + .pathToFunction("batch") + .queue(queueName) + .ddbStreamsTableName(ddbStreamsTestTable) + .kinesisStream(kinesisStreamName) + .build(); + + Map<String, String> outputs = infrastructure.deploy(); + queueUrl = outputs.get("QueueURL"); + kinesisStreamName = outputs.get("KinesisStreamName"); + outputTable = outputs.get("TableNameForAsyncTests"); + ddbStreamsTestTable = outputs.get("DdbStreamsTestTable"); + + ddbClient = DynamoDbClient.builder() + .region(region) + .httpClient(httpClient) + .build(); + + // GIVEN + sqsClient = SqsClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + kinesisClient = KinesisClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + } + + @AfterAll + static void tearDown() { + if (infrastructure != null) { + infrastructure.destroy(); + } + } + + @AfterEach + void cleanUpTest() { + // Delete everything in the output table + ScanResponse items = ddbClient.scan(ScanRequest.builder() + .tableName(outputTable) + .build()); + + for (Map<String, AttributeValue> item : items.items()) { + Map<String, AttributeValue> key = new HashMap<>() { + { + put("functionName", AttributeValue.builder() + .s(item.get("functionName").s()) + .build()); + put("id", AttributeValue.builder() + .s(item.get("id").s()) + .build()); + } + }; + + ddbClient.deleteItem(DeleteItemRequest.builder() + .tableName(outputTable) + .key(key) + .build()); + } + } + + @Test + void sqsBatchProcessingSucceeds() { + List<SendMessageBatchRequestEntry> entries = testProducts.stream() + .map(p -> { + try { + return SendMessageBatchRequestEntry.builder() + .id(p.getName()) + .messageBody(objectMapper.writeValueAsString(p)) + .build(); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList()); + + // WHEN + sqsClient.sendMessageBatch(SendMessageBatchRequest.builder() + .entries(entries) + .queueUrl(queueUrl) + .build()); + + // THEN + ScanRequest scanRequest = ScanRequest.builder().tableName(outputTable).build(); + RetryUtils.withRetry(() -> { + ScanResponse items = ddbClient.scan(scanRequest); + if (!areAllTestProductsPresent(items)) { + throw new DataNotReadyException("sqs-batch-processing not complete yet"); + } + return null; + }, "sqs-batch-processing", DataNotReadyException.class).get(); + + ScanResponse finalItems = ddbClient.scan(scanRequest); + assertThat(areAllTestProductsPresent(finalItems)).isTrue(); + } + + @Test + void kinesisBatchProcessingSucceeds() { + List<PutRecordsRequestEntry> entries = testProducts.stream() + .map(p -> { + try { + return PutRecordsRequestEntry.builder() + .partitionKey("1") + .data(SdkBytes.fromUtf8String(objectMapper.writeValueAsString(p))) + .build(); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList()); + + // WHEN + kinesisClient.putRecords(PutRecordsRequest.builder() + .streamName(kinesisStreamName) + .records(entries) + .build()); + + // THEN + ScanRequest scanRequest = ScanRequest.builder().tableName(outputTable).build(); + RetryUtils.withRetry(() -> { + ScanResponse items = ddbClient.scan(scanRequest); + if (!areAllTestProductsPresent(items)) { + throw new DataNotReadyException("kinesis-batch-processing not complete yet"); + } + return null; + }, "kinesis-batch-processing", DataNotReadyException.class).get(); + + ScanResponse finalItems = ddbClient.scan(scanRequest); + assertThat(areAllTestProductsPresent(finalItems)).isTrue(); + } + + @Test + void ddbStreamsBatchProcessingSucceeds() { + // GIVEN + String theId = "my-test-id"; + + // WHEN + ddbClient.putItem(PutItemRequest.builder() + .tableName(ddbStreamsTestTable) + .item(new HashMap<String, AttributeValue>() { + { + put("id", AttributeValue.builder() + .s(theId) + .build()); + } + }) + .build()); + + // THEN + ScanRequest scanRequest = ScanRequest.builder().tableName(outputTable).build(); + RetryUtils.withRetry(() -> { + ScanResponse items = ddbClient.scan(scanRequest); + if (items.count() != 1) { + throw new DataNotReadyException("DDB streams processing not complete yet"); + } + return null; + }, "ddb-streams-batch-processing", DataNotReadyException.class).get(); + + ScanResponse finalItems = ddbClient.scan(scanRequest); + assertThat(finalItems.count()).isEqualTo(1); + assertThat(finalItems.items().get(0).get("id").s()).isEqualTo(theId); + } + + private boolean areAllTestProductsPresent(ScanResponse items) { + for (Product p : testProducts) { + boolean foundIt = false; + for (Map<String, AttributeValue> a : items.items()) { + if (a.get("id").s().equals(Long.toString(p.id))) { + foundIt = true; + break; + } + } + if (!foundIt) { + return false; + } + } + return true; + } + + class Product { + private long id; + private String name; + private double price; + + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public double getPrice() { + return price; + } + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ET.java new file mode 100644 index 000000000..9ced0e4fb --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ET.java @@ -0,0 +1,84 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools; + +import static software.amazon.lambda.powertools.testutils.Infrastructure.FUNCTION_NAME_OUTPUT; +import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; + +import java.time.Year; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class IdempotencyE2ET { + private Infrastructure infrastructure; + private String functionName; + + private void setupInfrastructure(String pathToFunction) { + String random = UUID.randomUUID().toString().substring(0, 6); + infrastructure = Infrastructure.builder() + .testName(IdempotencyE2ET.class.getSimpleName() + "-" + pathToFunction) + .pathToFunction(pathToFunction) + .idempotencyTable("idempo" + random) + .build(); + Map<String, String> outputs = infrastructure.deploy(); + functionName = outputs.get(FUNCTION_NAME_OUTPUT); + } + + @AfterEach + void tearDown() { + if (infrastructure != null) { + infrastructure.destroy(); + } + } + + @ParameterizedTest + @ValueSource(strings = { "idempotency", "idempotency-functional", "idempotency-generics" }) + @Timeout(value = 15, unit = TimeUnit.MINUTES) + void test_ttlNotExpired_sameResult_ttlExpired_differentResult(String pathToFunction) throws InterruptedException { + setupInfrastructure(pathToFunction); + // GIVEN + String event = "{\"message\":\"TTL 10sec\"}"; + + // WHEN + // First invocation + InvocationResult result1 = invokeFunction(functionName, event); + + // Second invocation (should get same result) + InvocationResult result2 = invokeFunction(functionName, event); + + // Function idempotency record expiration is set to 10 seconds + Thread.sleep(12000); + + // Third invocation (should get different result) + InvocationResult result3 = invokeFunction(functionName, event); + + // THEN + Assertions.assertThat(result1.getResult()).contains(Year.now().toString()); + Assertions.assertThat(result2.getResult()).isEqualTo(result1.getResult()); + Assertions.assertThat(result3.getResult()).isNotEqualTo(result2.getResult()); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LargeMessageE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LargeMessageE2ET.java new file mode 100644 index 000000000..0de2dca60 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LargeMessageE2ET.java @@ -0,0 +1,216 @@ +package software.amazon.lambda.powertools; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.lambda.powertools.testutils.Infrastructure.FUNCTION_NAME_OUTPUT; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazon.sqs.javamessaging.AmazonSQSExtendedClient; +import com.amazon.sqs.javamessaging.ExtendedClientConfiguration; + +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryResponse; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.sqs.SqsClient; +import software.amazon.awssdk.services.sqs.model.SendMessageRequest; +import software.amazon.lambda.powertools.testutils.DataNotReadyException; +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.RetryUtils; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class LargeMessageE2ET { + + private static final Logger LOG = LoggerFactory.getLogger(LargeMessageE2ET.class); + private static final SdkHttpClient httpClient = UrlConnectionHttpClient.builder().build(); + private static final Region region = Region.of(System.getProperty("AWS_DEFAULT_REGION", "eu-west-1")); + + private static final S3Client s3Client = S3Client.builder() + .httpClient(httpClient) + .region(region) + .build(); + private static final DynamoDbClient dynamoDbClient = DynamoDbClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + + private Infrastructure infrastructure; + private String functionName; + private String bucketName; + private String queueUrl; + private String tableName; + private String messageId; + private String currentPathToFunction; + + private void setupInfrastructure(String pathToFunction) { + // Do not re-deploy the same function + if (pathToFunction.equals(currentPathToFunction)) { + return; + } + + // Destroy any existing infrastructure before re-deploying + if (infrastructure != null) { + infrastructure.destroy(); + } + + String random = UUID.randomUUID().toString().substring(0, 6); + bucketName = "largemessagebucket" + random; + String queueName = "largemessagequeue" + random; + + infrastructure = Infrastructure.builder() + .testName(LargeMessageE2ET.class.getSimpleName() + "-" + pathToFunction) + .queue(queueName) + .largeMessagesBucket(bucketName) + .pathToFunction(pathToFunction) + .timeoutInSeconds(60) + .build(); + + Map<String, String> outputs = infrastructure.deploy(); + functionName = outputs.get(FUNCTION_NAME_OUTPUT); + queueUrl = outputs.get("QueueURL"); + tableName = outputs.get("TableNameForAsyncTests"); + currentPathToFunction = pathToFunction; + + LOG.info("Testing '{}' with {}", LargeMessageE2ET.class.getSimpleName(), pathToFunction); + } + + @AfterAll + void cleanup() { + if (infrastructure != null) { + infrastructure.destroy(); + } + } + + @AfterEach + void tearDown() { + reset(); + } + + private void reset() { + if (messageId != null) { + Map<String, AttributeValue> itemToDelete = new HashMap<>(); + itemToDelete.put("functionName", AttributeValue.builder().s(functionName).build()); + itemToDelete.put("id", AttributeValue.builder().s(messageId).build()); + dynamoDbClient.deleteItem(DeleteItemRequest.builder().tableName(tableName).key(itemToDelete).build()); + messageId = null; + } + } + + @ParameterizedTest + @ValueSource(strings = { "largemessage", "largemessage-functional" }) + @Timeout(value = 5, unit = TimeUnit.MINUTES) + void bigSQSMessageOffloadedToS3_shouldLoadFromS3(String pathToFunction) throws IOException { + setupInfrastructure(pathToFunction); + + // GIVEN + final ExtendedClientConfiguration extendedClientConfig = new ExtendedClientConfiguration() + .withPayloadSupportEnabled(s3Client, bucketName); + try (AmazonSQSExtendedClient client = new AmazonSQSExtendedClient( + SqsClient.builder().region(region).httpClient(httpClient).build(), extendedClientConfig); + InputStream inputStream = this.getClass().getResourceAsStream("/large_sqs_message.txt");) { + String bigMessage = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + + // WHEN + client.sendMessage(SendMessageRequest + .builder() + .queueUrl(queueUrl) + .messageBody(bigMessage) + .build()); + } + + // THEN + QueryRequest request = QueryRequest + .builder() + .tableName(tableName) + .keyConditionExpression("functionName = :func") + .expressionAttributeValues( + Collections.singletonMap(":func", AttributeValue.builder().s(functionName).build())) + .build(); + + RetryUtils.withRetry(() -> { + QueryResponse response = dynamoDbClient.query(request); + if (response.items().size() != 1) { + throw new DataNotReadyException("Large message processing not complete yet"); + } + return null; + }, "large-message-processing", DataNotReadyException.class).get(); + + QueryResponse finalResponse = dynamoDbClient.query(request); + List<Map<String, AttributeValue>> items = finalResponse.items(); + assertThat(items).hasSize(1); + messageId = items.get(0).get("id").s(); + assertThat(Integer.valueOf(items.get(0).get("bodySize").n())).isEqualTo(300977); + assertThat(items.get(0).get("bodyMD5").s()).isEqualTo("22bde5e7b05fa80bc7be45bdd4bc6c75"); + } + + @ParameterizedTest + @ValueSource(strings = { "largemessage", "largemessage-functional" }) + @Timeout(value = 5, unit = TimeUnit.MINUTES) + void smallSQSMessage_shouldNotReadFromS3(String pathToFunction) { + setupInfrastructure(pathToFunction); + + // GIVEN + final ExtendedClientConfiguration extendedClientConfig = new ExtendedClientConfiguration() + .withPayloadSupportEnabled(s3Client, bucketName); + try (AmazonSQSExtendedClient client = new AmazonSQSExtendedClient( + SqsClient.builder().region(region).httpClient(httpClient).build(), + extendedClientConfig)) { + String message = "Hello World"; + + // WHEN + client.sendMessage(SendMessageRequest + .builder() + .queueUrl(queueUrl) + .messageBody(message) + .build()); + + // THEN + QueryRequest request = QueryRequest + .builder() + .tableName(tableName) + .keyConditionExpression("functionName = :func") + .expressionAttributeValues( + Collections.singletonMap(":func", AttributeValue.builder().s(functionName).build())) + .build(); + + RetryUtils.withRetry(() -> { + QueryResponse response = dynamoDbClient.query(request); + if (response.items().size() != 1) { + throw new DataNotReadyException("Small message processing not complete yet"); + } + return null; + }, "small-message-processing", DataNotReadyException.class).get(); + + QueryResponse finalResponse = dynamoDbClient.query(request); + List<Map<String, AttributeValue>> items = finalResponse.items(); + assertThat(items).hasSize(1); + messageId = items.get(0).get("id").s(); + assertThat(Integer.valueOf(items.get(0).get("bodySize").n())).isEqualTo( + message.getBytes(StandardCharsets.UTF_8).length); + assertThat(items.get(0).get("bodyMD5").s()).isEqualTo("b10a8db164e0754105b7a99be72e3fe5"); + } + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LargeMessageIdempotentE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LargeMessageIdempotentE2ET.java new file mode 100644 index 000000000..cd787b60a --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LargeMessageIdempotentE2ET.java @@ -0,0 +1,216 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryResponse; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.sqs.SqsClient; +import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; +import software.amazon.awssdk.services.sqs.model.SendMessageRequest; +import software.amazon.lambda.powertools.testutils.DataNotReadyException; +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.RetryUtils; + +class LargeMessageIdempotentE2ET { + + private static final Logger LOG = LoggerFactory.getLogger(LargeMessageIdempotentE2ET.class); + private static final SdkHttpClient httpClient = UrlConnectionHttpClient.builder().build(); + private static final Region region = Region.of(System.getProperty("AWS_DEFAULT_REGION", "eu-west-1")); + + private static final S3Client s3Client = S3Client.builder() + .httpClient(httpClient) + .region(region) + .build(); + // cannot use the extended library as it will create different S3 objects (we need to have the same for Idempotency) + private static final SqsClient sqsClient = SqsClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + private static final DynamoDbClient dynamoDbClient = DynamoDbClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + + private static Infrastructure infrastructure; + private static String functionName; + private static String bucketName; + private static String queueUrl; + private static String tableName; + + @BeforeAll + @Timeout(value = 5, unit = TimeUnit.MINUTES) + static void setup() { + String random = UUID.randomUUID().toString().substring(0, 6); + bucketName = "largemessagebucket" + random; + String queueName = "largemessagequeue" + random; + + infrastructure = Infrastructure.builder() + .testName(LargeMessageIdempotentE2ET.class.getSimpleName()) + .pathToFunction("largemessage_idempotent") + .idempotencyTable("idempo" + random) + .queue(queueName) + .largeMessagesBucket(bucketName) + .build(); + + Map<String, String> outputs = infrastructure.deploy(); + + functionName = outputs.get(Infrastructure.FUNCTION_NAME_OUTPUT); + queueUrl = outputs.get("QueueURL"); + tableName = outputs.get("TableNameForAsyncTests"); + + LOG.info("Testing '" + LargeMessageIdempotentE2ET.class.getSimpleName() + "'"); + } + + @AfterAll + static void tearDown() { + if (infrastructure != null) { + infrastructure.destroy(); + } + } + + @Test + void test_ttlNotExpired_doesNotInsertInDDB_ttlExpired_insertInDDB() throws InterruptedException, + IOException { + // GIVEN + String s3Key = UUID.randomUUID().toString(); + try (InputStream inputStream = this.getClass().getResourceAsStream("/large_sqs_message.txt")) { + String bigMessage = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + + // upload manually to S3 + s3Client.putObject(PutObjectRequest.builder() + .bucket(bucketName) + .key(s3Key) + .build(), RequestBody.fromString(bigMessage)); + } + + // WHEN + SendMessageRequest messageRequest = SendMessageRequest.builder() + .queueUrl(queueUrl) + .messageBody(String.format( + "[\"software.amazon.payloadoffloading.PayloadS3Pointer\",{\"s3BucketName\":\"%s\",\"s3Key\":\"%s\"}]", + bucketName, s3Key)) + .messageAttributes(Collections.singletonMap("SQSLargePayloadSize", MessageAttributeValue.builder() + .stringValue("300977") + .dataType("Number") + .build())) + .build(); + + // First invocation + // send message to SQS with the good pointer and metadata + sqsClient.sendMessage(messageRequest); + + // THEN + QueryRequest request = QueryRequest + .builder() + .tableName(tableName) + .keyConditionExpression("functionName = :func") + .expressionAttributeValues( + Collections.singletonMap(":func", AttributeValue.builder().s(functionName).build())) + .build(); + + RetryUtils.withRetry(() -> { + QueryResponse response = dynamoDbClient.query(request); + if (response.items().size() != 1) { + throw new DataNotReadyException("First invocation processing not complete yet"); + } + return null; + }, "first-invocation-processing", DataNotReadyException.class).get(); + + QueryResponse response = dynamoDbClient.query(request); + List<Map<String, AttributeValue>> items = response.items(); + assertThat(items).hasSize(1); + assertThat(Integer.valueOf(items.get(0).get("bodySize").n())).isEqualTo(300977); + assertThat(items.get(0).get("bodyMD5").s()).isEqualTo("22bde5e7b05fa80bc7be45bdd4bc6c75"); + long timeOfInvocation1 = Long.parseLong(items.get(0).get("now").n()); + + // WHEN + // Second invocation + // send the same message before ttl expires + sqsClient.sendMessage(messageRequest); + + RetryUtils.withRetry(() -> { + QueryResponse resp = dynamoDbClient.query(request); + if (resp.items().size() != 1) { + throw new DataNotReadyException("Second invocation processing not complete yet"); + } + return null; + }, "second-invocation-processing", DataNotReadyException.class).get(); + + // THEN + response = dynamoDbClient.query(request); + items = response.items(); + assertThat(items).hasSize(1); // we should have the same number of items (idempotency working) + assertThat(Integer.valueOf(items.get(0).get("bodySize").n())).isEqualTo(300977); + assertThat(items.get(0).get("bodyMD5").s()).isEqualTo("22bde5e7b05fa80bc7be45bdd4bc6c75"); + long timeOfInvocation2 = Long.parseLong(items.get(0).get("now").n()); + assertThat(timeOfInvocation2).isEqualTo(timeOfInvocation1); // should be the same as first invocation + + // WHEN + // waiting for TTL to expire + Thread.sleep(24000); + + // Third invocation + // send the same message again + sqsClient.sendMessage(messageRequest); + + RetryUtils.withRetry(() -> { + QueryResponse resp = dynamoDbClient.query(request); + if (resp.items().size() != 2) { + throw new DataNotReadyException("Third invocation processing not complete yet"); + } + return null; + }, "third-invocation-processing", DataNotReadyException.class).get(); + + // THEN + response = dynamoDbClient.query(request); + items = response.items(); + assertThat(items).hasSize(2); // not idempotent anymore, function should put a new item in DDB + assertThat(Integer.valueOf(items.get(0).get("bodySize").n())).isEqualTo(300977); + assertThat(items.get(0).get("bodyMD5").s()).isEqualTo("22bde5e7b05fa80bc7be45bdd4bc6c75"); + assertThat(Integer.valueOf(items.get(1).get("bodySize").n())).isEqualTo(300977); + assertThat(items.get(1).get("bodyMD5").s()).isEqualTo("22bde5e7b05fa80bc7be45bdd4bc6c75"); + long timeOfInvocation3 = Long.parseLong(items.get(0).get("now").n()); + long timeOfInvocation4 = Long.parseLong(items.get(1).get("now").n()); + assertThat(timeOfInvocation3).isNotEqualTo(timeOfInvocation4); // should be different (not idempotent anymore) + + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java new file mode 100644 index 000000000..20bc5394d --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java @@ -0,0 +1,103 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.lambda.powertools.testutils.Infrastructure.FUNCTION_NAME_OUTPUT; +import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; +import static software.amazon.lambda.powertools.testutils.logging.InvocationLogs.Level.INFO; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class LoggingE2ET { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private Infrastructure infrastructure; + private String functionName; + + private void setupInfrastructure(String pathToFunction) { + infrastructure = Infrastructure.builder() + .testName(LoggingE2ET.class.getSimpleName() + "-" + pathToFunction) + .tracing(true) + .pathToFunction(pathToFunction) + .environmentVariables( + Stream.of(new String[][] { + { "POWERTOOLS_LOG_LEVEL", "INFO" }, + { "POWERTOOLS_SERVICE_NAME", LoggingE2ET.class.getSimpleName() } + }) + .collect(Collectors.toMap(data -> data[0], data -> data[1]))) + .build(); + Map<String, String> outputs = infrastructure.deploy(); + functionName = outputs.get(FUNCTION_NAME_OUTPUT); + } + + @AfterEach + void tearDown() { + if (infrastructure != null) { + infrastructure.destroy(); + } + } + + @ParameterizedTest + @ValueSource(strings = { "logging-log4j", "logging-logback", "logging-functional" }) + @Timeout(value = 15, unit = TimeUnit.MINUTES) + void test_logInfoWithAdditionalKeys(String pathToFunction) throws JsonProcessingException { + setupInfrastructure(pathToFunction); + + // GIVEN + String orderId = UUID.randomUUID().toString(); + String event = "{\"message\":\"New Order\", \"keys\":{\"orderId\":\"" + orderId + "\"}}"; + + // WHEN + InvocationResult invocationResult1 = invokeFunction(functionName, event); + InvocationResult invocationResult2 = invokeFunction(functionName, event); + + // THEN + String[] functionLogs = invocationResult1.getLogs().getFunctionLogs(INFO); + assertThat(functionLogs).hasSize(1); + + JsonNode jsonNode = objectMapper.readTree(functionLogs[0]); + assertThat(jsonNode.get("message").asText()).isEqualTo("New Order"); + assertThat(jsonNode.get("orderId").asText()).isEqualTo(orderId); + assertThat(jsonNode.get("cold_start").asBoolean()).isTrue(); + assertThat(jsonNode.get("xray_trace_id").asText()).isNotBlank(); + assertThat(jsonNode.get("function_request_id").asText()).isEqualTo(invocationResult1.getRequestId()); + + // second call should not be cold start + functionLogs = invocationResult2.getLogs().getFunctionLogs(INFO); + assertThat(functionLogs).hasSize(1); + jsonNode = objectMapper.readTree(functionLogs[0]); + assertThat(jsonNode.get("cold_start").asBoolean()).isFalse(); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ET.java new file mode 100644 index 000000000..35f8b5ba3 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ET.java @@ -0,0 +1,129 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.lambda.powertools.testutils.Infrastructure.FUNCTION_NAME_OUTPUT; +import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; + +import java.time.Clock; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import software.amazon.lambda.powertools.testutils.DataNotReadyException; +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.RetryUtils; +import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; +import software.amazon.lambda.powertools.testutils.metrics.MetricsFetcher; + +class MetricsE2ET { + private static final String NAMESPACE = "MetricsE2ENamespace_" + UUID.randomUUID(); + private static final String SERVICE = "MetricsE2EService_" + UUID.randomUUID(); + private static Infrastructure infrastructure; + private static String functionName; + + @BeforeAll + @Timeout(value = 10, unit = TimeUnit.MINUTES) + static void setup() { + infrastructure = Infrastructure.builder() + .testName(MetricsE2ET.class.getSimpleName()) + .pathToFunction("metrics") + .environmentVariables( + Stream.of(new String[][] { + { "POWERTOOLS_METRICS_NAMESPACE", NAMESPACE }, + { "POWERTOOLS_SERVICE_NAME", SERVICE } + }) + .collect(Collectors.toMap(data -> data[0], data -> data[1]))) + .build(); + Map<String, String> outputs = infrastructure.deploy(); + functionName = outputs.get(FUNCTION_NAME_OUTPUT); + } + + @AfterAll + static void tearDown() { + if (infrastructure != null) { + infrastructure.destroy(); + } + } + + @Test + void test_recordMetrics() { + // GIVEN + + Instant currentTimeTruncatedToMinutes = Instant.now(Clock.systemUTC()).truncatedTo(ChronoUnit.MINUTES); + String event1 = "{ \"metrics\": {\"orders\": 1, \"products\": 4}, \"dimensions\": { \"Environment\": \"test\"}, \"highResolution\": \"false\"}"; + + String event2 = "{ \"metrics\": {\"orders\": 1, \"products\": 8}, \"dimensions\": { \"Environment\": \"test\"}, \"highResolution\": \"true\"}"; + // WHEN + InvocationResult invocationResult = invokeFunction(functionName, event1); + + invokeFunction(functionName, event2); + + // THEN + MetricsFetcher metricsFetcher = new MetricsFetcher(); + List<Double> coldStart = metricsFetcher.fetchMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, + NAMESPACE, + "ColdStart", Stream.of(new String[][] { + { "FunctionName", functionName }, + { "Service", SERVICE } }).collect(Collectors.toMap(data -> data[0], data -> data[1]))); + assertThat(coldStart.get(0)).isEqualTo(1); + List<Double> orderMetrics = RetryUtils.withRetry(() -> { + List<Double> metrics = metricsFetcher.fetchMetrics(invocationResult.getStart(), invocationResult.getEnd(), + 60, NAMESPACE, "orders", Collections.singletonMap("Environment", "test")); + if (metrics.get(0) != 2.0) { + throw new DataNotReadyException("Expected 2.0 orders but got " + metrics.get(0)); + } + return metrics; + }, "orderMetricsRetry", DataNotReadyException.class).get(); + assertThat(orderMetrics.get(0)).isEqualTo(2); + List<Double> productMetrics = metricsFetcher.fetchMetrics(invocationResult.getStart(), + invocationResult.getEnd(), 60, NAMESPACE, + "products", Collections.singletonMap("Environment", "test")); + + // When searching across a 1 minute time period with a period of 60 we find both metrics and the sum is 12 + assertThat(productMetrics.get(0)).isEqualTo(12); + + orderMetrics = metricsFetcher.fetchMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, + NAMESPACE, + "orders", Collections.singletonMap("Service", SERVICE)); + assertThat(orderMetrics.get(0)).isEqualTo(2); + productMetrics = metricsFetcher.fetchMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, + NAMESPACE, + "products", Collections.singletonMap("Service", SERVICE)); + assertThat(productMetrics.get(0)).isEqualTo(12); + + Instant searchStartTime = currentTimeTruncatedToMinutes.plusSeconds(15); + Instant searchEndTime = currentTimeTruncatedToMinutes.plusSeconds(45); + + List<Double> productMetricDataResult = metricsFetcher.fetchMetrics(searchStartTime, searchEndTime, 1, NAMESPACE, + "products", Collections.singletonMap("Environment", "test")); + + // We are searching across the time period the metric was created but with a period of 1 second. Only the high + // resolution metric will be available at this point + assertThat(productMetricDataResult.get(0)).isEqualTo(8); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ParametersE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ParametersE2ET.java new file mode 100644 index 000000000..39254a9e6 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ParametersE2ET.java @@ -0,0 +1,96 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.lambda.powertools.testutils.Infrastructure.FUNCTION_NAME_OUTPUT; +import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; + +import software.amazon.lambda.powertools.testutils.AppConfig; +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ParametersE2ET { + private final AppConfig appConfig; + private Infrastructure infrastructure; + private String functionName; + + ParametersE2ET() { + String appName = UUID.randomUUID().toString(); + Map<String, String> params = new HashMap<>(); + params.put("key1", "value1"); + params.put("key2", "value2"); + appConfig = new AppConfig(appName, "e2etest", params); + } + + @BeforeAll + @Timeout(value = 15, unit = TimeUnit.MINUTES) + void setup() { + infrastructure = Infrastructure.builder() + .testName(ParametersE2ET.class.getSimpleName()) + .pathToFunction("parameters") + .appConfig(appConfig) + .environmentVariables( + Stream.of(new String[][] { + { "POWERTOOLS_LOG_LEVEL", "INFO" }, + { "POWERTOOLS_SERVICE_NAME", ParametersE2ET.class.getSimpleName() } + }) + .collect(Collectors.toMap(data -> data[0], data -> data[1]))) + .build(); + Map<String, String> outputs = infrastructure.deploy(); + functionName = outputs.get(FUNCTION_NAME_OUTPUT); + } + + @AfterAll + void tearDown() { + if (infrastructure != null) { + infrastructure.destroy(); + } + } + + @Test + void test_getAppConfigValue() { + for (Map.Entry<String, String> configKey : appConfig.getConfigurationValues().entrySet()) { + + // Arrange + String event1 = "{" + + "\"app\": \"" + appConfig.getApplication() + "\", " + + "\"environment\": \"" + appConfig.getEnvironment() + "\", " + + "\"key\": \"" + configKey.getKey() + "\"" + + "}"; + + // Act + InvocationResult invocationResult = invokeFunction(functionName, event1); + + // Assert + assertThat(invocationResult.getResult()).isEqualTo("\"" + configKey.getValue() + "\""); + } + } + +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ET.java new file mode 100644 index 000000000..13d8adb9b --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ET.java @@ -0,0 +1,115 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static software.amazon.lambda.powertools.testutils.Infrastructure.FUNCTION_NAME_OUTPUT; +import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; +import software.amazon.lambda.powertools.testutils.tracing.SegmentDocument.SubSegment; +import software.amazon.lambda.powertools.testutils.tracing.Trace; +import software.amazon.lambda.powertools.testutils.tracing.TraceFetcher; + +class TracingE2ET { + private static final String SERVICE = "TracingE2EService_" + UUID.randomUUID(); + + private static Infrastructure infrastructure; + private static String functionName; + + @BeforeAll + @Timeout(value = 15, unit = TimeUnit.MINUTES) + static void setup() { + infrastructure = Infrastructure.builder() + .testName(TracingE2ET.class.getSimpleName()) + .pathToFunction("tracing") + .tracing(true) + .environmentVariables( + Map.of("POWERTOOLS_SERVICE_NAME", SERVICE, + "POWERTOOLS_TRACER_CAPTURE_RESPONSE", "true")) + .build(); + Map<String, String> outputs = infrastructure.deploy(); + functionName = outputs.get(FUNCTION_NAME_OUTPUT); + } + + @AfterAll + static void tearDown() { + if (infrastructure != null) { + infrastructure.destroy(); + } + } + + @Test + void test_tracing() { + // GIVEN + final String message = "Hello World"; + final String event = String.format("{\"message\":\"%s\"}", message); + final String result = String.format("%s (%s)", message, functionName); + + // WHEN + final InvocationResult invocationResult = invokeFunction(functionName, event); + + // THEN + final Trace trace = TraceFetcher.builder() + .start(invocationResult.getStart()) + .end(invocationResult.getEnd()) + .functionName(functionName) + .build() + .fetchTrace(); + + assertThat(trace.getSubsegments()).hasSize(1); + + final SubSegment handleRequestSegment = trace.getSubsegments().stream() + .filter(subSegment -> "## handleRequest".equals(subSegment.getName())) + .findFirst().orElse(null); + assertNotNull(handleRequestSegment); + assertThat(handleRequestSegment.getName()).isEqualTo("## handleRequest"); + assertThat(handleRequestSegment.getAnnotations()).hasSize(2); + assertThat(handleRequestSegment.getAnnotations()).containsEntry("ColdStart", true); + assertThat(handleRequestSegment.getAnnotations()).containsEntry("Service", SERVICE); + assertThat(handleRequestSegment.getMetadata()).hasSize(1); + final Map<String, Object> metadata = (Map<String, Object>) handleRequestSegment.getMetadata().get(SERVICE); + assertThat(metadata).containsEntry("handleRequest response", result); + assertThat(handleRequestSegment.getSubsegments()).hasSize(2); + + SubSegment sub = handleRequestSegment.getSubsegments().get(0); + assertThat(sub.getName()).isIn("## internal_stuff", "## buildMessage"); + + sub = handleRequestSegment.getSubsegments().get(1); + assertThat(sub.getName()).isIn("## internal_stuff", "## buildMessage"); + + SubSegment buildMessage = handleRequestSegment.getSubsegments().stream() + .filter(subSegment -> "## buildMessage".equals(subSegment.getName())) + .findFirst().orElse(null); + assertThat(buildMessage).isNotNull(); + assertThat(buildMessage.getAnnotations()).hasSize(1); + assertThat(buildMessage.getAnnotations()).containsEntry("message", message); + assertThat(buildMessage.getMetadata()).hasSize(1); + final Map<String, Object> buildMessageSegmentMetadata = (Map<String, Object>) buildMessage.getMetadata() + .get(SERVICE); + assertThat(buildMessageSegmentMetadata).containsEntry("buildMessage response", result); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationALBE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationALBE2ET.java new file mode 100644 index 000000000..bf90851cb --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationALBE2ET.java @@ -0,0 +1,107 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.lambda.powertools.testutils.Infrastructure.FUNCTION_NAME_OUTPUT; +import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; + +class ValidationALBE2ET { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private static Infrastructure infrastructure; + private static String functionName; + + @BeforeAll + @Timeout(value = 5, unit = TimeUnit.MINUTES) + static void setup() { + infrastructure = Infrastructure.builder().testName(ValidationALBE2ET.class.getSimpleName()) + .pathToFunction("validation-alb-event").build(); + Map<String, String> outputs = infrastructure.deploy(); + functionName = outputs.get(FUNCTION_NAME_OUTPUT); + } + + @AfterAll + static void tearDown() { + if (infrastructure != null) { + infrastructure.destroy(); + } + } + + @Test + void test_validInboundSQSEvent() throws IOException { + try (InputStream is = this.getClass().getResourceAsStream("/validation/valid_alb_in_out_event.json")) { + String validEvent = IOUtils.toString(is, StandardCharsets.UTF_8); + // WHEN + InvocationResult invocationResult = invokeFunction(functionName, validEvent); + + // THEN + // invocation should pass validation and return 200 + JsonNode validJsonNode = objectMapper.readTree(invocationResult.getResult()); + assertThat(validJsonNode.get("statusCode").asInt()).isEqualTo(200); + assertThat(validJsonNode.get("body").asText()).isEqualTo("{\"price\": 150}"); + } + } + + @Test + void test_invalidInboundSQSEvent() throws IOException { + try (InputStream is = this.getClass().getResourceAsStream("/validation/invalid_alb_in_event.json")) { + String invalidEvent = IOUtils.toString(is, StandardCharsets.UTF_8); + + // WHEN + InvocationResult invocationResult = invokeFunction(functionName, invalidEvent); + + // THEN + // invocation should fail inbound validation and return an error message + JsonNode validJsonNode = objectMapper.readTree(invocationResult.getResult()); + assertThat(validJsonNode.get("errorMessage").asText()).contains(": required property 'price' not found"); + } + } + + @Test + void test_invalidOutboundSQSEvent() throws IOException { + try (InputStream is = this.getClass().getResourceAsStream("/validation/invalid_alb_out_event.json")) { + String invalidEvent = IOUtils.toString(is, StandardCharsets.UTF_8); + + // WHEN + InvocationResult invocationResult = invokeFunction(functionName, invalidEvent); + + // THEN + // invocation should fail outbound validation and return 400 + JsonNode validJsonNode = objectMapper.readTree(invocationResult.getResult()); + assertThat(validJsonNode.get("errorMessage").asText()) + .contains("/price: must have an exclusive maximum value of 1000"); + } + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationApiGWE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationApiGWE2ET.java new file mode 100644 index 000000000..2d1bf4657 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationApiGWE2ET.java @@ -0,0 +1,110 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.lambda.powertools.testutils.Infrastructure.FUNCTION_NAME_OUTPUT; +import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; + +class ValidationApiGWE2ET { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private static Infrastructure infrastructure; + private static String functionName; + + @BeforeAll + @Timeout(value = 5, unit = TimeUnit.MINUTES) + static void setup() { + infrastructure = Infrastructure.builder().testName(ValidationApiGWE2ET.class.getSimpleName()) + .pathToFunction("validation-apigw-event").build(); + Map<String, String> outputs = infrastructure.deploy(); + functionName = outputs.get(FUNCTION_NAME_OUTPUT); + } + + @AfterAll + static void tearDown() { + if (infrastructure != null) { + infrastructure.destroy(); + } + } + + @Test + void test_validInboundApiGWEvent() throws IOException { + try (InputStream is = this.getClass().getResourceAsStream("/validation/valid_api_gw_in_out_event.json")) { + String validEvent = IOUtils.toString(is, StandardCharsets.UTF_8); + + // WHEN + InvocationResult invocationResult = invokeFunction(functionName, validEvent); + + // THEN + // invocation should pass validation and return 200 + JsonNode validJsonNode = objectMapper.readTree(invocationResult.getResult()); + assertThat(validJsonNode.get("statusCode").asInt()).isEqualTo(200); + assertThat(validJsonNode.get("body").asText()).isEqualTo("{\"price\": 150}"); + } + } + + @Test + void test_invalidInboundApiGWEvent() throws IOException { + try (InputStream is = this.getClass().getResourceAsStream("/validation/invalid_api_gw_in_event.json")) { + String invalidEvent = IOUtils.toString(is, StandardCharsets.UTF_8); + + // WHEN + InvocationResult invocationResult = invokeFunction(functionName, invalidEvent); + + // THEN + // invocation should fail inbound validation and return 400 + JsonNode validJsonNode = objectMapper.readTree(invocationResult.getResult()); + assertThat(validJsonNode.get("statusCode").asInt()).isEqualTo(400); + assertThat(validJsonNode.get("body").asText()).contains(": required property 'price' not found"); + } + } + + @Test + void test_invalidOutboundApiGWEvent() throws IOException { + try (InputStream is = this.getClass().getResourceAsStream("/validation/invalid_api_gw_out_event.json")) { + String invalidEvent = IOUtils.toString(is, StandardCharsets.UTF_8); + + // WHEN + InvocationResult invocationResult = invokeFunction(functionName, invalidEvent); + + // THEN + // invocation should fail outbound validation and return 400 + JsonNode validJsonNode = objectMapper.readTree(invocationResult.getResult()); + assertThat(validJsonNode.get("statusCode").asInt()).isEqualTo(400); + assertThat(validJsonNode.get("body").asText()) + .contains("/price: must have an exclusive maximum value of 1000"); + } + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/AppConfig.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/AppConfig.java new file mode 100644 index 000000000..4229af040 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/AppConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils; + +import java.util.Map; + +/** + * Defines configuration used to setup an AppConfig + * deployment when the infrastructure is rolled out. + * <p> + * All fields are non-nullable. + */ +public class AppConfig { + private String application; + private String environment; + private Map<String, String> configurationValues; + + public AppConfig(String application, String environment, Map<String, String> configurationValues) { + this.application = application; + this.environment = environment; + this.configurationValues = configurationValues; + } + + public String getApplication() { + return application; + } + + public String getEnvironment() { + return environment; + } + + public Map<String, String> getConfigurationValues() { + return configurationValues; + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/DataNotReadyException.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/DataNotReadyException.java new file mode 100644 index 000000000..0c16bb68b --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/DataNotReadyException.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils; + +/** + * Exception thrown when test data is not ready yet. + * This exception is used to trigger retries in tests waiting for async operations. + */ +public class DataNotReadyException extends RuntimeException { + public DataNotReadyException(String message) { + super(message); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/DockerConfiguration.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/DockerConfiguration.java new file mode 100644 index 000000000..0508534c2 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/DockerConfiguration.java @@ -0,0 +1,182 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils; + +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import software.amazon.awscdk.BundlingOptions; +import software.amazon.awscdk.BundlingOutput; +import software.amazon.awscdk.DockerImage; +import software.amazon.awscdk.DockerVolume; + +/** + * Configuration class for managing build environments and Docker settings + * used during Lambda function compilation. + */ +public class DockerConfiguration { + private final String baseImage; + private final List<String> buildArgs; + private final Map<String, String> environmentVariables; + private final List<DockerVolume> volumes; + + private DockerConfiguration(Builder builder) { + this.baseImage = builder.baseImage; + this.buildArgs = builder.buildArgs; + this.environmentVariables = builder.environmentVariables; + this.volumes = builder.volumes; + } + + public static Builder builder() { + return new Builder(); + } + + public String getBaseImage() { + return baseImage; + } + + public List<String> getBuildArgs() { + return buildArgs; + } + + public Map<String, String> getEnvironmentVariables() { + return environmentVariables; + } + + public List<DockerVolume> getVolumes() { + return volumes; + } + + /** + * Creates bundling options for GraalVM native image compilation. + */ + public BundlingOptions createGraalVMBundlingOptions(String pathToFunction, JavaRuntime runtime) { + List<String> packagingInstruction = Arrays.asList( + "/bin/sh", + "-c", + "cd " + pathToFunction + + " && timeout -s SIGKILL 10m mvn clean package -Pnative-image -ff" + + " -Dmaven.test.skip=true" + + " -Dmaven.compiler.source=" + runtime.getMvnProperty() + + " -Dmaven.compiler.target=" + runtime.getMvnProperty() + + " && mkdir -p /tmp/lambda-package" + + " && cp /asset-input/" + pathToFunction + "/target/handler /tmp/lambda-package/" + + " && chmod +x /tmp/lambda-package/handler" + + " && echo '#!/bin/bash\nset -e\n./handler $_HANDLER' > /tmp/lambda-package/bootstrap" + + " && chmod +x /tmp/lambda-package/bootstrap" + + " && cd /tmp/lambda-package" + + " && zip -r /asset-output/function.zip ."); + + return BundlingOptions.builder() + .command(packagingInstruction) + .image(DockerImage.fromRegistry(baseImage)) + .volumes(volumes) + .environment(environmentVariables) + .user("root") + .outputType(BundlingOutput.ARCHIVED) + .platform("linux/amd64") + .build(); + } + + /** + * Creates bundling options for standard JVM compilation. + */ + public BundlingOptions createJVMBundlingOptions(String pathToFunction, JavaRuntime runtime) { + List<String> packagingInstruction = Arrays.asList( + "/bin/sh", + "-c", + "cd " + pathToFunction + + " && timeout -s SIGKILL 5m mvn clean install -ff" + + " -Dmaven.test.skip=true" + + " -Dmaven.compiler.source=" + runtime.getMvnProperty() + + " -Dmaven.compiler.target=" + runtime.getMvnProperty() + + " && cp /asset-input/" + pathToFunction + "/target/function.jar /asset-output/"); + + return BundlingOptions.builder() + .command(packagingInstruction) + .image(DockerImage.fromRegistry(baseImage)) + .volumes(volumes) + .user("root") + .outputType(BundlingOutput.ARCHIVED) + .platform("linux/amd64") + .build(); + } + + /** + * Creates a default Docker configuration for GraalVM native image compilation. + */ + public static DockerConfiguration createGraalVMDefault(JavaRuntime runtime) { + // Use custom Dockerfile for GraalVM + String dockerDir = Paths.get(System.getProperty("user.dir"), "src", "test", "resources", "docker").toString(); + DockerImage customImage = DockerImage.fromBuild(dockerDir); + + return builder() + .baseImage(customImage.getImage()) + .environmentVariables(Map.of("JAVA_VERSION", runtime.getMvnProperty())) + .volumes(List.of( + DockerVolume.builder() + .hostPath(System.getProperty("user.home") + "/.m2/") + .containerPath("/root/.m2/") + .build())) + .build(); + } + + /** + * Creates a default Docker configuration for standard JVM compilation. + */ + public static DockerConfiguration createJVMDefault(JavaRuntime runtime) { + return builder() + .baseImage(runtime.getCdkRuntime().getBundlingImage().getImage()) + .volumes(List.of( + DockerVolume.builder() + .hostPath(System.getProperty("user.home") + "/.m2/") + .containerPath("/root/.m2/") + .build())) + .build(); + } + + public static class Builder { + private String baseImage; + private List<String> buildArgs; + private Map<String, String> environmentVariables; + private List<DockerVolume> volumes; + + public Builder baseImage(String baseImage) { + this.baseImage = baseImage; + return this; + } + + public Builder buildArgs(List<String> buildArgs) { + this.buildArgs = buildArgs; + return this; + } + + public Builder environmentVariables(Map<String, String> environmentVariables) { + this.environmentVariables = environmentVariables; + return this; + } + + public Builder volumes(List<DockerVolume> volumes) { + this.volumes = volumes; + return this; + } + + public DockerConfiguration build() { + return new DockerConfiguration(this); + } + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java new file mode 100644 index 000000000..ae96943c2 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java @@ -0,0 +1,620 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils; + +import static java.util.Collections.singletonList; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; + +import com.fasterxml.jackson.databind.JsonNode; + +import software.amazon.awscdk.App; +import software.amazon.awscdk.BundlingOptions; +import software.amazon.awscdk.CfnOutput; +import software.amazon.awscdk.DefaultStackSynthesizer; +import software.amazon.awscdk.Duration; +import software.amazon.awscdk.RemovalPolicy; +import software.amazon.awscdk.Stack; +import software.amazon.awscdk.cxapi.CloudAssembly; +import software.amazon.awscdk.services.appconfig.CfnApplication; +import software.amazon.awscdk.services.appconfig.CfnConfigurationProfile; +import software.amazon.awscdk.services.appconfig.CfnDeployment; +import software.amazon.awscdk.services.appconfig.CfnDeploymentStrategy; +import software.amazon.awscdk.services.appconfig.CfnEnvironment; +import software.amazon.awscdk.services.appconfig.CfnHostedConfigurationVersion; +import software.amazon.awscdk.services.dynamodb.Attribute; +import software.amazon.awscdk.services.dynamodb.AttributeType; +import software.amazon.awscdk.services.dynamodb.BillingMode; +import software.amazon.awscdk.services.dynamodb.StreamViewType; +import software.amazon.awscdk.services.dynamodb.Table; +import software.amazon.awscdk.services.iam.PolicyStatement; +import software.amazon.awscdk.services.kinesis.Stream; +import software.amazon.awscdk.services.kinesis.StreamMode; +import software.amazon.awscdk.services.lambda.Code; +import software.amazon.awscdk.services.lambda.Function; +import software.amazon.awscdk.services.lambda.Runtime; +import software.amazon.awscdk.services.lambda.StartingPosition; +import software.amazon.awscdk.services.lambda.Tracing; +import software.amazon.awscdk.services.lambda.eventsources.DynamoEventSource; +import software.amazon.awscdk.services.lambda.eventsources.KinesisEventSource; +import software.amazon.awscdk.services.lambda.eventsources.SqsEventSource; +import software.amazon.awscdk.services.logs.LogGroup; +import software.amazon.awscdk.services.logs.RetentionDays; +import software.amazon.awscdk.services.s3.Bucket; +import software.amazon.awscdk.services.s3.LifecycleRule; +import software.amazon.awscdk.services.s3.assets.AssetOptions; +import software.amazon.awscdk.services.sqs.DeadLetterQueue; +import software.amazon.awscdk.services.sqs.Queue; +import software.amazon.awssdk.core.waiters.WaiterResponse; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.cloudformation.CloudFormationClient; +import software.amazon.awssdk.services.cloudformation.model.Capability; +import software.amazon.awssdk.services.cloudformation.model.CreateStackRequest; +import software.amazon.awssdk.services.cloudformation.model.DeleteStackRequest; +import software.amazon.awssdk.services.cloudformation.model.DescribeStacksRequest; +import software.amazon.awssdk.services.cloudformation.model.DescribeStacksResponse; +import software.amazon.awssdk.services.cloudformation.model.OnFailure; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; +import software.amazon.awssdk.services.s3.model.ListObjectsV2Response; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.sts.StsClient; +import software.amazon.awssdk.utils.StringUtils; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +/** + * This class is in charge of bootstrapping the infrastructure for the tests. + * <br/> + * Tests are actually run on AWS, so we need to provision Lambda functions, DynamoDB table (for Idempotency), + * CloudWatch log groups, ... + * <br/> + * It uses the Cloud Development Kit (CDK) to define required resources. The CDK stack is then synthesized to retrieve + * the CloudFormation templates and the assets (function jars). Assets are uploaded to S3 (with the SDK + * `PutObjectRequest`) + * and the CloudFormation stack is created (with the SDK `createStack`) + */ +public final class Infrastructure { + public static final String FUNCTION_NAME_OUTPUT = "functionName"; + private static final Logger LOG = LoggerFactory.getLogger(Infrastructure.class); + + private final String stackName; + private final boolean tracing; + private final Map<String, String> envVar; + private final JavaRuntime runtime; + private final App app; + private final Stack stack; + private final long timeout; + private final String pathToFunction; + private final S3Client s3; + private final CloudFormationClient cfn; + private final Region region; + private final String account; + private final String idempotencyTable; + private final AppConfig appConfig; + private final SdkHttpClient httpClient; + private final String queue; + private final String kinesisStream; + private final String largeMessagesBucket; + private String ddbStreamsTableName; + private Object cfnTemplate; + private String cfnAssetDirectory; + + private Infrastructure(Builder builder) { + this.stackName = builder.stackName; + this.tracing = builder.tracing; + this.envVar = builder.environmentVariables; + this.runtime = builder.runtime; + this.timeout = builder.timeoutInSeconds; + this.pathToFunction = builder.pathToFunction; + this.idempotencyTable = builder.idemPotencyTable; + this.appConfig = builder.appConfig; + this.queue = builder.queue; + this.kinesisStream = builder.kinesisStream; + this.largeMessagesBucket = builder.largeMessagesBucket; + this.ddbStreamsTableName = builder.ddbStreamsTableName; + + this.app = new App(); + this.stack = createStackWithLambda(); + + this.synthesize(); + + this.httpClient = UrlConnectionHttpClient.builder().build(); + this.region = Region.of(System.getProperty("AWS_DEFAULT_REGION", "eu-west-1")); + this.account = StsClient.builder() + .httpClient(httpClient) + .region(region) + .build().getCallerIdentity().account(); + + s3 = S3Client.builder() + .httpClient(httpClient) + .region(region) + .build(); + cfn = CloudFormationClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + } + + public static Builder builder() { + return new Builder(); + } + + /** + * Use the CloudFormation SDK to create the stack + * + * @return the name of the function deployed part of the stack + */ + public Map<String, String> deploy() { + uploadAssets(); + LOG.info("Deploying '" + stackName + "' on account " + account); + cfn.createStack(CreateStackRequest.builder() + .stackName(stackName) + .templateBody(new Yaml().dump(cfnTemplate)) + .timeoutInMinutes(10) + .onFailure(OnFailure.ROLLBACK) + .capabilities(Capability.CAPABILITY_IAM) + .build()); + WaiterResponse<DescribeStacksResponse> waiterResponse = cfn.waiter() + .waitUntilStackCreateComplete(DescribeStacksRequest.builder().stackName(stackName).build()); + if (waiterResponse.matched().response().isPresent()) { + software.amazon.awssdk.services.cloudformation.model.Stack deployedStack = waiterResponse.matched() + .response().get().stacks().get(0); + LOG.info("Stack " + deployedStack.stackName() + " successfully deployed"); + Map<String, String> outputs = new HashMap<>(); + deployedStack.outputs().forEach(output -> outputs.put(output.outputKey(), output.outputValue())); + return outputs; + } else { + throw new RuntimeException("Failed to create stack"); + } + } + + /** + * Destroy the CloudFormation stack + */ + public void destroy() { + LOG.info("Deleting '" + stackName + "' on account " + account); + cfn.deleteStack(DeleteStackRequest.builder().stackName(stackName).build()); + } + + /** + * Build the CDK Stack containing the required resources (Lambda function, LogGroup, DDB Table) + * + * @return the CDK stack + */ + private Stack createStackWithLambda() { + boolean createTableForAsyncTests = false; + final Stack e2eStack = Stack.Builder.create(app, stackName) + .synthesizer(DefaultStackSynthesizer.Builder.create() + .generateBootstrapVersionRule(false) // Disable bootstrap version check + .build()) + .build(); + + boolean isGraalVMEnabled = Boolean.parseBoolean(System.getProperty("graalvm.enabled", "false")); + DockerConfiguration dockerConfig = isGraalVMEnabled + ? DockerConfiguration.createGraalVMDefault(runtime) + : DockerConfiguration.createJVMDefault(runtime); + + BundlingOptions bundlingOptions = isGraalVMEnabled + ? dockerConfig.createGraalVMBundlingOptions(pathToFunction, runtime) + : dockerConfig.createJVMBundlingOptions(pathToFunction, runtime); + + String functionName = stackName + "-function"; + CfnOutput.Builder.create(e2eStack, FUNCTION_NAME_OUTPUT) + .value(functionName) + .build(); + + LOG.debug("Building Lambda function with {} configuration", isGraalVMEnabled ? "GraalVM" : "JVM"); + Function function = Function.Builder + .create(e2eStack, functionName) + .code(Code.fromAsset("handlers/", AssetOptions.builder() + .bundling(bundlingOptions) + .build())) + .functionName(functionName) + .handler("software.amazon.lambda.powertools.e2e.Function::handleRequest") + .memorySize(1024) + .timeout(Duration.seconds(timeout)) + .runtime(isGraalVMEnabled ? Runtime.PROVIDED_AL2023 : runtime.getCdkRuntime()) + .environment(envVar) + .tracing(tracing ? Tracing.ACTIVE : Tracing.DISABLED) + .build(); + + LogGroup.Builder + .create(e2eStack, functionName + "-logs") + .logGroupName("/aws/lambda/" + functionName) + .retention(RetentionDays.ONE_DAY) + .removalPolicy(RemovalPolicy.RETAIN) + .build(); + + if (!StringUtils.isEmpty(idempotencyTable)) { + Table table = Table.Builder + .create(e2eStack, "IdempotencyTable") + .billingMode(BillingMode.PAY_PER_REQUEST) + .removalPolicy(RemovalPolicy.DESTROY) + .partitionKey(Attribute.builder().name("id").type(AttributeType.STRING).build()) + .tableName(idempotencyTable) + .timeToLiveAttribute("expiration") + .build(); + function.addEnvironment("IDEMPOTENCY_TABLE", idempotencyTable); + + table.grantReadWriteData(function); + } + + if (!StringUtils.isEmpty(queue)) { + Queue sqsQueue = Queue.Builder + .create(e2eStack, "SQSQueue") + .queueName(queue) + .visibilityTimeout(Duration.seconds(timeout * 6)) + .retentionPeriod(Duration.seconds(timeout * 6)) + .removalPolicy(RemovalPolicy.DESTROY) + .build(); + DeadLetterQueue.builder() + .queue(sqsQueue) + .maxReceiveCount(1) // do not retry in case of error + .build(); + sqsQueue.grantConsumeMessages(function); + SqsEventSource sqsEventSource = SqsEventSource.Builder + .create(sqsQueue) + .enabled(true) + .reportBatchItemFailures(true) + .batchSize(1) + .build(); + function.addEventSource(sqsEventSource); + CfnOutput.Builder + .create(e2eStack, "QueueURL") + .value(sqsQueue.getQueueUrl()) + .build(); + createTableForAsyncTests = true; + } + if (!StringUtils.isEmpty(kinesisStream)) { + Stream stream = Stream.Builder + .create(e2eStack, "KinesisStream") + .streamMode(StreamMode.ON_DEMAND) + .streamName(kinesisStream) + .removalPolicy(RemovalPolicy.DESTROY) + .build(); + + stream.grantRead(function); + KinesisEventSource kinesisEventSource = KinesisEventSource.Builder + .create(stream) + .enabled(true) + .batchSize(3) + .reportBatchItemFailures(true) + .startingPosition(StartingPosition.TRIM_HORIZON) + .maxBatchingWindow(Duration.seconds(1)) + .build(); + function.addEventSource(kinesisEventSource); + CfnOutput.Builder + .create(e2eStack, "KinesisStreamName") + .value(stream.getStreamName()) + .build(); + } + + if (!StringUtils.isEmpty(ddbStreamsTableName)) { + Table ddbStreamsTable = Table.Builder.create(e2eStack, "DDBStreamsTable") + .tableName(ddbStreamsTableName) + .stream(StreamViewType.KEYS_ONLY) + .removalPolicy(RemovalPolicy.DESTROY) + .partitionKey(Attribute.builder().name("id").type(AttributeType.STRING).build()) + .build(); + + DynamoEventSource ddbEventSource = DynamoEventSource.Builder.create(ddbStreamsTable) + .batchSize(1) + .startingPosition(StartingPosition.TRIM_HORIZON) + .maxBatchingWindow(Duration.seconds(1)) + .reportBatchItemFailures(true) + .build(); + function.addEventSource(ddbEventSource); + CfnOutput.Builder.create(e2eStack, "DdbStreamsTestTable").value(ddbStreamsTable.getTableName()).build(); + } + + if (!StringUtils.isEmpty(largeMessagesBucket)) { + Bucket offloadBucket = Bucket.Builder + .create(e2eStack, "LargeMessagesOffloadBucket") + .removalPolicy(RemovalPolicy.RETAIN) // autodelete does not work without cdk deploy + .bucketName(largeMessagesBucket) + .build(); + // instead of autodelete, have a lifecycle rule to delete files after a day + LifecycleRule.builder().expiration(Duration.days(1)).enabled(true).build(); + offloadBucket.grantReadWrite(function); + } + + if (appConfig != null) { + CfnApplication app = CfnApplication.Builder + .create(e2eStack, "AppConfigApp") + .name(appConfig.getApplication()) + .build(); + + CfnEnvironment environment = CfnEnvironment.Builder + .create(e2eStack, "AppConfigEnvironment") + .applicationId(app.getRef()) + .name(appConfig.getEnvironment()) + .build(); + + // Create a fast deployment strategy, so we don't have to wait ages + CfnDeploymentStrategy fastDeployment = CfnDeploymentStrategy.Builder + .create(e2eStack, "AppConfigDeployment") + .name("FastDeploymentStrategy") + .deploymentDurationInMinutes(0) + .finalBakeTimeInMinutes(0) + .growthFactor(100) + .replicateTo("NONE") + .build(); + + // Get the lambda permission to use AppConfig + function.addToRolePolicy(PolicyStatement.Builder + .create() + .actions(singletonList("appconfig:*")) + .resources(singletonList("*")) + .build()); + + CfnDeployment previousDeployment = null; + for (Map.Entry<String, String> entry : appConfig.getConfigurationValues().entrySet()) { + CfnConfigurationProfile configProfile = CfnConfigurationProfile.Builder + .create(e2eStack, "AppConfigProfileFor" + entry.getKey()) + .applicationId(app.getRef()) + .locationUri("hosted") + .name(entry.getKey()) + .build(); + + CfnHostedConfigurationVersion configVersion = CfnHostedConfigurationVersion.Builder + .create(e2eStack, "AppConfigHostedVersionFor" + entry.getKey()) + .applicationId(app.getRef()) + .contentType("text/plain") + .configurationProfileId(configProfile.getRef()) + .content(entry.getValue()) + .build(); + + CfnDeployment deployment = CfnDeployment.Builder + .create(e2eStack, "AppConfigDepoymentFor" + entry.getKey()) + .applicationId(app.getRef()) + .environmentId(environment.getRef()) + .deploymentStrategyId(fastDeployment.getRef()) + .configurationProfileId(configProfile.getRef()) + .configurationVersion(configVersion.getRef()) + .build(); + + // We need to chain the deployments to keep CFN happy + if (previousDeployment != null) { + deployment.addDependency(previousDeployment); + } + previousDeployment = deployment; + } + } + if (createTableForAsyncTests) { + Table table = Table.Builder + .create(e2eStack, "TableForAsyncTests") + .billingMode(BillingMode.PAY_PER_REQUEST) + .removalPolicy(RemovalPolicy.DESTROY) + .partitionKey(Attribute.builder().name("functionName").type(AttributeType.STRING).build()) + .sortKey(Attribute.builder().name("id").type(AttributeType.STRING).build()) + .build(); + + table.grantReadWriteData(function); + function.addEnvironment("TABLE_FOR_ASYNC_TESTS", table.getTableName()); + CfnOutput.Builder.create(e2eStack, "TableNameForAsyncTests").value(table.getTableName()).build(); + } + + return e2eStack; + } + + /** + * cdk synth to retrieve the CloudFormation template and assets directory + */ + private void synthesize() { + CloudAssembly synth = app.synth(); + cfnTemplate = synth.getStackByName(stack.getStackName()).getTemplate(); + cfnAssetDirectory = synth.getDirectory(); + } + + /** + * Upload assets (mainly lambda function jars) to S3 + */ + private void uploadAssets() { + Map<String, Asset> assets = findAssets(); + assets.forEach((objectKey, asset) -> { + // .zip will be used for GraalVM bundles. + if (!asset.assetPath.endsWith(".jar") && !asset.assetPath.endsWith(".zip")) { + LOG.info("Skipping upload of {}", asset); + return; + } + LOG.info("Uploading {}", asset); + + ListObjectsV2Response objects = s3 + .listObjectsV2(ListObjectsV2Request.builder().bucket(asset.bucketName).build()); + if (objects.contents().stream().anyMatch(o -> o.key().equals(objectKey))) { + LOG.debug("{} already exists, skipping", asset); + return; + } + s3.putObject(PutObjectRequest.builder().bucket(asset.bucketName).key(objectKey).build(), + Paths.get(cfnAssetDirectory, asset.assetPath)); + }); + } + + /** + * Reading the cdk assets.json file to retrieve the list of assets to push to S3 + * + * @return a map of assets + */ + private Map<String, Asset> findAssets() { + Map<String, Asset> assets = new HashMap<>(); + try { + JsonNode jsonNode = JsonConfig.get().getObjectMapper() + .readTree(new File(cfnAssetDirectory, stackName + ".assets.json")); + JsonNode files = jsonNode.get("files"); + files.iterator().forEachRemaining(file -> { + String assetPath = file.get("source").get("path").asText(); + String assetPackaging = file.get("source").get("packaging").asText(); + JsonNode destinations = file.get("destinations"); + String bucketName = null; + String objectKey = null; + Iterator<String> fieldNames = destinations.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + if (fieldName.startsWith("current_account-current_region")) { + bucketName = destinations.get(fieldName).get("bucketName").asText(); + objectKey = destinations.get(fieldName).get("objectKey").asText(); + break; + } + } + Asset asset = new Asset(assetPath, assetPackaging, bucketName.replace("${AWS::AccountId}", account) + .replace("${AWS::Region}", region.toString())); + assets.put(objectKey, asset); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + return assets; + } + + public static final class Builder { + public long timeoutInSeconds = 30; + public String pathToFunction; + public String testName; + public AppConfig appConfig; + private String largeMessagesBucket; + private String stackName; + private boolean tracing = false; + private JavaRuntime runtime; + private Map<String, String> environmentVariables = new HashMap<>(); + private String idemPotencyTable; + private String queue; + private String kinesisStream; + private String ddbStreamsTableName; + + private Builder() { + runtime = mapRuntimeVersion("JAVA_VERSION"); + } + + private JavaRuntime mapRuntimeVersion(String environmentVariableName) { + String javaVersion = System.getenv(environmentVariableName); // must be set in GitHub actions + JavaRuntime ret = null; + if (javaVersion == null) { + throw new IllegalArgumentException(environmentVariableName + " is not set"); + } + if (javaVersion.startsWith("11")) { + ret = JavaRuntime.JAVA11; + } else if (javaVersion.startsWith("17")) { + ret = JavaRuntime.JAVA17; + } else if (javaVersion.startsWith("21")) { + ret = JavaRuntime.JAVA21; + } else if (javaVersion.startsWith("25")) { + ret = JavaRuntime.JAVA25; + } else { + throw new IllegalArgumentException("Unsupported Java version " + javaVersion); + } + LOG.debug("Java Version set to {}, using runtime variable {}", ret, javaVersion); + return ret; + } + + public Infrastructure build() { + Objects.requireNonNull(testName, "testName must not be null"); + + String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 12); + stackName = testName + "-" + uuid; + + Objects.requireNonNull(pathToFunction, "pathToFunction must not be null"); + return new Infrastructure(this); + } + + public Builder testName(String testName) { + this.testName = testName; + return this; + } + + public Builder pathToFunction(String pathToFunction) { + this.pathToFunction = pathToFunction; + return this; + } + + public Builder tracing(boolean tracing) { + this.tracing = tracing; + return this; + } + + public Builder idempotencyTable(String tableName) { + this.idemPotencyTable = tableName; + return this; + } + + public Builder appConfig(AppConfig app) { + this.appConfig = app; + return this; + } + + public Builder environmentVariables(Map<String, String> environmentVariables) { + this.environmentVariables = environmentVariables; + return this; + } + + public Builder timeoutInSeconds(long timeoutInSeconds) { + this.timeoutInSeconds = timeoutInSeconds; + return this; + } + + public Builder queue(String queue) { + this.queue = queue; + return this; + } + + public Builder kinesisStream(String stream) { + this.kinesisStream = stream; + return this; + } + + public Builder ddbStreamsTableName(String tableName) { + this.ddbStreamsTableName = tableName; + return this; + } + + public Builder largeMessagesBucket(String largeMessagesBucket) { + this.largeMessagesBucket = largeMessagesBucket; + return this; + } + } + + private static class Asset { + private final String assetPath; + private final String assetPackaging; + private final String bucketName; + + Asset(String assetPath, String assetPackaging, String bucketName) { + this.assetPath = assetPath; + this.assetPackaging = assetPackaging; + this.bucketName = bucketName; + } + + @Override + public String toString() { + return "Asset{" + + "assetPath='" + assetPath + '\'' + + ", assetPackaging='" + assetPackaging + '\'' + + ", bucketName='" + bucketName + '\'' + + '}'; + } + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/JavaRuntime.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/JavaRuntime.java new file mode 100644 index 000000000..625a222aa --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/JavaRuntime.java @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils; + +import software.amazon.awscdk.services.lambda.Runtime; + +public enum JavaRuntime { + JAVA11("java11", Runtime.JAVA_11, "11"), + JAVA17("java17", Runtime.JAVA_17, "17"), + JAVA21("java21", Runtime.JAVA_21, "21"), + JAVA25("java25", Runtime.JAVA_25, "25"); + + private final String runtime; + private final Runtime cdkRuntime; + + private final String mvnProperty; + + JavaRuntime(String runtime, Runtime cdkRuntime, String mvnProperty) { + this.runtime = runtime; + this.cdkRuntime = cdkRuntime; + this.mvnProperty = mvnProperty; + } + + public Runtime getCdkRuntime() { + return cdkRuntime; + } + + public String getRuntime() { + return runtime; + } + + @Override + public String toString() { + return runtime; + } + + public String getMvnProperty() { + return mvnProperty; + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/RetryUtils.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/RetryUtils.java new file mode 100644 index 000000000..ce64f04ea --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/RetryUtils.java @@ -0,0 +1,105 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils; + +import java.time.Duration; +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.github.resilience4j.retry.Retry; +import io.github.resilience4j.retry.RetryConfig; + +/** + * Utility class for consistent retry configuration across all test utilities. + */ +public final class RetryUtils { + private static final Logger LOG = LoggerFactory.getLogger(RetryUtils.class); + + private static final RetryConfig DEFAULT_RETRY_CONFIG = RetryConfig.custom() + .maxAttempts(60) // 60 attempts over 5 minutes + .waitDuration(Duration.ofSeconds(5)) // 5 seconds between attempts + .build(); + + private RetryUtils() { + // Utility class + } + + /** + * Creates a retry instance with default configuration for the specified throwable types. + * + * @param name the name for the retry instance + * @param retryOnThrowables the throwable classes to retry on + * @return configured Retry instance + */ + @SafeVarargs + public static Retry createRetry(String name, Class<? extends Throwable>... retryOnThrowables) { + return createRetry(name, DEFAULT_RETRY_CONFIG, retryOnThrowables); + } + + /** + * Creates a retry instance with custom configuration for the specified throwable types. + * + * @param name the name for the retry instance + * @param customConfig the custom retry configuration + * @param retryOnThrowables the throwable classes to retry on + * @return configured Retry instance + */ + @SafeVarargs + public static Retry createRetry(String name, RetryConfig customConfig, + Class<? extends Throwable>... retryOnThrowables) { + RetryConfig config = RetryConfig.from(customConfig) + .retryExceptions(retryOnThrowables) + .build(); + + Retry retry = Retry.of(name, config); + retry.getEventPublisher().onRetry(event -> LOG.warn("Retry attempt {} for {}: {}", + event.getNumberOfRetryAttempts(), name, event.getLastThrowable().getMessage())); + + return retry; + } + + /** + * Decorates a supplier with retry logic for the specified throwable types. + * + * @param supplier the supplier to decorate + * @param name the name for the retry instance + * @param retryOnThrowables the throwable classes to retry on + * @return decorated supplier with retry logic + */ + @SafeVarargs + public static <T> Supplier<T> withRetry(Supplier<T> supplier, String name, + Class<? extends Throwable>... retryOnThrowables) { + Retry retry = createRetry(name, retryOnThrowables); + return Retry.decorateSupplier(retry, supplier); + } + + /** + * Decorates a supplier with custom retry logic for the specified throwable types. + * + * @param supplier the supplier to decorate + * @param name the name for the retry instance + * @param customConfig the custom retry configuration + * @param retryOnThrowables the throwable classes to retry on + * @return decorated supplier with retry logic + */ + @SafeVarargs + public static <T> Supplier<T> withRetry(Supplier<T> supplier, String name, RetryConfig customConfig, + Class<? extends Throwable>... retryOnThrowables) { + Retry retry = createRetry(name, customConfig, retryOnThrowables); + return Retry.decorateSupplier(retry, supplier); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/InvocationResult.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/InvocationResult.java new file mode 100644 index 000000000..b91840b8e --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/InvocationResult.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils.lambda; + +import java.time.Instant; +import software.amazon.awssdk.services.lambda.model.InvokeResponse; +import software.amazon.lambda.powertools.testutils.logging.InvocationLogs; + +public class InvocationResult { + + private final InvocationLogs logs; + private final String result; + + private final String requestId; + private final Instant start; + private final Instant end; + + public InvocationResult(InvokeResponse response, Instant start, Instant end) { + requestId = response.responseMetadata().requestId(); + logs = new InvocationLogs(response.logResult(), requestId); + result = response.payload().asUtf8String(); + this.start = start; + this.end = end; + } + + public InvocationLogs getLogs() { + return logs; + } + + public String getResult() { + return result; + } + + public String getRequestId() { + return requestId; + } + + public Instant getStart() { + return start; + } + + public Instant getEnd() { + return end; + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/LambdaInvoker.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/LambdaInvoker.java new file mode 100644 index 000000000..cf45076bf --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/LambdaInvoker.java @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils.lambda; + +import static java.time.temporal.ChronoUnit.MINUTES; + +import java.time.Clock; +import java.time.Instant; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.lambda.LambdaClient; +import software.amazon.awssdk.services.lambda.model.InvokeRequest; +import software.amazon.awssdk.services.lambda.model.InvokeResponse; +import software.amazon.awssdk.services.lambda.model.LogType; + +public class LambdaInvoker { + private static final SdkHttpClient httpClient = UrlConnectionHttpClient.builder().build(); + private static final Region region = Region.of(System.getProperty("AWS_DEFAULT_REGION", "eu-west-1")); + private static final LambdaClient lambda = LambdaClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + + public static InvocationResult invokeFunction(String functionName, String input) { + SdkBytes payload = SdkBytes.fromUtf8String(input); + + InvokeRequest request = InvokeRequest.builder() + .functionName(functionName) + .payload(payload) + .logType(LogType.TAIL) + .build(); + + Instant start = Instant.now(Clock.systemUTC()).truncatedTo(MINUTES); + InvokeResponse response = lambda.invoke(request); + Instant end = start.plus(1, MINUTES); + return new InvocationResult(response, start, end); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/logging/InvocationLogs.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/logging/InvocationLogs.java new file mode 100644 index 000000000..cd63d308a --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/logging/InvocationLogs.java @@ -0,0 +1,87 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils.logging; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.stream.IntStream; + +/** + * Logs for a specific Lambda invocation + */ +public class InvocationLogs { + private final String[] logs; + private final String[] functionLogs; + + public InvocationLogs(String base64Logs, String requestId) { + String rawLogs = new String(Base64.getDecoder().decode(base64Logs), StandardCharsets.UTF_8); + this.logs = rawLogs.split("\n"); + + String start = String.format("START RequestId: %s", requestId); + String end = String.format("END RequestId: %s", requestId); + int startPos = IntStream.range(0, logs.length) + .filter(i -> logs[i].startsWith(start)) + .findFirst() + .orElse(-1); + int endPos = IntStream.range(0, logs.length) + .filter(i -> logs[i].equals(end)) + .findFirst() + .orElse(-1); + this.functionLogs = Arrays.copyOfRange(this.logs, startPos + 1, endPos); + } + + public String[] getAllLogs() { + return logs; + } + + /** + * Return only logs from function, exclude START, END, and REPORT and other elements generated by Lambda service + * + * @return only logs generated by the function + */ + public String[] getFunctionLogs() { + return this.functionLogs; + } + + public String[] getFunctionLogs(Level level) { + String[] filtered = getFunctionLogs(); + + return Arrays.stream(filtered).filter(log -> log.contains("\"level\":\"" + level.getLevel() + "\"")) + .toArray(String[]::new); + } + + public enum Level { + DEBUG("DEBUG"), + INFO("INFO"), + WARN("WARN"), + ERROR("ERROR"); + + private final String level; + + Level(String lvl) { + this.level = lvl; + } + + public String getLevel() { + return level; + } + + @Override + public String toString() { + return level; + } + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricDataNotFoundException.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricDataNotFoundException.java new file mode 100644 index 000000000..79478c14e --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricDataNotFoundException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils.metrics; + +import software.amazon.lambda.powertools.testutils.DataNotReadyException; + +/** + * Exception thrown when metric data is not found in CloudWatch. + * This exception is used to trigger retries as metrics may not be available immediately. + */ +public class MetricDataNotFoundException extends DataNotReadyException { + public MetricDataNotFoundException(String message) { + super(message); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricsFetcher.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricsFetcher.java new file mode 100644 index 000000000..f856d8f2f --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricsFetcher.java @@ -0,0 +1,103 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils.metrics; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.cloudwatch.CloudWatchClient; +import software.amazon.awssdk.services.cloudwatch.model.Dimension; +import software.amazon.awssdk.services.cloudwatch.model.GetMetricDataRequest; +import software.amazon.awssdk.services.cloudwatch.model.GetMetricDataResponse; +import software.amazon.awssdk.services.cloudwatch.model.Metric; +import software.amazon.awssdk.services.cloudwatch.model.MetricDataQuery; +import software.amazon.awssdk.services.cloudwatch.model.MetricStat; +import software.amazon.awssdk.services.cloudwatch.model.StandardUnit; +import software.amazon.lambda.powertools.testutils.RetryUtils; + +/** + * Class in charge of retrieving the actual metrics of a Lambda execution on CloudWatch + */ +public class MetricsFetcher { + private static final Logger LOG = LoggerFactory.getLogger(MetricsFetcher.class); + + private static final SdkHttpClient httpClient = UrlConnectionHttpClient.builder().build(); + private static final Region region = Region.of(System.getProperty("AWS_DEFAULT_REGION", "eu-west-1")); + private static final CloudWatchClient cloudwatch = CloudWatchClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + + /** + * Retrieve the metric values from start to end. Different parameters are required (see + * {@link CloudWatchClient#getMetricData} for more info). + * Use a retry mechanism as metrics may not be available instantaneously after a function runs. + * + * @param start + * @param end + * @param period + * @param namespace + * @param metricName + * @param dimensions + * @return + */ + public List<Double> fetchMetrics(Instant start, Instant end, int period, String namespace, String metricName, + Map<String, String> dimensions) { + List<Dimension> dimensionsList = new ArrayList<>(); + if (dimensions != null) { + dimensions.forEach((key, value) -> dimensionsList.add(Dimension.builder().name(key).value(value).build())); + } + + Supplier<List<Double>> supplier = () -> { + LOG.debug("Get Metrics for namespace {}, start {}, end {}, metric {}, dimensions {}", namespace, start, + end, metricName, dimensionsList); + GetMetricDataResponse metricData = cloudwatch.getMetricData(GetMetricDataRequest.builder() + .startTime(start) + .endTime(end) + .metricDataQueries(MetricDataQuery.builder() + .id(metricName.toLowerCase(Locale.ROOT)) + .metricStat(MetricStat.builder() + .unit(StandardUnit.COUNT) + .metric(Metric.builder() + .namespace(namespace) + .metricName(metricName) + .dimensions(dimensionsList) + .build()) + .period(period) + .stat("Sum") + .build()) + .returnData(true) + .build()) + .build()); + List<Double> values = metricData.metricDataResults().get(0).values(); + if (values == null || values.isEmpty()) { + throw new MetricDataNotFoundException("No data found for metric " + metricName); + } + return values; + }; + + return RetryUtils.withRetry(supplier, "metrics-fetcher-" + metricName, MetricDataNotFoundException.class).get(); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/SegmentDocument.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/SegmentDocument.java new file mode 100644 index 000000000..5654b9876 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/SegmentDocument.java @@ -0,0 +1,202 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils.tracing; + +import com.fasterxml.jackson.annotation.JsonSetter; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class SegmentDocument { + private String id; + + @JsonSetter("trace_id") + private String traceId; + + private String name; + + @JsonSetter("start_time") + private long startTime; + + @JsonSetter("end_time") + private long endTime; + + private String origin; + + private List<SubSegment> subsegments = new ArrayList<>(); + + public SegmentDocument() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTraceId() { + return traceId; + } + + public void setTraceId(String traceId) { + this.traceId = traceId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + public long getEndTime() { + return endTime; + } + + public void setEndTime(long endTime) { + this.endTime = endTime; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public List<SubSegment> getSubsegments() { + return subsegments; + } + + public void setSubsegments(List<SubSegment> subsegments) { + this.subsegments = subsegments; + } + + public Duration getDuration() { + return Duration.ofMillis(endTime - startTime); + } + + public boolean hasSubsegments() { + return !subsegments.isEmpty(); + } + + public static class SubSegment { + private String id; + + private String name; + + @JsonSetter("start_time") + private long startTime; + + @JsonSetter("end_time") + private long endTime; + + private List<SubSegment> subsegments = new ArrayList<>(); + + private Map<String, Object> annotations; + + private Map<String, Object> metadata; + + private String namespace; + + public SubSegment() { + } + + public boolean hasSubsegments() { + return !subsegments.isEmpty(); + } + + public Duration getDuration() { + return Duration.ofMillis(endTime - startTime); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + public long getEndTime() { + return endTime; + } + + public void setEndTime(long endTime) { + this.endTime = endTime; + } + + public List<SubSegment> getSubsegments() { + return subsegments; + } + + public void setSubsegments(List<SubSegment> subsegments) { + this.subsegments = subsegments; + } + + public Map<String, Object> getAnnotations() { + return annotations; + } + + public void setAnnotations(Map<String, Object> annotations) { + this.annotations = annotations; + } + + public Map<String, Object> getMetadata() { + return metadata; + } + + public void setMetadata(Map<String, Object> metadata) { + this.metadata = metadata; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/Trace.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/Trace.java new file mode 100644 index 000000000..7298957aa --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/Trace.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils.tracing; + +import java.util.ArrayList; +import java.util.List; +import software.amazon.lambda.powertools.testutils.tracing.SegmentDocument.SubSegment; + +public class Trace { + private final List<SubSegment> subsegments = new ArrayList<>(); + + public Trace() { + } + + public List<SubSegment> getSubsegments() { + return subsegments; + } + + public void addSubSegment(SubSegment subSegment) { + subsegments.add(subSegment); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java new file mode 100644 index 000000000..0aed9e811 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java @@ -0,0 +1,261 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils.tracing; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; + +import io.github.resilience4j.retry.RetryConfig; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.xray.XRayClient; +import software.amazon.awssdk.services.xray.model.BatchGetTracesRequest; +import software.amazon.awssdk.services.xray.model.BatchGetTracesResponse; +import software.amazon.awssdk.services.xray.model.GetTraceSummariesRequest; +import software.amazon.awssdk.services.xray.model.GetTraceSummariesResponse; +import software.amazon.awssdk.services.xray.model.TimeRangeType; +import software.amazon.awssdk.services.xray.model.TraceSummary; +import software.amazon.lambda.powertools.testutils.RetryUtils; +import software.amazon.lambda.powertools.testutils.tracing.SegmentDocument.SubSegment; + +/** + * Class in charge of retrieving the actual traces of a Lambda execution on X-Ray + */ +public class TraceFetcher { + private static final ObjectMapper MAPPER = JsonMapper.builder() + .disable(MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .build(); + private static final Logger LOG = LoggerFactory.getLogger(TraceFetcher.class); + private static final SdkHttpClient httpClient = UrlConnectionHttpClient.builder().build(); + private static final Region region = Region.of(System.getProperty("AWS_DEFAULT_REGION", "eu-west-1")); + private static final XRayClient xray = XRayClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + private final Instant start; + private final Instant end; + private final String filterExpression; + private final List<String> excludedSegments; + + /** + * @param start beginning of the time slot to search in + * @param end end of the time slot to search in + * @param filterExpression eventual filter for the search + * @param excludedSegments list of segment to exclude from the search + */ + public TraceFetcher(Instant start, Instant end, String filterExpression, List<String> excludedSegments) { + this.start = start; + this.end = end; + this.filterExpression = filterExpression; + this.excludedSegments = excludedSegments; + } + + public static Builder builder() { + return new Builder(); + } + + /** + * Retrieve the traces corresponding to a specific function during a specific time slot. + * Use a retry mechanism as traces may not be available instantaneously after a function runs. + * + * @return traces + */ + public Trace fetchTrace() { + Supplier<Trace> supplier = () -> { + List<String> traceIds = getTraceIds(); + return getTrace(traceIds); + }; + + RetryConfig customConfig = RetryConfig.custom() + .maxAttempts(120) // 120 attempts over 10 minutes + .waitDuration(Duration.ofSeconds(5)) // 5 seconds between attempts + .build(); + + return RetryUtils.withRetry(supplier, "trace-fetcher", customConfig, TraceNotFoundException.class).get(); + } + + /** + * Retrieve traces from trace ids. + * + * @param traceIds + * @return + */ + private Trace getTrace(List<String> traceIds) { + BatchGetTracesResponse tracesResponse = xray.batchGetTraces(BatchGetTracesRequest.builder() + .traceIds(traceIds) + .build()); + if (!tracesResponse.hasTraces()) { + throw new TraceNotFoundException(String.format("No trace found for traceIds %s", traceIds)); + } + + Trace traceRes = new Trace(); + tracesResponse.traces().forEach(trace -> { + if (trace.hasSegments()) { + trace.segments().forEach(segment -> { + try { + SegmentDocument document = MAPPER.readValue(segment.document(), SegmentDocument.class); + if ("AWS::Lambda::Function".equals(document.getOrigin()) && document.hasSubsegments()) { + LOG.debug("Populating subsegments for document {}", MAPPER.writeValueAsString(document)); + getNestedSubSegments(document.getSubsegments(), traceRes, Collections.emptyList()); + // If only the default (excluded) subsegments were populated we need to keep retrying for + // our custom subsegments. They might appear later. + if (traceRes.getSubsegments().isEmpty()) { + throw new TraceNotFoundException( + "Found AWS::Lambda::Function SegmentDocument with no non-excluded subsegments."); + } + } else if ("AWS::Lambda::Function".equals(document.getOrigin())) { + LOG.debug( + "Found AWS::Lambda::Function SegmentDocument with no subsegments. Retrying {}", + MAPPER.writeValueAsString(document)); + throw new TraceNotFoundException( + "Found AWS::Lambda::Function SegmentDocument with no subsegments."); + } + } catch (JsonProcessingException e) { + LOG.error("Failed to parse segment document: " + e.getMessage()); + throw new RuntimeException(e); + } + }); + } else { + throw new TraceNotFoundException(String.format("No segments found in trace %s", trace.id())); + } + }); + + return traceRes; + } + + private void getNestedSubSegments(List<SubSegment> subsegments, Trace traceRes, List<String> idsToIgnore) { + subsegments.forEach(subsegment -> { + List<String> subSegmentIdsToIgnore = Collections.emptyList(); + if (!excludedSegments.contains(subsegment.getName()) && !idsToIgnore.contains(subsegment.getId())) { + traceRes.addSubSegment(subsegment); + if (subsegment.hasSubsegments()) { + subSegmentIdsToIgnore = subsegment.getSubsegments().stream().map(SubSegment::getId) + .collect(Collectors.toList()); + } + } + if (subsegment.hasSubsegments()) { + getNestedSubSegments(subsegment.getSubsegments(), traceRes, subSegmentIdsToIgnore); + } + }); + } + + /** + * Use the X-Ray SDK to retrieve the trace ids corresponding to a specific function during a specific time slot + * + * @return a list of trace ids + */ + private List<String> getTraceIds() { + LOG.debug("Searching for traces from {} to {} with filter: {}", start, end, filterExpression); + GetTraceSummariesResponse traceSummaries = xray.getTraceSummaries(GetTraceSummariesRequest.builder() + .startTime(start) + .endTime(end) + .timeRangeType(TimeRangeType.TRACE_ID) + .sampling(false) + .filterExpression(filterExpression) + .build()); + + LOG.debug("Found {} trace summaries", + traceSummaries.hasTraceSummaries() ? traceSummaries.traceSummaries().size() : 0); + + if (!traceSummaries.hasTraceSummaries()) { + throw new TraceNotFoundException(String.format("No trace id found for filter '%s' between %s and %s", + filterExpression, start, end)); + } + List<String> traceIds = traceSummaries.traceSummaries().stream().map(TraceSummary::id) + .collect(Collectors.toList()); + if (traceIds.isEmpty()) { + throw new TraceNotFoundException( + String.format("Empty trace summary found for filter '%s' between %s and %s", + filterExpression, start, end)); + } + LOG.debug("Found trace IDs: {}", traceIds); + return traceIds; + } + + public static class Builder { + private Instant start; + private Instant end; + private String filterExpression; + private List<String> excludedSegments = Arrays.asList("Initialization", "Init", "Invocation", "Overhead"); + + public TraceFetcher build() { + if (filterExpression == null) { + throw new IllegalArgumentException("filterExpression or functionName is required"); + } + if (start == null) { + throw new IllegalArgumentException("start is required"); + } + if (end == null) { + end = start.plus(1, ChronoUnit.MINUTES); + } + // Expand search window by 1 minute on each side to account for timing imprecisions + Instant expandedStart = start.minus(1, ChronoUnit.MINUTES); + Instant expandedEnd = end.plus(1, ChronoUnit.MINUTES); + LOG.debug( + "Looking for traces from {} to {} (expanded from {} to {}) with filter {} and excluded segments {}", + expandedStart, expandedEnd, start, end, filterExpression, excludedSegments); + return new TraceFetcher(expandedStart, expandedEnd, filterExpression, excludedSegments); + } + + public Builder start(Instant start) { + this.start = start; + return this; + } + + public Builder end(Instant end) { + this.end = end; + return this; + } + + public Builder filterExpression(String filterExpression) { + this.filterExpression = filterExpression; + return this; + } + + /** + * "Initialization", "Invocation", "Overhead" are excluded by default + * + * @param excludedSegments + * @return + */ + public Builder excludeSegments(List<String> excludedSegments) { + this.excludedSegments = excludedSegments; + return this; + } + + public Builder functionName(String functionName) { + this.filterExpression = String.format("service(id(name: \"%s\", type: \"AWS::Lambda::Function\"))", + functionName); + return this; + } + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceNotFoundException.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceNotFoundException.java new file mode 100644 index 000000000..453aae669 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceNotFoundException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.testutils.tracing; + +import software.amazon.lambda.powertools.testutils.DataNotReadyException; + +/** + * Exception thrown when trace data is not found in X-Ray. + * This exception is used to trigger retries as traces may not be available immediately. + */ +public class TraceNotFoundException extends DataNotReadyException { + public TraceNotFoundException(String message) { + super(message); + } +} diff --git a/powertools-e2e-tests/src/test/resources/docker/Dockerfile b/powertools-e2e-tests/src/test/resources/docker/Dockerfile new file mode 100644 index 000000000..1ceb29aa0 --- /dev/null +++ b/powertools-e2e-tests/src/test/resources/docker/Dockerfile @@ -0,0 +1,14 @@ +# Use the official AWS SAM base image for Java 25 +FROM public.ecr.aws/sam/build-java25@sha256:bffac7de6e418a93d2aefc1e8e7c79eda0971e7a026725fe618b58ddfba7a128 + +# Install GraalVM dependencies +RUN curl -4 -L https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz +RUN mv graalvm-jdk-25.* /usr/lib/graalvm + +# Make native image and mvn available on CLI +RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image +RUN ln -s /usr/lib/maven/bin/mvn /usr/bin/mvn + +# Set GraalVM as default +ENV JAVA_HOME=/usr/lib/graalvm +ENV PATH=/usr/lib/graalvm/bin:$PATH diff --git a/powertools-e2e-tests/src/test/resources/large_sqs_message.txt b/powertools-e2e-tests/src/test/resources/large_sqs_message.txt new file mode 100644 index 000000000..102216e83 --- /dev/null +++ b/powertools-e2e-tests/src/test/resources/large_sqs_message.txt @@ -0,0 +1,977 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam in nibh eget ante viverra mattis. Etiam elit est, pharetra efficitur fermentum at, accumsan id eros. Aenean accumsan nisi facilisis tempor suscipit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nam magna erat, tincidunt eu placerat at, molestie at sem. In sit amet cursus mauris. Etiam ullamcorper lacus nisi, et porta metus semper id. Nulla mauris nunc, pharetra at facilisis a, consequat id metus. Sed convallis ligula non felis vulputate finibus. Curabitur nisl nulla, pharetra in justo a, dapibus ultricies dui. Morbi ultricies sollicitudin purus, quis pellentesque leo rhoncus sed. Curabitur sed eros quis lacus vulputate pretium. Vestibulum vitae urna nec arcu vulputate efficitur. Cras venenatis sodales dolor non ullamcorper. + +Donec eu placerat magna. Vestibulum justo lacus, luctus ac luctus sit amet, dapibus at orci. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec eu tortor ac justo pharetra hendrerit. Phasellus ornare lacinia vestibulum. Maecenas diam orci, molestie rhoncus fringilla vitae, aliquam eu purus. Maecenas vehicula felis dui, vel dapibus odio lobortis quis. Cras a velit non nisl efficitur tempor. + +Suspendisse magna enim, ultrices et elementum ut, venenatis ut purus. Curabitur convallis vel tortor id rhoncus. Pellentesque varius arcu non imperdiet eleifend. Vestibulum fringilla aliquet vehicula. Nullam et lorem ullamcorper, tincidunt ante eget, egestas ex. Donec laoreet, justo id tincidunt egestas, lectus diam faucibus tortor, nec venenatis felis leo a urna. In at tortor semper augue finibus ornare et vitae erat. Praesent vulputate, dui sed pulvinar porta, justo tortor feugiat tellus, eget accumsan velit turpis at orci. Nulla vitae velit facilisis augue sagittis fringilla nec sagittis nunc. + +Duis vel elementum odio, id eleifend mauris. Nam in ultrices nulla. Quisque pharetra blandit risus eget lacinia. Nulla mattis mi felis, a ultrices nisi egestas vulputate. Donec a augue ac ex laoreet semper at porta ante. Vestibulum arcu ipsum, vehicula vel mollis lobortis, ullamcorper sed augue. Ut viverra faucibus nunc, sit amet sodales diam gravida sollicitudin. Fusce finibus quam sed ornare consequat. Suspendisse viverra ultricies dui in volutpat. Maecenas blandit erat in rhoncus placerat. Phasellus eu nisi rutrum, bibendum nunc eu, viverra ex. Praesent sollicitudin mi sit amet dictum convallis. Vivamus purus ligula, interdum at tincidunt sit amet, pellentesque ut elit. Aenean vitae ultrices ex. In facilisis lectus est, nec vulputate diam tristique vel. In gravida tincidunt ex et viverra. + +Pellentesque mi justo, dignissim a nisl quis, tempor dictum lacus. Nam tristique varius dolor tincidunt pharetra. Nullam iaculis est sed quam dignissim cursus. Duis sit amet vehicula nisi, sed consectetur velit. Nullam non tellus nec dolor venenatis porta quis at quam. In pharetra id orci sed faucibus. Cras et malesuada erat. + +Quisque fringilla commodo metus nec feugiat. Donec et est urna. Maecenas ut magna dui. Vivamus eu sem venenatis, porta mi at, efficitur tortor. In lacinia hendrerit elit, in faucibus nulla ultrices et. Mauris accumsan nec orci et vestibulum. Aliquam eget justo velit. Mauris fringilla ullamcorper enim, in elementum enim eleifend a. Nulla id libero ac arcu tristique ultrices. Maecenas mattis orci vitae nisl laoreet auctor. + +Quisque id aliquam orci. Nunc molestie vitae quam eu consectetur. Vivamus molestie venenatis est, nec lacinia odio faucibus eget. Etiam ornare eu leo eget posuere. Quisque elementum ligula at euismod placerat. Maecenas lobortis leo diam, vel egestas odio iaculis ac. Integer dapibus mi metus. Sed at eleifend ligula, ullamcorper vestibulum eros. Suspendisse dignissim metus in vulputate iaculis. Nam rhoncus felis sit amet velit scelerisque semper. Ut sed augue eget nulla laoreet scelerisque. + +Aenean convallis ipsum id turpis gravida, et elementum ante facilisis. Donec vitae arcu id mauris mollis hendrerit. Mauris imperdiet cursus nibh at laoreet. Sed sit amet enim magna. Mauris blandit nisi quis arcu sodales, eget consequat orci euismod. Curabitur id bibendum diam. Ut pharetra tellus nec enim tristique, id efficitur eros accumsan. Suspendisse potenti. Praesent vel porttitor risus. Nulla vitae ex nec ex hendrerit euismod non mattis arcu. Aenean eget velit massa. Pellentesque venenatis arcu mauris, sit amet scelerisque sem lobortis ornare. Vivamus auctor porta eros, eget blandit lorem semper non. Nunc sed nibh commodo, hendrerit nunc at, malesuada nisl. Aliquam pretium leo sed iaculis vehicula. Vivamus interdum porta augue. + +Suspendisse potenti. Aenean sodales vehicula erat, vel sollicitudin eros eleifend id. Suspendisse hendrerit malesuada est, imperdiet tristique ex aliquet ac. Vivamus ultricies placerat lorem, ac placerat lectus sollicitudin ac. Etiam consequat odio vel bibendum pretium. Integer elementum nisl magna. Nam et vehicula risus, sed mollis tortor. Ut in molestie turpis. Nam luctus, elit molestie varius luctus, lacus ligula ullamcorper elit, non interdum ipsum neque non nibh. Curabitur vulputate facilisis suscipit. Sed vitae augue eu ex luctus lacinia ac vitae quam. Quisque non nibh ex. Praesent gravida efficitur dui, a vulputate nulla ultrices vitae. Duis libero dolor, facilisis in tempus vitae, pharetra non libero. Sed urna leo, placerat quis neque at, interdum placerat odio. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Vivamus in hendrerit elit, quis egestas sem. Sed tempus dolor finibus massa ultrices, sed tristique quam semper. Pellentesque euismod molestie facilisis. Vivamus sit amet ultrices elit. Ut eleifend, libero eu molestie maximus, ipsum elit pulvinar tortor, et aliquet nulla orci eu est. Nullam pretium, ligula at faucibus gravida, velit mauris pulvinar felis, sit amet tincidunt magna dui ut quam. Phasellus porttitor eu nunc id maximus. Suspendisse volutpat suscipit porta. + +Integer dictum augue purus, sed cursus eros blandit id. Vestibulum non nibh viverra, molestie lectus eu, vestibulum justo. Nullam a libero non nibh dignissim posuere id vel orci. Nulla viverra lorem eget condimentum scelerisque. Donec porta nunc sed sapien pretium dapibus. In hac habitasse platea dictumst. Suspendisse sit amet ligula elementum, accumsan ipsum vel, imperdiet massa. Fusce scelerisque non erat ac bibendum. Pellentesque consequat vehicula euismod. Morbi sapien nisl, ultrices ut scelerisque consectetur, ornare et orci. Maecenas efficitur eros a venenatis rutrum. Aenean bibendum dui in enim luctus posuere. Donec placerat porta eros eu dignissim. Fusce nulla velit, sodales eget nibh gravida, tincidunt venenatis urna. Aenean condimentum, massa in fringilla lobortis, turpis felis lacinia metus, sit amet facilisis nisl quam aliquet diam. Praesent fringilla vitae arcu nec lobortis. + +In tincidunt nisi ac dictum cursus. Sed dolor purus, posuere vel dolor vel, semper tempor elit. Integer nec scelerisque tellus, sit amet dictum magna. Donec hendrerit aliquet libero, a varius velit dapibus quis. Donec lectus turpis, egestas vel vestibulum vitae, iaculis quis eros. Maecenas id convallis metus. Duis quis tellus iaculis, lobortis massa vitae, condimentum eros. Pellentesque scelerisque nisl id hendrerit tempor. Donec quam augue, maximus eu porta ut, venenatis a ante. Curabitur dictum et nibh sed auctor. Nam et consequat odio. In vitae magna a dui porta pellentesque quis id magna. Sed eu nunc bibendum, imperdiet nunc sit amet, cursus diam. Vivamus in odio vel nibh sollicitudin molestie. Morbi sit amet leo et odio congue pharetra. Aliquam quis suscipit orci. + +Donec at ornare orci. Suspendisse nec tellus elit. Nam erat urna, laoreet at elit sed, tristique interdum ligula. Nullam dapibus orci sed lorem convallis fringilla. Cras urna ipsum, tristique maximus nisl quis, auctor mattis quam. Donec finibus consectetur lectus et interdum. Aenean posuere lectus vel turpis auctor finibus. Praesent molestie lectus ipsum, et tristique quam porttitor ac. Suspendisse aliquam pretium tortor, id egestas erat. Nulla mollis, orci at semper vulputate, nisl est pharetra diam, sit amet vulputate diam augue a sem. Donec in mauris felis. + +Nunc eleifend at elit a volutpat. Integer semper quis quam at faucibus. Donec pretium purus vitae erat pretium posuere. Sed ac aliquet nulla. Nam ut sagittis mauris. Sed quis sagittis risus, id pretium urna. Nam sagittis elementum ornare. Aenean ligula sapien, congue eget mi quis, viverra commodo nibh. Suspendisse non iaculis magna, nec volutpat neque. Donec eu dapibus eros. + +Nam sit amet iaculis massa. Nullam vel laoreet tellus, id cursus tortor. In hac habitasse platea dictumst. Curabitur in urna risus. Proin dui sem, interdum accumsan cursus ut, dignissim in purus. Nunc vitae consequat arcu. Proin volutpat elementum quam in posuere. Pellentesque luctus arcu in ornare tempor. Cras est turpis, viverra vel consectetur ac, dictum a quam. Donec sagittis tortor sed volutpat accumsan. Curabitur a tellus vitae arcu pretium viverra vel in ligula. Pellentesque turpis augue, porta vitae quam id, fermentum congue leo. Sed nec fermentum turpis. Nulla vehicula vulputate sem eu consequat. In tincidunt nisi et mi maximus, non facilisis justo porttitor. Proin eu sapien aliquam, accumsan nunc non, pulvinar leo. + +Nam vitae commodo sem. Ut laoreet in quam in suscipit. Nullam at faucibus diam. Fusce ut purus at ex lobortis elementum vitae quis sapien. Nam tempus consectetur ex, sed luctus felis. Donec porttitor mi dui, non luctus quam consectetur eu. Ut a egestas diam. Etiam sodales, nisl vitae gravida pulvinar, libero est condimentum tellus, vitae ullamcorper tortor justo a urna. Maecenas ac velit accumsan, laoreet leo eget, elementum libero. Maecenas dictum tincidunt blandit. Proin sed bibendum urna. Phasellus fermentum tincidunt tempor. Mauris iaculis, mi ac fermentum interdum, nibh odio pellentesque nunc, vel scelerisque sapien sem id purus. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Maecenas imperdiet sit amet lectus at hendrerit. Vivamus luctus, elit non lacinia hendrerit, risus velit finibus nulla, quis sagittis ipsum diam ut odio. Sed mauris sapien, vehicula efficitur gravida sed, gravida in lectus. Fusce eget consectetur turpis, in fermentum neque. Nullam turpis turpis, feugiat a accumsan in, euismod a augue. Vestibulum nec neque libero. Phasellus eget efficitur turpis, fermentum molestie nunc. Sed consequat elit urna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. + +Nunc egestas consequat blandit. Donec quis porta sapien. Sed augue nunc, efficitur vitae tellus sed, finibus bibendum sem. Fusce id sem quis quam pharetra ultrices. Phasellus non convallis velit. Quisque erat tellus, pretium eget porta in, ornare a arcu. Aenean nec lectus lorem. Suspendisse dolor lectus, ullamcorper ornare lorem a, consequat lobortis elit. Nunc dignissim est nec gravida facilisis. Proin faucibus erat vel eros volutpat, in vulputate neque sodales. + +Nunc vitae imperdiet ipsum, faucibus vehicula magna. Nulla nec nisl sapien. Curabitur et dui eget tortor efficitur accumsan. Aenean in pellentesque erat. Nam condimentum neque pulvinar, aliquam nisl eu, mollis lorem. Integer rutrum arcu eget felis semper euismod. Quisque vestibulum vel diam a tempor. Aenean mollis, tellus sit amet pretium pulvinar, nulla dolor ornare ligula, eget dignissim orci tortor ut sapien. Nam ut ipsum id lectus venenatis tempus vel non ex. Suspendisse blandit vitae nunc vitae porta. Suspendisse tincidunt est sit amet ultricies consectetur. Nulla fermentum hendrerit ex, vehicula rhoncus arcu lobortis ut. Vestibulum fermentum ornare diam at pellentesque. Praesent nunc lorem, porta et magna nec, sodales commodo justo. Duis aliquam sapien et rutrum tempus. Vestibulum malesuada felis eu ligula posuere luctus. + +Maecenas lacinia, lectus eget rhoncus aliquam, tortor est gravida sapien, vel aliquam arcu erat et magna. Praesent fringilla leo eget neque posuere imperdiet. In porttitor elit non enim gravida euismod. Aliquam tempus, orci at interdum dapibus, mauris lacus egestas sapien, ac ullamcorper ex nibh sed orci. Quisque iaculis enim et lectus egestas, malesuada posuere lectus interdum. Sed dignissim neque vel turpis dictum ornare. Vestibulum suscipit consequat maximus. + +Ut et ante sit amet leo rutrum volutpat. Sed malesuada quis sapien et ornare. Aliquam ac ex enim. Curabitur vel quam et orci posuere feugiat. Pellentesque nec metus eget sapien eleifend tincidunt non sit amet arcu. Cras posuere metus eget risus varius fermentum. Nullam orci eros, efficitur nec sapien nec, pretium laoreet erat. Nam gravida purus mauris, ac viverra orci hendrerit sed. Aenean ligula massa, posuere id faucibus vitae, malesuada quis augue. Morbi consectetur mattis mi, quis sodales diam bibendum eget. Aliquam sagittis neque at feugiat posuere. Donec gravida lectus a lectus fringilla tincidunt. Vivamus volutpat dui et turpis condimentum, ut tincidunt tortor lacinia. Ut laoreet, ante in pellentesque sodales, elit mauris scelerisque dui, quis tincidunt quam massa ac enim. Nulla porta porta sapien vel sollicitudin. + +Phasellus sed lectus posuere, mollis ante non, feugiat odio. Aenean a quam id mi ornare dapibus. Donec venenatis ipsum non velit accumsan, id elementum dolor imperdiet. Phasellus lacinia erat diam, sit amet convallis lectus ornare in. Curabitur lorem ligula, maximus eu varius quis, auctor quis odio. Etiam in orci porttitor, sodales ipsum at, rhoncus turpis. Nulla eget luctus nisi. Duis convallis est eget ligula fringilla viverra. Duis nec sapien quis dui luctus ornare sit amet quis erat. Quisque nec justo dui. + +Sed eleifend, tellus quis laoreet rhoncus, leo turpis imperdiet turpis, pretium varius odio tortor et leo. Morbi ut mi fringilla, porttitor sem ac, accumsan ante. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum cursus dolor accumsan dolor aliquet dignissim. Duis sed feugiat erat. Donec sed arcu accumsan, ornare nisl eget, lobortis nibh. Praesent pulvinar quis justo et hendrerit. + +Sed velit nibh, efficitur ut leo sed, eleifend iaculis nisl. Mauris ac diam euismod, luctus enim maximus, tincidunt sem. Ut tempus magna vitae blandit bibendum. Sed sapien tortor, ultrices et justo vel, pharetra faucibus dui. Vivamus et vestibulum arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Mauris tincidunt suscipit risus, vitae varius felis commodo in. Nullam semper tortor dolor, sit amet pharetra odio interdum hendrerit. Pellentesque sed justo eros. Cras quam purus, eleifend id tellus molestie, eleifend finibus lorem. Donec a volutpat libero, at vehicula tellus. Vivamus tellus orci, pharetra in nisi vitae, tincidunt dapibus nunc. + +Suspendisse ultricies sem laoreet quam molestie ullamcorper. Sed auctor sodales metus, eget convallis eros ultrices quis. Duis rutrum mi a tortor iaculis posuere. Suspendisse potenti. Pellentesque dictum faucibus dui a vestibulum. Donec elit nisl, aliquet at dolor non, viverra dignissim felis. Donec mi elit, condimentum sit amet magna id, efficitur sodales arcu. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque est elit, porttitor eget bibendum nec, auctor nec nulla. Pellentesque dignissim nisl vel orci commodo, ut ullamcorper justo viverra. + +Suspendisse pharetra gravida turpis sit amet efficitur. Nulla mattis tortor eget pharetra ultrices. Ut a turpis maximus, pharetra justo non, tempor quam. Nam et volutpat lectus. Vestibulum congue turpis augue, quis eleifend nulla euismod in. Vestibulum sed erat tempus, porttitor diam vel, elementum metus. Aenean facilisis molestie ante, sit amet ornare lacus mollis sed. Maecenas rutrum urna nec ex malesuada consequat. Nunc interdum pellentesque nisl sed viverra. Nam nec fermentum ipsum. Nulla facilisi. Maecenas congue dolor erat, a fermentum enim congue vitae. Integer metus libero, feugiat at eleifend eu, iaculis nec leo. Praesent eleifend convallis leo, eu posuere urna elementum eu. + +Aliquam dapibus dolor vel urna luctus venenatis. Suspendisse potenti. Donec vitae tellus nisi. Pellentesque vel neque dignissim, ornare tellus sed, sodales metus. Aliquam vitae maximus tortor. Nullam mattis, odio id porttitor euismod, est leo aliquet massa, bibendum pulvinar justo orci a magna. Morbi ut nisl congue, porttitor erat non, gravida turpis. In nulla risus, ullamcorper quis suscipit vel, tristique ut nulla. In malesuada odio augue, ac hendrerit nunc mattis a. Nam in quam ut elit ullamcorper mattis. Sed fringilla tempus felis, id pretium tortor varius non. Aenean quis sem quis risus mattis posuere vel quis nibh. + +Sed pulvinar commodo dui sit amet malesuada. Quisque porta tellus placerat congue efficitur. Cras id blandit enim. Praesent a urna felis. Aliquam ornare, nisl eget iaculis tempor, ligula mi vehicula dolor, ut eleifend massa massa vel est. Fusce venenatis laoreet lobortis. Proin tempus aliquet nunc ut porttitor. In est mauris, blandit eu mattis vitae, aliquam a orci. Cras vitae nulla nec ex cursus blandit. Aliquam fermentum, erat in aliquet dictum, metus urna fermentum quam, non varius magna lectus non urna. + +Donec faucibus nisl nunc. Integer rutrum dui enim, malesuada semper turpis pulvinar eget. Fusce consectetur ipsum tellus, sed maximus lorem auctor in. Morbi nec est in quam ultricies hendrerit. Sed aliquam sollicitudin elementum. Aenean aliquam ex sit amet dignissim venenatis. Duis malesuada leo nisi, id accumsan turpis pretium id. Sed ornare magna non pharetra pharetra. Ut auctor dolor neque, nec bibendum odio viverra in. Nulla convallis interdum condimentum. Proin vestibulum turpis in lorem ultrices, bibendum tristique ligula faucibus. In tincidunt auctor sem, vitae fringilla neque pulvinar eu. Etiam commodo pellentesque enim. Phasellus feugiat non sapien eget sollicitudin. Etiam consequat efficitur lacus vel maximus. Suspendisse vitae elementum diam. + +Mauris urna velit, efficitur at viverra at, interdum a velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean bibendum sagittis massa in interdum. Mauris facilisis dui eget ipsum euismod ultrices. Donec sit amet lorem iaculis, condimentum velit quis, lacinia justo. Integer faucibus metus sed eros semper, in congue tortor venenatis. Proin tincidunt maximus enim, accumsan facilisis tellus gravida eu. Phasellus interdum aliquam ante, sit amet rhoncus nisl ornare at. Suspendisse libero eros, cursus quis fermentum nec, tempor eget lectus. Aenean ullamcorper augue lacus, non iaculis dui rutrum id. Aliquam erat volutpat. + +Morbi hendrerit fermentum sodales. Proin rutrum congue auctor. Mauris pellentesque elit non velit condimentum tincidunt a sit amet velit. Etiam accumsan ante id neque commodo vulputate. Aenean nec mattis neque. Aliquam tempor urna quis nisl convallis congue. Praesent vitae porttitor ante. Mauris mattis vestibulum ante, nec auctor augue. Praesent sem leo, accumsan eu fermentum egestas, iaculis ac nulla. + +Etiam vel nisi congue, varius neque id, volutpat quam. Fusce placerat arcu hendrerit orci condimentum vehicula. Integer sem ex, facilisis et lacinia a, condimentum at ante. Morbi condimentum diam tellus, non lobortis arcu pellentesque sit amet. Phasellus imperdiet augue eget sollicitudin mollis. Vivamus sollicitudin arcu aliquam lectus tristique, in consequat diam egestas. Suspendisse aliquet non velit id feugiat. Sed eu est fringilla, gravida nulla ac, dignissim tortor. Phasellus pellentesque nisl non venenatis sagittis. Etiam facilisis nulla quis lorem scelerisque vehicula id at ex. Duis in risus enim. In eu vulputate massa, vel dignissim odio. Praesent ut mi tellus. In hac habitasse platea dictumst. + +Nunc malesuada turpis in fermentum lobortis. Donec blandit eu orci non laoreet. Suspendisse posuere blandit tortor, in accumsan velit cursus in. Integer suscipit justo nulla, in viverra orci suscipit et. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum a pulvinar lacus. Vestibulum mattis nisi vel nunc convallis maximus. Fusce aliquam, erat in volutpat gravida, elit odio feugiat ante, nec varius enim leo eget nisl. Donec sit amet ligula lobortis, sollicitudin dolor quis, blandit augue. Duis at interdum sem. Nullam eleifend ligula urna, vitae sollicitudin purus cursus nec. + +In commodo risus eu justo hendrerit, ut posuere mi eleifend. Suspendisse sollicitudin odio sem. Vestibulum at dapibus dui, vel dictum nisi. In hac habitasse platea dictumst. Sed ac pharetra ex. Ut ultrices augue ut vulputate condimentum. Phasellus convallis arcu tortor, ac tincidunt justo cursus vitae. Mauris dignissim dapibus imperdiet. Nullam id quam eget mauris cursus molestie finibus eu enim. Fusce laoreet orci eu nunc fermentum tincidunt. Pellentesque vitae ex ac nunc porta mattis sed ut ligula. Phasellus erat dui, consequat et lacinia vel, blandit nec dui. Donec ipsum magna, rhoncus ac viverra vitae, feugiat vel ligula. + +Cras ut nisl sed elit dictum semper a at magna. Aliquam laoreet viverra velit vel lobortis. Nam tempor lorem sit amet purus tincidunt accumsan. Vestibulum et vestibulum ligula. Donec sit amet neque faucibus leo rutrum semper. Maecenas scelerisque, lacus et lobortis congue, purus quam euismod risus, et mattis orci nisl eget est. In hac habitasse platea dictumst. Pellentesque eros velit, sollicitudin quis ante vel, blandit maximus mi. Suspendisse at eros id quam vulputate ornare. Duis placerat tellus vel odio ultrices, ac feugiat enim placerat. Vestibulum ut massa mattis, commodo orci euismod, cursus lacus. Suspendisse potenti. Sed venenatis cursus neque, at lacinia erat ornare et. Sed rutrum, dui at porttitor hendrerit, lacus magna fringilla quam, id mollis elit leo ut ante. Praesent vel diam sed urna mollis laoreet eget ut risus. + +Mauris varius odio lectus, sit amet consequat nulla sollicitudin sed. Suspendisse commodo rhoncus enim vitae pellentesque. Aliquam vulputate sollicitudin aliquet. Mauris interdum interdum orci, non varius sem ornare ut. Sed vel nibh nunc. Duis tincidunt quam quis lectus egestas, eu ultricies ante posuere. Aenean in mauris eros. Suspendisse potenti. Vivamus sit amet augue at velit accumsan fermentum. Phasellus vel dui sit amet felis convallis sodales. + +Nunc mattis vitae sapien ut dignissim. Nam fermentum sit amet massa eu accumsan. In ut ipsum sit amet ante pellentesque accumsan. Nulla egestas eros eget lacus rhoncus pharetra. Nam pellentesque laoreet ex in lobortis. Vivamus congue tincidunt molestie. Integer vel turpis augue. Cras vel molestie quam. Etiam vel mauris ut tellus finibus consequat. + +Curabitur velit erat, vestibulum blandit massa a, aliquam elementum tellus. Pellentesque id nisl tempor lorem condimentum dapibus. Quisque ut lorem at orci elementum dapibus quis ut enim. Praesent venenatis congue arcu eu sagittis. Nunc nunc massa, posuere ac gravida id, scelerisque nec velit. Suspendisse non lacus a orci dapibus congue. Sed leo velit, facilisis sed egestas ut, vehicula at turpis. Praesent a finibus felis. Quisque est mi, pellentesque sit amet varius eu, gravida id est. + +Donec auctor gravida urna ac suscipit. Etiam placerat ipsum vitae ante congue, at fringilla lectus ullamcorper. Donec viverra lacus ut erat posuere, dignissim porta purus tempor. Duis eget enim quis diam vehicula pulvinar. Donec tempus eros velit, sit amet pharetra ligula placerat in. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas sit amet pretium tellus, non dictum ligula. Nullam a ex purus. Vestibulum id ex rhoncus, pretium eros vel, consequat tellus. Vivamus lacinia dolor nec ipsum aliquam, euismod fermentum sem efficitur. Nulla facilisi. Pellentesque pharetra lacinia augue, et eleifend lorem aliquet eu. Ut ut porta ante. Duis ut auctor nisi, id porttitor erat. Proin sollicitudin sem quis justo sodales aliquam. Nulla pharetra gravida arcu vel tempus. + +Maecenas nec imperdiet nulla, vitae fringilla diam. Mauris maximus aliquet tellus at congue. Aliquam in dictum elit. Sed pretium mattis lectus, non pellentesque enim dignissim vel. Sed quis elementum diam, a porttitor enim. Cras cursus eros nisl. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ornare sapien vel diam vestibulum, at commodo metus lobortis. Sed euismod iaculis scelerisque. Pellentesque venenatis malesuada dolor, at tempus eros venenatis sit amet. Fusce a massa non libero blandit suscipit. + +Nulla facilisi. Sed nec leo nisl. Proin condimentum nunc at risus semper, in laoreet dui congue. Integer in dolor vitae ex maximus porttitor. In efficitur vulputate metus, ac egestas diam pharetra ac. Sed tristique tempus ligula dignissim convallis. Ut tincidunt laoreet fringilla. Praesent nunc est, lacinia tristique odio in, varius rhoncus ipsum. Vivamus bibendum justo at metus ullamcorper imperdiet. Quisque elit nibh, rhoncus a placerat eget, vehicula quis ante. + +Morbi fermentum turpis enim, eu interdum dui interdum sit amet. Sed vulputate lacus nec ligula gravida euismod. Duis in nisl fringilla, sollicitudin diam ac, laoreet tortor. Sed eget porttitor augue. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed eu enim convallis augue vulputate convallis. Praesent laoreet, leo at ornare luctus, nisi felis semper sapien, sit amet sodales augue mauris eu lorem. Fusce ornare volutpat malesuada. Curabitur vehicula, eros id fringilla placerat, tellus elit venenatis lorem, ac imperdiet purus ex blandit felis. Nunc suscipit elementum risus ac dictum. Aliquam bibendum dignissim ipsum, sit amet posuere sem viverra ut. Vestibulum egestas in ex ac volutpat. + +Donec ut faucibus velit, nec suscipit metus. Nullam dui ligula, commodo eget est venenatis, ullamcorper porttitor lectus. Suspendisse dictum, metus vitae commodo ultricies, enim quam pretium enim, a efficitur tortor purus sed ipsum. Nam sit amet lacus vel elit consectetur ultrices. Vestibulum ac nibh in metus porttitor vestibulum. In blandit et est non efficitur. Aenean vestibulum tortor sit amet mattis semper. Praesent sit amet turpis ac neque malesuada tincidunt nec nec urna. Mauris posuere elit nisl, ac ullamcorper nisi pulvinar in. Nulla ac elit in neque ullamcorper facilisis sed et dui. + +Quisque molestie euismod dui, non scelerisque arcu dictum a. Donec eu nulla nisl. Aliquam neque orci, ultrices in nunc vitae, sollicitudin eleifend augue. Etiam at mattis velit, a efficitur dui. Nam bibendum enim non elit tristique aliquet. Vestibulum eget nibh lacus. Pellentesque id sapien dui. Nullam urna leo, faucibus et efficitur vel, ullamcorper iaculis mi. Donec sit amet bibendum justo, id dapibus sem. Pellentesque dignissim varius tellus nec egestas. Praesent eu orci vel ipsum pharetra maximus. Cras nisl ligula, sollicitudin in ligula vel, posuere sagittis eros. Phasellus porta tristique mauris vel eleifend. Mauris sit amet volutpat ipsum, sed vestibulum orci. + +Ut rutrum augue quis rhoncus tincidunt. Aenean sollicitudin lacus at erat varius ullamcorper. Integer vitae orci vel nisi vulputate cursus eget nec ex. Mauris elementum, augue ac accumsan convallis, libero leo porta ante, sit amet elementum felis ligula non enim. Ut venenatis semper posuere. Vestibulum nec nisl nisi. Ut a sapien ac orci finibus dictum sed at ex. Phasellus ac lorem nisl. Quisque vitae tempor lacus. Vestibulum in mauris diam. Proin mattis ligula vitae ipsum bibendum, at dapibus nunc placerat. Nam iaculis justo in accumsan tristique. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris pretium velit et tristique pellentesque. Nunc in sapien a purus congue rutrum. Nam placerat risus in ante rutrum rutrum. In in ligula magna. Duis orci ante, vehicula elementum consectetur vitae, lobortis vitae arcu. Ut feugiat tempus metus quis sollicitudin. Etiam cursus venenatis augue at fermentum. + +Vestibulum id vehicula massa. Proin ut ligula et sapien placerat tristique non nec nibh. Etiam non lacus placerat, molestie ex eu, venenatis elit. Sed posuere, ante et ullamcorper luctus, elit lectus blandit tortor, nec consequat massa elit a tortor. Fusce urna felis, porttitor at ultrices vel, eleifend porta ipsum. Pellentesque nisl nibh, molestie sit amet sem eu, dapibus pharetra nulla. Phasellus viverra augue eu augue volutpat dignissim. Integer bibendum faucibus varius. Curabitur non turpis purus. + +Sed pretium eros nisl, sed ullamcorper magna faucibus quis. Etiam ornare euismod tellus, vitae accumsan eros bibendum ut. Morbi a ex sed risus faucibus rutrum et ut urna. Nullam non tortor commodo, facilisis massa non, tristique metus. Curabitur placerat, arcu in egestas ullamcorper, nisl nisl luctus felis, ac dapibus erat velit sed erat. Proin sagittis felis vitae sem posuere semper. Vivamus fermentum eu nisi ac facilisis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque pellentesque lacinia ex tempus lacinia. Aliquam erat volutpat. Nam aliquet mattis risus non pretium. Suspendisse potenti. + +Suspendisse et sapien ornare, elementum erat vel, ultrices nisi. Curabitur interdum dignissim tincidunt. Cras eget est a velit consectetur finibus et sed nisl. Curabitur vestibulum semper posuere. Aliquam porta diam commodo nulla tempus imperdiet. Sed id dictum neque. Ut sit amet risus aliquet, dignissim velit sed, tristique ipsum. Nam a sapien id magna commodo tincidunt quis ac tellus. Nunc nec pellentesque orci. In justo purus, vulputate quis urna a, fringilla blandit sem. Cras porttitor quam vitae metus vehicula faucibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut eleifend orci lectus, vitae semper eros hendrerit id. Fusce nec tellus condimentum, vehicula elit quis, gravida erat. Maecenas volutpat interdum velit id vestibulum. + +Pellentesque id metus metus. Nullam ac metus non nisi facilisis aliquet quis a sem. Morbi cursus consectetur aliquam. Morbi id sapien nibh. Etiam tempus tempor tempor. Nam scelerisque condimentum purus. Proin nec cursus eros, in condimentum dolor. Nam in sagittis urna. Ut eget ornare erat. Vivamus nec elit ut sapien tristique aliquet a nec urna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Maecenas varius pellentesque diam. + +Ut eget libero iaculis urna porta iaculis vel ac eros. Sed sed mi et libero porta consequat a in libero. Sed in imperdiet ipsum, sit amet ultricies dui. Curabitur sit amet consectetur eros. Praesent nunc tellus, feugiat ac laoreet consectetur, accumsan nec magna. Praesent at elementum orci. Donec dapibus venenatis libero, id tincidunt libero euismod ac. + +Aenean sit amet ex placerat, molestie sapien a, volutpat turpis. Vivamus elementum lacinia nisi a convallis. Donec a sagittis turpis. Curabitur pulvinar laoreet dolor id consequat. Aliquam aliquam feugiat magna, vitae sagittis lorem mattis ut. Donec sed auctor nulla. Ut convallis, neque vitae faucibus efficitur, nisi justo pharetra odio, vitae tristique purus lorem et nisi. Quisque eleifend aliquam arcu nec maximus. Nunc mauris elit, finibus nec gravida non, maximus nec nibh. Sed eu ipsum in arcu gravida maximus. Maecenas massa dolor, ullamcorper eget odio eu, congue ornare leo. Suspendisse venenatis ultricies imperdiet. Nam finibus orci vitae sagittis finibus. Pellentesque sit amet dapibus diam. Nam eu nunc elit. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam euismod turpis nec urna lobortis, ut malesuada ex suscipit. Fusce sed viverra risus. Pellentesque tristique nulla pulvinar tincidunt accumsan. Nullam vitae nibh imperdiet erat rutrum pellentesque et sit amet quam. Aenean laoreet ultricies hendrerit. Duis elementum tellus a feugiat vulputate. Aliquam aliquam enim placerat dolor viverra, non suscipit lorem ultrices. + +Proin interdum vitae lorem quis viverra. Praesent nec tempor dolor, vitae sagittis turpis. Etiam sit amet imperdiet sapien, ac laoreet arcu. Proin aliquam, risus nec maximus dapibus, dui diam elementum dolor, vitae tempor augue lorem vel justo. Aenean ac egestas dolor. Ut aliquet, felis at fringilla venenatis, leo nisl ullamcorper ex, vitae pellentesque turpis orci quis lectus. Nullam vitae suscipit metus. Integer congue, ante in lacinia rhoncus, erat lorem interdum elit, egestas suscipit lectus nunc non orci. Nunc vel viverra quam. Mauris sit amet sodales tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam quis sollicitudin libero. In nec venenatis orci. + +Integer non quam fringilla, tristique nulla id, gravida arcu. Aenean scelerisque lacinia magna. Praesent nunc sem, lobortis non convallis rhoncus, rutrum vitae ante. Sed et orci ut erat viverra bibendum a et lectus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce sit amet eros eget dolor pharetra vestibulum. Nullam vestibulum, massa dictum finibus egestas, orci nulla pulvinar sem, nec tempor libero lacus eu ante. Nam lacus erat, gravida eu placerat sit amet, facilisis sed urna. Suspendisse efficitur felis non ornare dictum. Nam sit amet nulla enim. Nulla eget scelerisque est. Suspendisse lacinia velit arcu, id lobortis felis facilisis in. + +Suspendisse potenti. Nulla porttitor metus ut nunc dictum tristique. Cras sit amet tortor eget ligula tristique efficitur. Ut at nisi id purus imperdiet laoreet. Sed sit amet malesuada urna. In nunc dolor, luctus ac condimentum ut, dapibus vel metus. Suspendisse pretium urna eget libero convallis vestibulum. Integer ut mauris hendrerit ex posuere euismod sed sed odio. Nulla egestas libero sit amet magna venenatis faucibus. Pellentesque semper vestibulum elit, et pretium felis scelerisque non. Suspendisse aliquet leo arcu, sed dapibus ex semper non. Donec lacinia dictum dignissim. Maecenas ipsum nunc, aliquet eget consectetur sit amet, aliquet vitae odio. + +Proin consectetur blandit feugiat. Nulla ac dictum quam. Vivamus suscipit scelerisque ipsum, vitae consequat neque sagittis id. Donec eu augue hendrerit, varius ipsum at, cursus massa. Morbi id augue id ipsum porttitor mollis. Phasellus nec libero eu arcu finibus dapibus. Nullam nec pharetra ante. Aenean sit amet urna eget justo tempus pharetra ut nec mi. Pellentesque viverra, ligula nec elementum ornare, ante elit eleifend enim, nec dignissim ligula elit in nibh. Vestibulum facilisis, felis eu condimentum dapibus, ante risus tincidunt urna, ut posuere dui augue ut ante. + +Nam arcu lacus, accumsan ac dolor in, egestas euismod est. Sed consectetur mauris et enim tincidunt semper. Donec sit amet pellentesque diam. Fusce viverra arcu a placerat fermentum. Nullam euismod dui eget egestas ultrices. Duis euismod viverra tortor eget eleifend. Pellentesque vitae neque dapibus, bibendum mi eu, auctor velit. + +Pellentesque congue consectetur turpis, eget auctor dui suscipit vel. Aliquam a sollicitudin turpis. Praesent nec blandit tortor. Suspendisse non nunc at urna tincidunt elementum. Integer eu elit nec urna maximus blandit quis at tortor. Nulla laoreet elit a purus tempus, et tincidunt lorem sagittis. Aenean semper erat eu neque hendrerit ornare. Cras posuere lorem nec orci vulputate finibus. Fusce tempor ex ac lacus gravida venenatis. + +Phasellus laoreet, libero vel fermentum euismod, sem diam accumsan quam, vitae gravida arcu est at sem. In bibendum, felis at viverra congue, felis nunc fringilla libero, ut scelerisque erat massa ut neque. Suspendisse potenti. Cras faucibus dolor vel tortor blandit, quis imperdiet magna semper. Nullam ut urna dapibus, vulputate est a, hendrerit purus. Vivamus vestibulum nisi a tempus placerat. Morbi vitae ultricies lacus. + +Nunc vel efficitur urna. Fusce bibendum suscipit mauris, quis imperdiet nisl bibendum non. Nam sed nisi imperdiet, pellentesque sem sed, pellentesque diam. Praesent luctus feugiat odio, eget fermentum lectus ullamcorper non. Phasellus pulvinar lectus sed ligula semper lobortis. Pellentesque fermentum ultricies fermentum. Quisque id turpis vel orci rutrum rhoncus id vel metus. Vivamus ex leo, accumsan eu luctus sit amet, tincidunt vitae erat. Sed eu mollis odio, ut ultricies lacus. Duis enim odio, pellentesque non velit vel, aliquam blandit erat. Mauris feugiat felis fermentum purus blandit, id rhoncus lorem tempus. Integer cursus, nunc vitae laoreet commodo, nunc lacus venenatis orci, quis eleifend risus est nec quam. Nulla tincidunt nunc ac purus tincidunt, eu molestie ipsum sollicitudin. + +Vestibulum ac varius velit, non suscipit nulla. Vestibulum a sagittis tortor. Suspendisse vestibulum felis quam, eget blandit nulla dictum vel. Praesent eu tellus ut lacus facilisis ultrices. Etiam fringilla nulla nec leo viverra faucibus. Sed nec sollicitudin nisl. Aenean libero massa, ornare sed elit ut, interdum fermentum arcu. + +Aliquam maximus diam et elit dapibus, eget condimentum sapien finibus. Etiam gravida nunc dapibus facilisis feugiat. Sed quis massa ligula. Nunc eleifend dolor lacus, at dapibus nisi vulputate eget. Etiam vel euismod urna, quis molestie nibh. Vestibulum bibendum erat non dictum sollicitudin. Suspendisse sed placerat lacus. Cras sollicitudin quam justo, a condimentum urna feugiat a. Quisque mauris libero, ultricies at ligula a, facilisis euismod ante. + +Morbi hendrerit maximus sapien ut auctor. Nullam nisi erat, aliquam ac metus eu, fermentum laoreet augue. Nam ultricies mi at vulputate laoreet. In ipsum est, blandit sit amet lorem id, egestas aliquam odio. Praesent venenatis lobortis mi. Aenean eu lacus consequat, mattis enim et, pellentesque leo. Duis convallis facilisis placerat. Donec porta, enim eget placerat congue, nisi augue faucibus odio, et fringilla purus elit vitae felis. + +Nulla et tellus a libero pellentesque eleifend vitae mattis nisi. Nunc placerat neque et sapien lobortis, aliquet pharetra nunc vulputate. Suspendisse potenti. Etiam ullamcorper lacinia varius. Suspendisse sit amet malesuada magna. Nam molestie mi nec diam tempus laoreet. Suspendisse urna tellus, scelerisque vitae purus et, dapibus fermentum felis. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec scelerisque eget neque sed hendrerit. Donec at ligula augue. Donec vulputate massa nunc, a feugiat nunc elementum ut. Donec pulvinar libero sit amet ante faucibus sagittis. Mauris quis diam ultrices, tristique sapien nec, tempus urna. Morbi hendrerit odio sit amet leo lacinia, non egestas diam condimentum. Suspendisse efficitur sapien eget elit euismod tristique. Duis posuere vestibulum risus id sodales. Ut eget mi in urna rutrum molestie non nec dolor. Curabitur sollicitudin pharetra lacus, in sodales tortor tempor vitae. Nullam rhoncus arcu vel euismod varius. + +Aenean malesuada, purus quis volutpat sollicitudin, lectus risus lacinia mi, a facilisis ante lacus a dolor. Aenean vel aliquam tortor. Vivamus ornare, tellus at malesuada suscipit, quam dolor egestas erat, id convallis enim augue a enim. Aenean ultricies sodales placerat. Vivamus vel erat ac mi vulputate commodo nec eget urna. Suspendisse potenti. Mauris quis dapibus est, id tristique libero. + +Suspendisse ex diam, imperdiet quis dui id, interdum sodales elit. Maecenas at nunc ligula. Etiam imperdiet convallis magna sit amet tristique. Proin hendrerit laoreet magna, eget malesuada elit rhoncus et. Curabitur eget odio imperdiet, molestie sapien pretium, efficitur turpis. Aliquam sed congue arcu. Pellentesque vitae mauris id neque pulvinar tempor. Donec lobortis tellus id gravida dictum. Nulla et varius ex. Vestibulum pharetra eu quam dapibus finibus. Nullam accumsan sagittis turpis. Mauris fermentum orci et tortor tempus, ac tristique odio sollicitudin. Duis nec elit et magna imperdiet molestie eget vel ante. + +Suspendisse tempus augue nec massa molestie aliquam. Pellentesque vestibulum, erat sit amet fringilla fermentum, sapien lorem tempor felis, at efficitur augue erat quis nisi. Proin nec felis sagittis, sollicitudin turpis eu, fringilla leo. Vestibulum at augue nec dui vestibulum aliquam a at metus. Duis ullamcorper eleifend bibendum. Maecenas viverra mauris lacus, et consequat nisl pharetra eu. Praesent rutrum diam eu mi aliquet, non pellentesque est suscipit. Vestibulum massa leo, blandit eget mi at, cursus faucibus nunc. Praesent nec bibendum libero, vitae cursus libero. Sed consequat vitae eros nec maximus. + +Pellentesque et tortor eget lectus feugiat venenatis. Integer ornare nisl quam, nec imperdiet justo finibus at. Morbi malesuada, ante sit amet rutrum tempor, risus lorem tristique urna, egestas varius purus ex ut sem. Etiam sit amet feugiat sapien. Etiam quis risus commodo, eleifend velit quis, tincidunt enim. Mauris fermentum lacinia velit quis efficitur. Nunc sit amet lacus sit amet urna viverra feugiat. Nullam sagittis rhoncus suscipit. Aliquam eu sem ligula. Sed sodales risus et tortor lacinia tristique. Nulla massa augue, malesuada sit amet euismod ac, euismod placerat nulla. Sed lobortis ex nec lacus facilisis, nec semper mauris ultrices. Quisque ornare non felis vitae pellentesque. Donec mattis ut magna sed imperdiet. Integer viverra tempus feugiat. + +Donec laoreet aliquam imperdiet. Fusce justo neque, dictum vitae fringilla vitae, euismod sed augue. Fusce sodales, lectus a congue bibendum, ante eros pharetra tortor, eu sollicitudin libero ipsum sit amet felis. Curabitur sed condimentum quam, in iaculis ante. Ut feugiat dictum odio, vitae hendrerit lacus volutpat sed. Sed scelerisque et metus nec gravida. Mauris mattis porttitor magna, ut maximus justo sagittis vitae. Donec at metus ut leo gravida vehicula in sed diam. Vestibulum dignissim suscipit sagittis. Nam varius et arcu sit amet lacinia. Sed eget elit rhoncus, elementum dolor a, vehicula orci. Nam vitae tellus sapien. Phasellus massa lorem, commodo quis placerat in, semper faucibus tortor. + +Vivamus id dolor mi. Curabitur sagittis posuere quam in mollis. Phasellus finibus ipsum vel elit tincidunt, a bibendum ligula vulputate. Suspendisse quis purus nisl. Integer mi sem, posuere quis nisi a, consequat consequat magna. Vestibulum bibendum mauris in risus auctor fringilla vel at lacus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus venenatis mauris at mattis eleifend. Donec tempus ipsum ac nisl convallis ultrices. + +Nullam eu nibh ut erat vehicula accumsan. Vivamus vitae faucibus libero. In in luctus odio. Nulla nec enim pharetra, fermentum erat in, tempor turpis. Sed ac magna suscipit, dignissim elit id, faucibus libero. Maecenas placerat in dolor et faucibus. Sed maximus purus dolor, non egestas massa rutrum non. Nunc ut rhoncus lacus. Sed sit amet dolor eu sapien sagittis molestie. Aenean ipsum erat, rutrum a diam et, varius porttitor ligula. Phasellus fermentum fermentum felis. + +Phasellus odio massa, lacinia ac hendrerit vestibulum, placerat sit amet erat. Praesent eget justo scelerisque, congue est in, pharetra mi. Etiam blandit turpis dui, a faucibus ipsum viverra vitae. Praesent sed scelerisque arcu. Cras nec venenatis ipsum. In et tempus metus. Pellentesque cursus quis nibh quis porttitor. Duis leo lacus, pretium eget rutrum eu, tincidunt lobortis tellus. Cras in erat tristique arcu faucibus luctus sit amet tempus erat. Nullam placerat, est a interdum iaculis, purus justo convallis arcu, at mattis erat sem tempor velit. Curabitur sapien sapien, tempor eget sodales vitae, blandit ac libero. Proin eget sem et arcu malesuada convallis non a est. + +Ut id sagittis urna. Morbi est mauris, molestie quis magna ut, convallis porta justo. Etiam in vulputate sem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nullam consectetur enim nisl, sit amet pulvinar turpis elementum at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum convallis cursus libero vel feugiat. + +Curabitur vel scelerisque metus. Etiam aliquet faucibus quam, vel aliquet est mattis quis. Pellentesque pulvinar sodales augue, a dignissim sem tristique vitae. Pellentesque dolor quam, pharetra vitae rhoncus ut, blandit at elit. Sed dignissim diam turpis, ac condimentum justo iaculis vel. Donec facilisis eros sit amet est dapibus, vel pellentesque eros consequat. Integer euismod mollis metus, vel rutrum risus imperdiet ac. Fusce semper dolor sit amet mi congue accumsan. Maecenas sagittis magna justo, vel varius ante scelerisque id. Fusce at urna sapien. Aliquam dui sapien, facilisis eu magna laoreet, feugiat tincidunt elit. Fusce iaculis sit amet erat id dignissim. Suspendisse sollicitudin laoreet consequat. Pellentesque eu mollis quam, vel dapibus libero. Duis mattis tortor vitae orci vehicula, et viverra ipsum lobortis. Quisque iaculis sapien est, id sagittis tortor rutrum at. + +Duis ut condimentum risus, et venenatis nulla. Praesent urna ex, faucibus eu mollis nec, lobortis vitae neque. Vivamus a malesuada tellus. Quisque bibendum dolor nec massa porta, nec malesuada magna auctor. Phasellus non auctor turpis, et bibendum risus. Ut pellentesque justo non neque convallis, ut imperdiet diam sagittis. Nunc arcu nisi, rutrum sagittis neque elementum, finibus consequat felis. Proin euismod elit eu urna tristique ultrices. + +Curabitur id hendrerit ipsum. Aliquam tortor nisi, dignissim vel sodales a, ultricies a ipsum. Etiam commodo arcu sed volutpat varius. Proin vehicula lacinia tempor. Vestibulum feugiat nibh eu ante tempor efficitur. Etiam eleifend a orci eu euismod. Cras a tortor risus. Cras nec urna non urna malesuada maximus vel et ipsum. In ullamcorper porttitor dolor, at fringilla tortor tristique ac. Nulla gravida tortor nec dolor convallis, accumsan sollicitudin dui luctus. Vestibulum euismod vestibulum semper. Nam semper lacus dolor, vitae rhoncus nisi blandit nec. Phasellus turpis dolor, posuere sed sodales sit amet, interdum ut arcu. + +Sed blandit nulla et sem vulputate, et semper lacus posuere. Proin velit lorem, sagittis tincidunt aliquet vitae, fermentum sed orci. Ut interdum ultrices dolor eu tempor. Quisque pretium vulputate dui at blandit. Aenean pretium urna sed purus dapibus aliquam. Mauris tristique augue pretium magna scelerisque, vel cursus sapien porttitor. Nullam neque justo, aliquam non neque id, lobortis facilisis nibh. Duis molestie dui eu augue tristique porttitor. Sed posuere justo massa, efficitur vulputate erat posuere non. Cras quis condimentum tellus. + +Etiam eleifend ipsum ut justo viverra porttitor. Nam bibendum enim nec ligula vestibulum, vel fringilla velit posuere. Praesent leo enim, varius sit amet nulla ut, sodales sollicitudin nunc. Phasellus hendrerit varius ex quis tempus. Suspendisse maximus laoreet enim, ut bibendum tortor. Ut non magna vehicula augue condimentum scelerisque. Aenean efficitur elit vel rhoncus euismod. Vestibulum sit amet sollicitudin dui. Quisque ac diam nibh. Nullam quam enim, volutpat eu turpis et, posuere consectetur neque. + +Ut tincidunt semper dictum. Etiam varius enim sit amet metus consequat malesuada in at ligula. Cras nisl dui, tincidunt a urna et, porttitor rhoncus velit. Mauris interdum imperdiet lectus ac mollis. Aliquam sollicitudin ornare mi, a varius nulla faucibus aliquet. Nunc fermentum tempor ullamcorper. Pellentesque laoreet libero condimentum pellentesque pharetra. Nullam sed eros vel erat vestibulum dictum. Nulla nec interdum dui, quis finibus nunc. + +Sed ac turpis in mi ultricies finibus sit amet et diam. Pellentesque sagittis non ligula et convallis. Suspendisse interdum est aliquet erat rhoncus semper. Donec pretium turpis ante, vel posuere mauris facilisis vel. Vivamus nibh ligula, pharetra sit amet hendrerit nec, vulputate ut sapien. Nulla ullamcorper condimentum metus vitae imperdiet. Ut eget ex purus. Nulla dapibus malesuada pharetra. Praesent sit amet ornare turpis. Nulla pulvinar felis eget arcu mollis vulputate. Vestibulum eget semper felis. Donec viverra justo id mi tempor, a mollis ipsum porttitor. Maecenas aliquet volutpat ante eget tempor. Duis ipsum nisi, scelerisque quis metus vitae, blandit faucibus nisl. Mauris rutrum nisi sed lorem dictum ultricies. + +Nulla dictum arcu augue, aliquet ornare ligula suscipit ut. Integer libero erat, bibendum quis arcu at, consequat luctus sem. Ut ut erat tincidunt, porta erat efficitur, bibendum neque. Maecenas eget convallis sapien. Nullam facilisis ex et lorem fringilla, ut convallis leo dictum. Duis porttitor, ex eu venenatis faucibus, erat augue dapibus leo, sit amet scelerisque neque dui sed arcu. Praesent at diam nec sem sodales condimentum. Vivamus vehicula urna tellus, a semper ipsum cursus id. Duis auctor enim erat, laoreet volutpat dui rhoncus maximus. Suspendisse pellentesque euismod lacus, at auctor purus. Suspendisse volutpat imperdiet diam, id laoreet est egestas at. + +Fusce lobortis ex vel condimentum convallis. Vivamus fermentum convallis dolor quis rutrum. Donec tempus ornare maximus. Mauris quam neque, dignissim rutrum maximus sed, volutpat sed lectus. Donec id augue gravida, pretium purus eu, sagittis tellus. Maecenas tincidunt gravida felis nec pulvinar. Fusce turpis urna, sodales non rhoncus nec, sodales ac ipsum. Suspendisse risus felis, imperdiet id malesuada fermentum, ultricies et erat. Suspendisse potenti. Pellentesque tellus ipsum, dapibus eget pellentesque eleifend, venenatis sed risus. Ut sed mollis nisl. Aliquam lobortis aliquam ipsum, et ornare magna bibendum ut. Proin quis justo vitae neque tincidunt maximus ultricies et purus. Nullam non semper turpis. Quisque non mauris odio. + +Donec commodo tempus egestas. Vestibulum at diam vel mi tincidunt finibus. Donec aliquam magna id eros tristique finibus. Cras ut enim sapien. Proin gravida risus a nisi dapibus, ac vulputate nunc laoreet. Donec at leo vel nulla iaculis feugiat sed et ante. Nulla a arcu at ligula sollicitudin semper sed eget est. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Ut dolor magna, luctus id elit ut, interdum viverra lacus. Aenean sed tempor metus. Aenean arcu dui, eleifend quis est sed, luctus lacinia nulla. Morbi id luctus libero. Maecenas sodales leo eu sapien maximus porta. Praesent gravida augue eu ligula pretium cursus. + +Duis aliquam luctus tortor, eu facilisis quam sodales sed. Phasellus feugiat venenatis lorem, in scelerisque est efficitur quis. Cras quis nisl nisl. Quisque magna felis, tempus nec pharetra et, tempor id ligula. Pellentesque sed dignissim velit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris convallis iaculis posuere. Nullam orci velit, venenatis eget enim quis, molestie consequat dolor. Nulla egestas risus vestibulum sagittis blandit. Nullam dui lacus, tempus euismod sapien a, eleifend sodales justo. In id tortor neque. Vivamus faucibus ante ac odio vestibulum, vitae condimentum nibh consectetur. In rutrum hendrerit est, sit amet mollis ex aliquet tempor. + +Donec non tincidunt sem. Nullam dignissim felis nibh, et accumsan felis tristique sed. Maecenas id massa erat. Ut justo ligula, aliquet eu condimentum a, aliquet eget ex. Phasellus et neque eu nisl consectetur ullamcorper. Sed gravida efficitur nisi, vitae posuere nisi tincidunt sed. Duis vitae molestie metus, vitae finibus urna. Integer at lacus vitae turpis convallis feugiat a dignissim libero. In dolor nibh, aliquam eu malesuada in, tincidunt vitae nisi. Nullam at lectus risus. Integer vitae ligula interdum, congue nibh id, tincidunt dolor. + +Mauris risus quam, mollis eu consequat vitae, molestie sit amet risus. Vestibulum congue vulputate nibh sit amet ullamcorper. Nullam elit justo, hendrerit euismod elit nec, gravida tincidunt ipsum. Aenean tellus tortor, commodo vitae cursus vitae, finibus quis mi. Nam in tellus scelerisque, cursus magna a, elementum tellus. Praesent ut lacinia justo. Donec consectetur, mi nec faucibus viverra, nisl justo suscipit est, in consequat ex urna vitae orci. Fusce molestie feugiat ex sed dictum. Phasellus interdum eros dapibus sodales volutpat. Morbi elementum sapien ante, quis sagittis justo fermentum at. Maecenas vestibulum massa quis eleifend rhoncus. Praesent auctor ex at urna pellentesque facilisis. + +Duis tincidunt porta quam, in commodo orci dapibus interdum. Donec mattis erat ante, nec faucibus magna pharetra id. Aliquam dignissim ultricies auctor. Duis quis ex mi. Phasellus ipsum mi, volutpat quis augue nec, dapibus aliquet lectus. Aenean id eros mi. Fusce rhoncus iaculis magna, commodo commodo nibh pellentesque ut. Donec consectetur lacinia erat ut luctus. Mauris efficitur erat vitae sapien fringilla sollicitudin. Maecenas a egestas ipsum. Aenean placerat congue augue, ut condimentum lorem accumsan in. Morbi ac quam nunc. + +Nunc posuere faucibus semper. Integer at convallis ex. Duis a purus molestie, dignissim mi quis, consequat nulla. Proin tempus congue ligula, sit amet ultricies enim consequat ut. Nunc ac est at nisl euismod cursus. Pellentesque vulputate molestie ipsum. Praesent varius, lacus non lobortis blandit, nibh lacus scelerisque nisi, sit amet interdum ligula tellus ac purus. Vestibulum sed quam tempor, pharetra tellus ullamcorper, tincidunt est. Nulla tempus tortor nec erat tempus eleifend pellentesque eget purus. Duis gravida dui justo, ac commodo ipsum mollis et. Nullam vitae nibh enim. Ut sodales mi eu diam sagittis, quis luctus tortor euismod. + +Maecenas a augue ac augue varius rhoncus. Fusce nunc turpis, mattis eu iaculis a, dapibus nec purus. Pellentesque imperdiet sit amet purus a blandit. Morbi augue tellus, venenatis nec tortor in, consectetur tincidunt nulla. Aenean purus libero, volutpat quis neque vitae, mattis efficitur eros. Donec sodales venenatis augue, sed faucibus magna accumsan convallis. Pellentesque efficitur elementum imperdiet. Cras at condimentum leo. Curabitur feugiat ut nisi facilisis commodo. + +Sed commodo sapien quam, quis vulputate orci lobortis nec. Aenean luctus dictum ipsum, non consequat sapien molestie vel. Proin blandit libero tortor, vitae efficitur tortor hendrerit at. Sed eleifend eu velit eu tempor. Nunc auctor tincidunt mollis. Ut molestie, erat ut lobortis convallis, odio felis sollicitudin elit, vitae ultricies lorem neque et dui. Sed venenatis tempus ullamcorper. Integer eget elementum nulla. Maecenas a placerat ipsum. Etiam sagittis sagittis rhoncus. Aliquam faucibus magna id lacus pharetra, nec interdum lacus sodales. + +Mauris ipsum sem, venenatis non elit in, feugiat pretium lacus. Fusce eget tincidunt dolor. Nam pharetra lacus vitae diam bibendum, ac placerat tortor aliquet. Sed eget gravida orci. Ut in orci lorem. Morbi condimentum ligula dolor, in dictum mi vestibulum sed. Suspendisse eu velit finibus, tincidunt dolor malesuada, mattis est. Morbi iaculis sapien vitae metus facilisis fringilla. Donec sed volutpat neque. Mauris ipsum metus, venenatis cursus mattis quis, convallis ultricies purus. Aliquam quam magna, sagittis at mi quis, lacinia iaculis turpis. Praesent viverra, ex nec euismod lacinia, urna risus pretium odio, sit amet mattis metus eros non lectus. + +Nam sed vehicula est, ac aliquet mi. Aenean iaculis placerat orci, ut lobortis dui vestibulum convallis. Vestibulum eleifend ante sed lorem interdum lobortis ut vel tortor. Sed auctor id nisl sed bibendum. Fusce maximus vulputate mauris, tempus sollicitudin nunc efficitur vitae. Nulla pellentesque molestie leo, quis hendrerit enim. In vel dapibus nisi. Nam at dui quis eros porttitor volutpat in id magna. Maecenas quis quam a justo cursus aliquet viverra sit amet mauris. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut hendrerit est at augue pretium condimentum at ut velit. Suspendisse non gravida metus. + +Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Praesent lacinia commodo elit, sed fringilla ipsum imperdiet ut. Proin a dictum ante. Suspendisse potenti. Praesent ac nulla volutpat, ultricies diam vel, auctor risus. Nullam aliquam elit vel quam suscipit molestie vel ac dolor. Ut pharetra finibus elit, id vulputate ex dignissim in. Ut ac finibus urna, a luctus magna. + +Fusce suscipit convallis eros nec blandit. Cras quis lectus nibh. Donec pulvinar rhoncus pulvinar. Vivamus nulla nisi, vestibulum vel neque et, dictum gravida ex. Vestibulum in arcu vitae nibh auctor rhoncus ac in massa. Sed semper enim ac dui sollicitudin porttitor. Etiam ante erat, laoreet sed dolor eget, cursus commodo lorem. Curabitur maximus tincidunt nulla at molestie. Phasellus ultrices felis a cursus facilisis. Nullam a semper tortor. Nulla ac vulputate justo. Mauris maximus nisi quam, elementum eleifend nulla condimentum nec. Aenean congue tincidunt mi, id mollis orci blandit vitae. Integer placerat orci at nisl vestibulum lacinia. + +Sed egestas posuere egestas. Quisque pulvinar velit commodo felis accumsan, a consequat purus laoreet. Ut at arcu at augue sodales rhoncus. Ut non nisl dui. Sed sed nulla quis orci eleifend auctor ut et sem. Aliquam erat volutpat. Donec egestas tincidunt leo in interdum. Donec varius odio nulla, id porttitor erat venenatis ut. Nulla ac nisl ut enim placerat congue. Fusce consequat purus eget risus luctus finibus. Donec in blandit odio. Sed vestibulum nisl ut diam vestibulum volutpat. Donec magna quam, tempor eget velit quis, blandit tristique lacus. Pellentesque et orci dui. + +Integer tempor mollis purus ut volutpat. Pellentesque efficitur cursus neque in ullamcorper. Integer ac tincidunt lacus, at venenatis tellus. Curabitur convallis commodo enim a convallis. Aenean sagittis sodales nibh, sed eleifend diam ultricies at. Vestibulum erat mauris, lobortis ac tempor a, viverra non sem. Nulla non ipsum mollis, dignissim elit a, laoreet enim. + +In laoreet purus eget orci rhoncus viverra. Quisque vel metus quis nulla hendrerit viverra. In consectetur velit vitae purus vestibulum, in hendrerit odio sollicitudin. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ut est sed ligula tincidunt fermentum nec ac nulla. Nam in sagittis velit. Vivamus vel dapibus erat, ullamcorper sollicitudin metus. + +Quisque pulvinar nulla eget mi mattis, ut convallis nulla ullamcorper. Vivamus placerat mauris vel elementum aliquet. Sed ac accumsan eros. Vivamus vitae rhoncus urna. Fusce gravida cursus varius. In posuere finibus leo cursus molestie. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris vulputate mi ut nunc finibus convallis. Praesent luctus erat eu urna facilisis convallis. + +Nam efficitur tempus augue varius porttitor. Pellentesque scelerisque dolor scelerisque, dignissim massa eget, iaculis nibh. Praesent viverra molestie dui a pulvinar. Mauris malesuada mattis felis, vitae sagittis metus pellentesque viverra. Phasellus faucibus congue blandit. Pellentesque mattis, justo sed imperdiet rutrum, metus metus porta nisi, vel malesuada lorem massa at dolor. Nullam nisi eros, tristique quis mollis ac, hendrerit nec leo. Praesent viverra molestie vestibulum. Nullam eu aliquam risus, at dignissim diam. Quisque blandit fermentum erat, sed ullamcorper augue pellentesque in. Integer eleifend libero non lacus sagittis auctor. Aliquam volutpat interdum accumsan. + +Etiam in eros porta, bibendum lacus eget, feugiat orci. Integer massa dui, commodo ac luctus nec, ornare id velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis dignissim scelerisque ex. Aliquam tempus cursus nibh, sed scelerisque libero sagittis ut. Morbi finibus vestibulum nulla, nec aliquam urna venenatis vulputate. Pellentesque ultricies, lorem a dignissim bibendum, augue nisi eleifend libero, cursus ultrices nulla purus a nunc. Quisque dictum pulvinar turpis vitae finibus. Etiam eget ultrices diam. Sed commodo enim ante, fermentum rhoncus tortor tincidunt ac. Suspendisse fermentum aliquet erat non posuere. Aenean vel dapibus lorem. + +Aenean lorem libero, rutrum vel egestas vel, pellentesque ut ante. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis diam, pellentesque sit amet ultricies sed, vestibulum in leo. Morbi aliquet, quam ut fringilla sollicitudin, justo risus suscipit mi, in vulputate neque erat ut turpis. Duis auctor dui non urna sagittis dictum. Vestibulum nec felis vel sapien congue volutpat a a orci. Phasellus tincidunt libero ac lorem feugiat, ac elementum lectus eleifend. Praesent sit amet est id massa convallis congue. Integer ac nunc eget orci pharetra vehicula. Mauris et ultricies diam. Nunc et pellentesque lacus, eget bibendum sapien. Morbi condimentum mi ac metus euismod convallis. Proin consequat rhoncus varius. Maecenas feugiat elementum vulputate. + +Vestibulum tempor feugiat dapibus. Ut ornare in augue non euismod. Nulla faucibus gravida condimentum. Curabitur pharetra dui non lorem sagittis iaculis. Quisque vitae nibh magna. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce quis pharetra sem, sed aliquet lectus. + +Fusce volutpat tempor tincidunt. Pellentesque ultricies imperdiet tincidunt. Integer porta dictum lorem, non luctus tellus bibendum sit amet. Quisque posuere felis et est dapibus, commodo rutrum leo molestie. Fusce sodales felis sed sem pharetra pretium. Praesent nec sollicitudin nisl. Donec volutpat convallis elementum. Duis id libero augue. Nulla facilisi. + +Nulla viverra, urna nec efficitur vulputate, metus odio lacinia purus, in sagittis elit erat vel massa. Fusce ligula leo, finibus non felis dignissim, dictum ullamcorper odio. Phasellus vel eros eu sem venenatis sagittis a nec nulla. Pellentesque sapien elit, lobortis quis dictum et, maximus vitae metus. Curabitur quis aliquam eros. Quisque cursus condimentum urna vel efficitur. Etiam aliquet lorem ut varius suscipit. In viverra molestie felis vitae vehicula. Curabitur cursus, nunc sodales faucibus ullamcorper, urna magna dapibus velit, accumsan blandit mi quam nec justo. Donec ac dui lorem. Sed eu sagittis nulla. Suspendisse ut ante lacus. Fusce non tristique felis. Nulla convallis consectetur arcu, semper egestas urna efficitur quis. Nulla ac turpis at leo pretium maximus. Morbi cursus diam ac purus imperdiet, non commodo tellus cursus. + +Mauris ut eros suscipit, suscipit nisi vel, porttitor sapien. Morbi in nulla sapien. Cras maximus pretium justo, vel eleifend urna vulputate sed. Aenean egestas nisl ex. Curabitur sed pharetra ligula. Nulla blandit lorem id mauris tincidunt, at elementum risus aliquet. Quisque id interdum dui. + +Aenean nec elit condimentum ex viverra hendrerit. Pellentesque blandit nibh elit, a ultrices orci vestibulum vitae. Donec ultricies malesuada justo ac luctus. Pellentesque laoreet nunc a sem varius, ac dapibus ligula volutpat. Aenean sem felis, aliquam nec ullamcorper quis, ullamcorper non ante. Pellentesque libero leo, sagittis nec tempus a, tincidunt eget risus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec elementum massa vel diam suscipit suscipit. Suspendisse eget neque porta, cursus orci eget, imperdiet libero. + +Vivamus faucibus vulputate sapien, non pulvinar ex sodales vitae. Maecenas consequat est urna, id faucibus eros dictum at. Vestibulum tortor mi, porta vitae sagittis sed, convallis quis enim. Vestibulum gravida justo pulvinar sem ornare, efficitur dictum neque varius. Nullam egestas congue tellus vitae interdum. Nam lectus erat, porta vitae lorem non, mollis semper velit. Nulla vestibulum tincidunt elit. Phasellus sed vulputate leo. + +Sed efficitur quam ac iaculis volutpat. Praesent nec feugiat ex. Aenean at ligula iaculis, imperdiet lacus et, condimentum magna. Integer euismod leo id mauris condimentum, quis semper lacus blandit. Sed volutpat neque urna, sit amet ullamcorper est dignissim non. Vestibulum tristique sollicitudin risus, sed hendrerit massa finibus id. Vestibulum sit amet risus non eros vehicula malesuada. Nam facilisis lacus sed quam pulvinar, at fermentum lectus tincidunt. + +Vivamus laoreet sem nec odio blandit, ut ornare sem egestas. Suspendisse potenti. Nulla aliquam pretium volutpat. Maecenas a orci suscipit, aliquam eros a, maximus urna. Aliquam eu gravida justo, et hendrerit justo. Suspendisse a commodo lorem, quis viverra nisl. Nulla vel pellentesque nisl. Nulla ligula tellus, vehicula sit amet turpis in, sodales tincidunt tellus. Proin ut nibh sed ipsum porta dignissim vel sed mauris. Interdum et malesuada fames ac ante ipsum primis in faucibus. In hac habitasse platea dictumst. Vestibulum efficitur nulla rutrum, accumsan lacus at, dignissim tortor. Morbi egestas metus ut nibh tincidunt posuere at sed elit. Integer eget hendrerit nulla. + +Donec sagittis sagittis tempus. Proin iaculis neque vehicula commodo faucibus. Pellentesque erat ante, vehicula sed efficitur vitae, varius non sem. Mauris pellentesque, felis nec egestas scelerisque, nulla nunc fringilla arcu, feugiat fringilla quam neque in elit. Nullam ut ex odio. Mauris enim risus, consequat at suscipit at, pulvinar ut arcu. Fusce mollis sem sed tellus tempus pellentesque. In pharetra, libero sed tristique vestibulum, massa velit egestas risus, at mollis augue lorem sit amet turpis. Mauris risus dui, sagittis vitae congue ut, condimentum ut augue. Quisque non sollicitudin purus, sit amet consectetur sapien. + +Sed scelerisque ipsum sed augue varius, sit amet ultricies purus posuere. Etiam vehicula at nunc in posuere. Cras eget risus a sapien mollis facilisis. Morbi vel purus auctor, faucibus mauris non, malesuada leo. Donec venenatis consectetur libero in pretium. Ut efficitur molestie metus id scelerisque. Nunc dolor justo, pharetra vel sagittis vitae, placerat ut justo. Donec tempor fermentum est semper auctor. Nullam tincidunt risus non risus elementum varius. Suspendisse euismod augue metus, nec pellentesque enim sagittis ac. Morbi et lectus quis justo commodo varius commodo vel risus. In bibendum purus in erat ullamcorper, sit amet dignissim sapien scelerisque. Quisque tempus elementum rutrum. + +In viverra sollicitudin pretium. Sed finibus scelerisque sollicitudin. Nulla lobortis purus in lectus iaculis, a ornare ex accumsan. Morbi malesuada mollis placerat. In hac habitasse platea dictumst. Pellentesque sed dui quis odio scelerisque molestie eu nec libero. Proin viverra magna ligula, at luctus lacus posuere sed. Nullam non congue mi. Praesent non mattis neque, sit amet tempus diam. Mauris eget cursus lacus, id dictum mi. + +Sed semper velit in vehicula tristique. In lobortis est augue, et maximus tellus iaculis ut. Proin quis ex maximus erat iaculis imperdiet. Curabitur ultrices turpis ac nibh congue ornare. Fusce a aliquet felis, nec sodales tellus. Nunc mauris ante, feugiat et facilisis at, pharetra ultricies dui. Integer felis metus, porttitor ac ipsum vitae, placerat varius ex. Morbi in dui a nunc aliquam posuere. Nulla tempus tortor ac lorem consectetur sodales. Praesent sit amet placerat diam. Curabitur sodales mauris ante, et tincidunt lacus ultricies quis. Nulla eu finibus nisl, sit amet volutpat leo. Donec ligula enim, feugiat non aliquet nec, congue interdum lorem. Pellentesque a nisi blandit, semper massa sit amet, auctor eros. + +Aenean ligula libero, commodo a erat at, tristique facilisis quam. Sed congue fringilla lacus, vel iaculis quam suscipit ornare. Curabitur non pretium mi. Donec condimentum odio dui, id malesuada ipsum porta ac. Aliquam imperdiet cursus ullamcorper. Duis cursus tincidunt nibh sit amet cursus. Aliquam urna orci, sodales ac ipsum at, placerat lobortis neque. Sed sit amet convallis felis, vestibulum finibus arcu. Phasellus venenatis egestas felis, id aliquam turpis iaculis non. Proin a lacus ut felis malesuada sodales. Duis elementum, eros ac placerat bibendum, quam nisl varius mauris, vel venenatis neque augue et justo. Nunc varius sem velit, vel tempus mi aliquet id. Fusce purus massa, mollis id nulla non, dignissim cursus massa. Sed sollicitudin interdum felis, nec eleifend nisl feugiat eget. Nulla rutrum id odio ut consectetur. Maecenas fermentum turpis vitae libero ullamcorper suscipit. + +Vestibulum auctor lectus ligula, non sollicitudin odio maximus nec. Cras faucibus, felis sed suscipit tempor, risus lorem consectetur est, eget pulvinar nibh dui eu dolor. Vestibulum pellentesque, ex aliquam vulputate laoreet, libero lorem rhoncus risus, vitae congue velit justo vitae nisi. Nullam placerat venenatis tortor, sit amet lacinia augue placerat nec. Quisque vulputate lectus vel pulvinar condimentum. Nullam at libero iaculis, mattis purus vel, tincidunt diam. Suspendisse eu ullamcorper eros. Nulla id eros odio. Ut enim leo, ultricies quis mauris sed, interdum commodo felis. Etiam enim lacus, scelerisque a interdum eget, mollis vel tellus. Phasellus vel vulputate orci. Nulla ornare leo sed arcu rhoncus convallis. Duis libero arcu, pulvinar ut tellus vitae, aliquam euismod magna. Donec semper nisi eget nulla tempus, sed condimentum massa sollicitudin. + +Suspendisse bibendum ante vitae ullamcorper semper. Praesent dui est, elementum nec lacus in, pellentesque accumsan sapien. Cras quis urna at lacus posuere commodo eget nec arcu. Nunc varius ante quis ligula rhoncus, ut dignissim metus commodo. Integer volutpat gravida nunc eget faucibus. Quisque imperdiet, mauris vitae cursus malesuada, lacus mauris tempus sem, ut accumsan purus lectus quis nisi. Vestibulum aliquam dui sed nisl laoreet, id posuere quam imperdiet. Aliquam ultricies non enim vel tempor. Donec sed feugiat justo, vel rutrum enim. Phasellus lorem quam, dapibus a tincidunt vulputate, pellentesque non lorem. + +Nunc quam diam, condimentum sit amet enim semper, hendrerit laoreet magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam et pulvinar leo, ac fermentum lectus. Sed erat ante, mattis ac tempor vitae, euismod at tortor. Ut sagittis fringilla scelerisque. Ut pulvinar molestie leo eu faucibus. Sed quis efficitur purus. Pellentesque nibh nisi, porta id orci id, sagittis sodales tellus. Ut venenatis velit a lectus lacinia, at ultrices nisi commodo. Vivamus eros orci, ornare vel ullamcorper elementum, posuere id ligula. Aliquam non massa pellentesque, semper ipsum scelerisque, dapibus leo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nulla pretium leo diam, sit amet bibendum lacus tempus quis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer quis pulvinar nibh. + +Mauris sed eros nisi. Cras malesuada, turpis vitae laoreet porta, sapien odio maximus nulla, ut efficitur mauris odio tincidunt elit. Nam ut urna eros. Sed at vestibulum risus. Nulla luctus pulvinar rhoncus. Pellentesque maximus ligula a est ullamcorper, sed tempus tortor ultrices. Curabitur ligula odio, mollis sit amet risus quis, tempor auctor magna. Suspendisse vel quam quis lorem commodo facilisis. Donec eu ipsum suscipit, molestie dui sed, fringilla dui. Proin placerat ex a lorem sodales convallis. Sed molestie quam mi. + +Fusce laoreet nec dolor id bibendum. Nunc condimentum vulputate massa lacinia fermentum. Donec maximus cursus eros sed porta. Nunc eu porta turpis. Nulla cursus et nisl eu elementum. Ut rutrum scelerisque aliquet. In tellus erat, rhoncus id tempus vitae, vestibulum non quam. + +Vivamus luctus elit a efficitur dapibus. Praesent magna mi, pellentesque sed accumsan dapibus, blandit at lectus. Praesent sodales erat diam. Pellentesque placerat lobortis enim, a dapibus ligula molestie ut. Phasellus dui augue, suscipit ac urna non, iaculis eleifend augue. Maecenas sed elit iaculis, pretium ligula lacinia, aliquam libero. Nulla sem mi, pellentesque viverra lorem vel, luctus vulputate augue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque fermentum nunc ex, at lobortis turpis congue vitae. Suspendisse iaculis elementum enim commodo facilisis. Vestibulum non neque tellus. Proin vitae sodales urna. Mauris interdum purus et neque tempus, vitae mattis libero finibus. Suspendisse iaculis condimentum nibh, eu mattis enim vehicula a. + +Fusce dictum urna non lectus ullamcorper vestibulum id in elit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc fermentum odio non ante sollicitudin posuere. Praesent vulputate quam nec magna euismod pellentesque. Etiam tempor nunc eget velit volutpat eleifend. Mauris sodales nunc ullamcorper, gravida purus eget, rutrum sem. Nam a sapien rhoncus, scelerisque lacus ac, condimentum ipsum. Pellentesque suscipit, ligula ut rhoncus ullamcorper, ligula augue rutrum lorem, eget aliquam risus tortor eget metus. Curabitur scelerisque bibendum purus vel vulputate. Morbi et odio at neque sodales suscipit. Nam at pretium nulla. In scelerisque vulputate suscipit. Nulla facilisi. Pellentesque eu sem eu ligula efficitur viverra. Pellentesque gravida consequat urna id bibendum. + +Nulla consectetur facilisis est nec aliquam. Proin hendrerit magna suscipit ante accumsan hendrerit. Nulla ut sem id neque tempor vehicula. Sed eget massa eget sem placerat tincidunt. Ut eleifend, justo sit amet egestas lacinia, ex velit pharetra nulla, nec aliquet elit neque ut turpis. Quisque in gravida velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed euismod malesuada convallis. Ut ultricies, magna et blandit fringilla, dolor dolor maximus sapien, in aliquet augue est vel odio. Nulla commodo elit massa, euismod tempor turpis aliquet eget. Morbi non odio et tortor egestas ultrices. Etiam semper, ipsum et dictum pharetra, eros purus bibendum lacus, vel laoreet lacus sem vehicula nibh. Nunc enim nulla, pulvinar sit amet lobortis sed, porttitor molestie risus. Morbi tincidunt diam mi, nec auctor sem rutrum at. + +Donec pellentesque odio odio, eu condimentum risus consectetur quis. Vivamus ullamcorper, mauris non semper rutrum, dui risus suscipit tellus, ut tempor velit risus vitae nibh. Duis tempor nibh at tristique rutrum. Cras congue nisl at sem sollicitudin efficitur. Aenean auctor purus vel libero fermentum elementum. Mauris convallis orci id interdum accumsan. Aliquam at metus risus. Sed accumsan, quam id dignissim congue, velit risus eleifend ligula, ut fringilla elit erat at neque. Aliquam tempor, quam ut ultrices volutpat, orci lectus vulputate turpis, eget pharetra purus nisi non lorem. Ut condimentum convallis justo, a volutpat eros. Sed tempus turpis leo, in convallis est fringilla sed. Vivamus eu laoreet tellus. Quisque ullamcorper ullamcorper leo. Nam fermentum egestas facilisis. Nulla egestas ligula feugiat urna molestie, faucibus convallis erat accumsan. Cras pellentesque ipsum lectus, vitae luctus dolor suscipit nec. + +Vivamus vel nibh sed mi tincidunt cursus quis facilisis tellus. Donec eget est eu velit pretium molestie. Maecenas lacinia risus turpis. Sed tristique id risus sit amet venenatis. Duis maximus, metus ac molestie convallis, quam ex dapibus sem, at rutrum metus nisi quis lectus. Suspendisse sodales in nisi at hendrerit. Maecenas suscipit lobortis vulputate. Nunc convallis sit amet elit eget porta. Mauris tincidunt massa in augue finibus iaculis. Vestibulum imperdiet, orci vel bibendum laoreet, ligula quam mollis lacus, a eleifend nisi tellus vitae ipsum. Cras non ante sollicitudin, auctor dolor id, condimentum tellus. Morbi malesuada leo nec lectus scelerisque, nec interdum lacus ornare. Integer ultrices ligula nunc, sed blandit urna aliquam eget. Proin consequat viverra ex non rhoncus. Phasellus id nibh at tortor sodales blandit. Donec dignissim ipsum vel ligula malesuada rhoncus. + +Nullam mauris sem, dapibus ac nisl at, hendrerit faucibus nisi. Mauris dictum fermentum pellentesque. Praesent in gravida odio. Vestibulum pharetra iaculis est quis tincidunt. Sed venenatis rhoncus lacus, non porta ante vulputate at. Duis fringilla ipsum at urna euismod molestie. Duis a porttitor ante. Mauris pharetra elit et metus auctor, a eleifend sapien porta. Vivamus ut tincidunt purus, lobortis euismod tellus. Nullam non nulla gravida, laoreet tellus ac, placerat urna. + +Sed justo sapien, scelerisque nec nisl vel, efficitur aliquet purus. Phasellus eget posuere nisl. Integer porta vel purus nec ornare. Pellentesque rutrum in nulla at hendrerit. Nullam elementum sodales volutpat. Duis tempus, purus sagittis auctor ullamcorper, dui erat hendrerit nulla, id finibus eros dui quis risus. Quisque posuere nunc quis augue placerat lacinia. Nunc quis lorem vulputate, tincidunt enim nec, rhoncus odio. Ut porttitor porta velit ut vulputate. Duis convallis sagittis magna nec gravida. Ut eu luctus sem, non placerat erat. Vivamus viverra ut ligula ac finibus. Cras ligula urna, tincidunt id risus eu, dapibus viverra lorem. Aliquam erat volutpat. Sed dolor nisi, faucibus non ligula faucibus, laoreet bibendum diam. Vivamus sagittis lacus ut condimentum tincidunt. + +Proin tristique mi ullamcorper dolor accumsan mollis. Nulla facilisi. Pellentesque iaculis mattis augue ac rutrum. Mauris in nulla at ligula fringilla pulvinar. Nam commodo ornare nibh ac viverra. Maecenas eget augue a risus mattis ullamcorper non at nibh. Aenean nec erat diam. Pellentesque augue nisl, venenatis mollis dolor non, bibendum malesuada leo. Pellentesque elementum purus ornare mauris iaculis porttitor. Sed mollis tempor dui eu elementum. Donec porttitor tellus non mattis gravida. Mauris venenatis suscipit convallis. Sed dolor sapien, pellentesque et tellus a, tempus cursus magna. Nunc lobortis leo magna, at maximus orci ultrices eget. Nullam vulputate dictum nisi, vel egestas orci. + +Nam nibh mi, ornare sit amet nibh vel, lobortis lacinia leo. Ut egestas mi vel nunc luctus vestibulum. Nullam id tempus felis, eget convallis erat. Aliquam venenatis ut tellus id fringilla. Aliquam erat volutpat. Sed vehicula, odio nec mollis dapibus, ligula sapien consequat risus, ut volutpat diam neque ut neque. Mauris venenatis libero vitae sem elementum, sit amet maximus massa varius. Maecenas malesuada mi id leo euismod, eu maximus sapien vehicula. Nulla facilisi. Duis nec venenatis ante, non pulvinar lacus. In sollicitudin sit amet velit suscipit egestas. Maecenas a lectus euismod, dictum nulla at, malesuada lectus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec lorem massa, laoreet non elit nec, sollicitudin blandit felis. + +Cras dignissim fermentum tellus ut fringilla. Maecenas maximus eros pretium sagittis venenatis. Maecenas id enim ac est semper efficitur ac hendrerit leo. Cras eu arcu tincidunt risus pellentesque aliquam. Pellentesque vel sodales libero, non rhoncus enim. Integer vulputate vulputate libero, eu consectetur eros euismod vitae. Fusce magna nibh, vehicula sed turpis eget, malesuada ultricies sem. Sed convallis sem nisl, rhoncus mollis mauris bibendum ut. Pellentesque vitae massa a justo dapibus laoreet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Sed mollis egestas nisl vel ultricies. Phasellus sit amet varius leo, sed vehicula sapien. Etiam in urna eget neque aliquet ultricies in nec quam. Aliquam at feugiat elit. + +Maecenas placerat nisi ultrices neque pulvinar, et ultrices ante tempus. Vestibulum laoreet quam lacus, eget commodo magna dictum consectetur. Aliquam erat volutpat. Aenean tincidunt ipsum sit amet justo aliquet tempus. Quisque blandit sit amet est facilisis dictum. Sed non consequat dui. Integer purus diam, gravida quis tortor lobortis, iaculis vehicula sem. Morbi pellentesque est elit, vitae interdum elit laoreet et. Vestibulum in blandit ante, eu blandit velit. Morbi sagittis eu est eu vulputate. Nullam ac mi feugiat nibh feugiat suscipit. Sed interdum tincidunt efficitur. Integer dictum sem erat, ut pharetra libero lacinia in. Mauris at hendrerit odio, a facilisis lacus. Donec blandit massa ac nisi ultrices faucibus. + +Aenean tempus id ligula at mollis. Cras id dui magna. Integer id neque tincidunt, rhoncus nunc et, laoreet nibh. Pellentesque consectetur odio ligula, et consectetur tortor scelerisque non. Cras quis ex pharetra mi ornare lobortis. Phasellus fringilla interdum felis, vel aliquam ligula. Mauris cursus varius turpis in iaculis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent eget mi id nulla semper dapibus. + +Vivamus lacus augue, eleifend dignissim ipsum eu, interdum accumsan massa. Nam id quam vel ligula consectetur volutpat id eget eros. Aliquam sed felis velit. Duis egestas velit ac dolor blandit, ut elementum mi tincidunt. Integer varius nisl a sapien pellentesque, et pellentesque ante elementum. Fusce ac orci interdum, faucibus nisl ut, sagittis nisi. Vestibulum sed diam eu magna congue ornare. Integer dignissim ligula sit amet mauris pretium vulputate. Duis ac tincidunt magna. Sed vehicula, ante nec vulputate venenatis, mauris odio blandit dolor, vitae lacinia nibh lorem et metus. + +Sed non sapien vel lorem tempor semper ac ac tellus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas auctor ante sit amet suscipit vulputate. In ac cursus turpis. Donec eu sem bibendum, blandit est nec, blandit tortor. Pellentesque scelerisque justo vitae magna cursus, vitae egestas libero interdum. Pellentesque vitae ligula vel mauris vehicula convallis. Nunc ornare lectus sit amet lorem aliquet dignissim. Cras nec ligula pretium, semper nulla a, pulvinar lacus. Sed ac augue bibendum, posuere ipsum eu, venenatis quam. Sed elementum nunc quis odio pellentesque, a vestibulum ex congue. Cras id malesuada arcu. Mauris ut egestas nulla, sed tempus ligula. Donec ac elit ut nisi pulvinar elementum. Suspendisse id auctor lectus, vitae ultricies lacus. + +Vestibulum pulvinar ornare cursus. Curabitur accumsan sollicitudin mi in vestibulum. Vestibulum vulputate tincidunt luctus. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus a mattis neque, id malesuada odio. Aliquam id auctor magna. Vestibulum quis nibh lacinia, tincidunt felis sed, vestibulum ex. + +Donec placerat ipsum diam, vel imperdiet velit eleifend ac. Quisque dapibus erat non dui convallis eleifend. Praesent pellentesque felis id suscipit rutrum. Donec quis lacinia turpis. Nulla justo eros, fringilla vel fringilla in, euismod sollicitudin arcu. In ut lobortis arcu, at rhoncus elit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. + +Pellentesque iaculis quam nec interdum eleifend. Fusce mauris mauris, imperdiet sollicitudin volutpat eu, sollicitudin blandit leo. Vestibulum hendrerit diam gravida erat imperdiet, blandit dignissim dui tempor. Phasellus viverra ante quis aliquam finibus. Duis sed condimentum augue, nec sollicitudin dui. Ut ullamcorper metus ac ligula accumsan, ac fringilla metus gravida. Morbi rhoncus est nunc, at lacinia ex fringilla sit amet. Integer lobortis ultricies nisi vitae accumsan. Nulla nec sagittis ante. Mauris bibendum nisi ut magna vulputate maximus. Praesent in elit eu metus dignissim cursus. Ut a tellus felis. Mauris eget ornare diam. + +Suspendisse potenti. Nam nec tortor ex. Duis eu elementum ligula. Pellentesque efficitur sodales orci, vitae sollicitudin odio tempor ac. Nam lacus ante, lobortis vitae lobortis eu, eleifend et velit. Nulla et lectus eu nibh tristique malesuada a sit amet felis. Proin molestie, lectus vitae sagittis malesuada, massa velit finibus est, id vestibulum dolor felis eget lectus. Phasellus varius pellentesque neque, a malesuada ex mattis eget. Nulla facilisi. Phasellus interdum placerat lobortis. Sed et odio tristique, viverra libero et, sagittis diam. Proin tristique lectus id bibendum maximus. Integer ornare euismod ligula nec malesuada. Nulla facilisi. Nullam bibendum hendrerit pharetra. + +Duis pharetra auctor felis, eget aliquet justo commodo at. Donec quis nibh non arcu sodales efficitur. Aenean lorem sapien, tincidunt eu sapien nec, convallis tempor diam. Curabitur volutpat, felis ut congue euismod, lacus nisl euismod ipsum, vel consectetur orci nibh sed ligula. Curabitur consectetur vitae mauris sed tempor. Fusce vel pretium nibh, et tristique dolor. Aliquam semper a ante non finibus. Praesent euismod urna augue, at feugiat orci commodo ut. Duis imperdiet purus non augue cursus gravida. Maecenas sodales purus et sollicitudin venenatis. Nam ultrices lorem lectus, ut pretium turpis hendrerit eget. Vestibulum vel lacinia lectus, at dignissim diam. Vivamus ut tortor ac tortor blandit finibus. Etiam porttitor tortor sit amet elit lacinia gravida. Pellentesque pretium, orci vitae tempor vehicula, nisi tellus tincidunt sapien, id egestas felis quam a nunc. Pellentesque sed vestibulum nisl. + +Mauris congue, justo vel dapibus pretium, ipsum augue consectetur leo, at aliquam ipsum neque non mauris. Nam sollicitudin in urna eu blandit. Aliquam erat volutpat. Morbi eget interdum ante. Pellentesque congue gravida arcu, eget ornare ante elementum nec. Integer a auctor dolor, in varius sem. Etiam dictum nibh magna, at volutpat nunc blandit eu. Curabitur at sem ipsum. + +Nulla porttitor erat eget volutpat iaculis. Pellentesque egestas, nisl vel ultrices efficitur, ex magna condimentum urna, in tincidunt massa turpis eget metus. Etiam vitae magna elementum, fermentum justo ut, pretium velit. Aliquam aliquet venenatis malesuada. Quisque consequat enim metus, pellentesque blandit leo rhoncus id. Vivamus vestibulum, est in condimentum mollis, ligula eros blandit lorem, vitae dignissim lectus mi eget nulla. Cras posuere leo at neque convallis, sed pretium massa ultrices. Morbi tempus porttitor turpis. Nam vitae mauris et massa venenatis tincidunt. Quisque vestibulum lacus ligula, in sodales felis elementum vel. Sed a tellus condimentum, sodales nisi id, aliquet elit. + +Vestibulum ac ex eu turpis lacinia condimentum nec eget ipsum. Nulla non imperdiet erat, at malesuada lorem. Praesent efficitur imperdiet augue vel laoreet. Sed dignissim, ante non porta feugiat, enim nunc rhoncus lorem, eu luctus turpis risus sit amet orci. Vivamus bibendum pharetra venenatis. In at gravida nisi. Vestibulum pulvinar sapien ac augue scelerisque, vitae blandit nibh malesuada. Nunc vitae est faucibus, accumsan risus a, laoreet urna. Cras imperdiet lacus in augue facilisis dignissim. Duis sed nisi quis diam mollis rutrum. + +Vestibulum eget egestas neque. Praesent viverra, velit quis porttitor euismod, sem libero venenatis mauris, id congue urna nulla sed lorem. Nulla mollis metus nec diam malesuada aliquam. Duis ac risus nunc. Cras sollicitudin urna nunc, id sodales quam gravida sit amet. Fusce in vulputate orci, in venenatis lorem. Donec a sagittis ipsum. Quisque consequat sapien tellus, sed efficitur lacus aliquam eu. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean in neque at augue elementum commodo pellentesque eget ligula. Nullam condimentum efficitur tincidunt. Phasellus posuere tincidunt odio sed facilisis. Aenean eu risus at est euismod facilisis. Curabitur elit purus, malesuada quis blandit id, rutrum vitae dui. Praesent porta rutrum erat, ullamcorper viverra nunc. Cras ut velit dui. + +Etiam posuere pulvinar mi at ullamcorper. Pellentesque finibus, tellus non convallis commodo, orci nibh dapibus nisl, at aliquam purus nulla eget dui. Praesent fringilla urna nec nulla pellentesque, nec rhoncus turpis ultricies. Sed laoreet velit pellentesque libero varius, ac interdum urna viverra. Phasellus sed consectetur massa. Morbi quis velit nec ipsum varius tempor. Proin id sodales felis. Aliquam lacinia risus quis ligula condimentum sodales. Nulla vel arcu aliquet neque iaculis aliquet. Cras sed lorem eu turpis tincidunt sodales. Sed pulvinar elementum ligula, nec faucibus nisl. Fusce nec tellus eget dui tempor sagittis. In vitae enim in ex viverra commodo. Duis est erat, fringilla ac semper a, dapibus in tortor. + +Maecenas commodo vulputate iaculis. Aliquam non facilisis est. Donec pellentesque vitae nibh nec volutpat. In commodo metus placerat lorem commodo, non lacinia nibh bibendum. In viverra rhoncus erat. Mauris nec nisl blandit, elementum justo nec, accumsan libero. Aliquam elementum, velit et ullamcorper convallis, turpis lorem elementum lorem, quis consequat tortor eros ut erat. Curabitur et enim quis felis vulputate congue et vel purus. Sed elementum interdum ipsum, sed tincidunt arcu scelerisque et. + +Aenean interdum elementum mauris ut porta. Mauris vel purus ac odio vulputate pulvinar at quis odio. In turpis turpis, convallis in augue id, elementum vulputate lorem. Sed tincidunt fermentum vulputate. Nunc ipsum ipsum, molestie vel convallis id, pharetra vel arcu. Maecenas vel dui elit. Sed blandit dolor sit amet risus commodo faucibus. Duis rhoncus felis arcu, vel aliquam nisi faucibus sed. Suspendisse cursus eget nunc ut bibendum. Fusce non ligula risus. Curabitur vitae cursus metus, quis fringilla diam. Phasellus turpis ante, pulvinar ac turpis tristique, sollicitudin congue lectus. Proin quis ipsum at ipsum euismod euismod. + +Nunc ut fermentum nunc. Donec id commodo lacus, at hendrerit justo. Donec cursus purus sodales nunc commodo, quis bibendum elit hendrerit. Quisque quis pellentesque nibh, ac vulputate neque. Aenean non placerat felis, eget feugiat ligula. Vivamus a eros accumsan, cursus neque at, ultricies magna. Fusce tincidunt tellus vitae mi rutrum laoreet sed quis ligula. Nullam ullamcorper ligula ligula, vel fringilla metus aliquet a. Morbi aliquet, mauris vel interdum venenatis, ex arcu venenatis tortor, at tincidunt dui ipsum et arcu. Nulla blandit gravida nulla ac iaculis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed ullamcorper lectus in sapien lobortis, eget posuere massa varius. + +Cras lacinia nunc faucibus mauris placerat pretium eget non sapien. Morbi viverra bibendum posuere. Aliquam accumsan sagittis dolor non iaculis. Sed nunc odio, lobortis in dolor ac, rutrum fermentum velit. In venenatis, velit in molestie semper, est magna condimentum dui, aliquam auctor lorem nulla vel ligula. Morbi vehicula turpis turpis, quis mollis justo tempus vitae. Proin luctus lacus in mauris porttitor aliquet. Duis vitae nunc ex. Nullam eget erat vitae nulla iaculis rhoncus. Sed lacus dui, suscipit eu leo vitae, tincidunt dignissim risus. Praesent ut massa ut arcu sagittis consequat. Sed sit amet tincidunt turpis. Nulla bibendum, felis eget posuere dictum, libero mi tristique elit, at venenatis neque elit ac quam. Curabitur nisi sem, scelerisque nec nunc ac, pulvinar pharetra odio. Curabitur egestas pellentesque arcu sed suscipit. In mattis dolor vel dui mollis feugiat. + +Sed commodo, dui ac vestibulum dictum, tellus libero tincidunt lacus, viverra commodo est felis vitae urna. Proin tincidunt neque vel turpis eleifend laoreet. Vestibulum sagittis, tortor sed iaculis consequat, urna ante sagittis est, ac ullamcorper lorem nibh dignissim odio. Nam arcu mi, cursus et blandit nec, aliquam ut nulla. Aenean quis iaculis tellus, eu egestas augue. Etiam pretium eget nisi quis iaculis. Aliquam sed convallis eros. Ut bibendum rhoncus lacus, in vestibulum dui ultricies id. Fusce vestibulum, mauris ut tempor consequat, dolor nisl pellentesque elit, in porta arcu elit vel ante. Vivamus at nisl est. Etiam nec blandit tortor, at pulvinar orci. Proin semper dapibus tincidunt. Phasellus lobortis enim ullamcorper dolor tempor cursus. + +Mauris a libero in enim gravida aliquet ac sit amet nibh. Vestibulum ac neque posuere, blandit libero ac, vehicula enim. Aliquam auctor iaculis eros sit amet molestie. Quisque faucibus turpis et massa tristique, nec dapibus mauris aliquet. Proin blandit aliquet mauris, non tincidunt odio blandit vel. Curabitur a nibh in eros commodo tincidunt eu et libero. Curabitur sit amet dapibus ex, in condimentum magna. Sed eu sem sem. Nunc tellus dolor, rutrum eu mauris nec, congue feugiat purus. Fusce tempor, neque vitae bibendum imperdiet, dolor ipsum condimentum urna, et egestas quam tortor in ex. Aliquam velit magna, commodo hendrerit sagittis sed, feugiat eget erat. Nunc quis ullamcorper velit, eu consequat augue. Nam arcu mauris, condimentum sit amet magna in, finibus scelerisque nunc. Mauris erat est, hendrerit ac accumsan eget, facilisis ut nisl. Quisque dignissim arcu quis diam tincidunt tristique. Sed rhoncus nisl non enim fermentum, a lobortis dolor consectetur. + +Sed eget condimentum ligula. Vestibulum vitae cursus eros. Donec elementum sapien magna, posuere iaculis sem ultrices lobortis. Morbi eu bibendum lectus. Suspendisse ante eros, ullamcorper ac viverra eget, pellentesque sed sapien. Duis sit amet tincidunt dui, vitae lobortis purus. Sed venenatis tincidunt volutpat. Vivamus a nisl ac elit consectetur semper ut eu libero. Proin id cursus ex. In hac habitasse platea dictumst. Aenean sed nisi vitae odio venenatis pulvinar vitae ac risus. Sed varius magna ut erat luctus vehicula. + +Nunc non ex eget purus blandit faucibus at quis velit. Donec quis mi vestibulum, facilisis nisi quis, tincidunt turpis. Sed bibendum metus sed consectetur mollis. Maecenas fermentum, erat finibus pulvinar lacinia, ex risus dictum sem, ut vestibulum augue diam vel diam. Donec ac massa non nibh pretium laoreet eget in orci. Nulla placerat eleifend mi, pretium vestibulum diam condimentum vitae. Nunc odio turpis, feugiat vitae turpis eget, pellentesque commodo turpis. Etiam sapien purus, consequat nec mi eget, consectetur efficitur neque. Phasellus porttitor sapien sit amet nunc semper, vel bibendum nibh finibus. Ut ac imperdiet ex, eu congue felis. In posuere nisi felis. Mauris tempus pretium mauris, ac viverra nunc hendrerit id. Sed fermentum nec sem ac pulvinar. Integer dictum velit eget congue venenatis. + +Cras eros dolor, venenatis ac dictum sed, dignissim nec sem. Curabitur tempor erat quis pretium interdum. Nunc vestibulum justo nisi, sit amet sagittis tellus consequat vel. Donec pharetra nunc vitae consequat eleifend. Quisque ut mauris quis nunc volutpat consectetur. Nam a suscipit ligula, at gravida libero. Vestibulum blandit, tellus sed bibendum volutpat, libero tortor convallis nisl, sit amet placerat lorem dolor nec libero. Integer blandit libero elit, ut congue ex euismod congue. Nulla sodales justo id eros condimentum faucibus. + +Proin quam ante, hendrerit at bibendum eu, pharetra at lectus. Proin finibus arcu id nisl aliquam dapibus. Fusce a suscipit nisl. Ut placerat ultrices nibh nec efficitur. Vestibulum vitae interdum magna. Donec lobortis finibus risus, et luctus ipsum efficitur euismod. Quisque dictum diam et venenatis euismod. Cras dictum molestie aliquet. Vestibulum imperdiet quam eget diam malesuada, quis pulvinar odio dignissim. Curabitur sapien elit, iaculis vitae justo eget, pretium malesuada elit. Donec gravida molestie tincidunt. Nullam at commodo ipsum. Sed nisi erat, tincidunt ultricies interdum consequat, pretium mollis ipsum. Vivamus in erat consectetur, sodales nibh at, porta nunc. Mauris semper, dui vitae condimentum tempus, mauris justo volutpat urna, ac ullamcorper tortor dolor in sem. + +Aenean leo ligula, egestas at vestibulum ac, porta vel mauris. Curabitur at lorem non mauris fringilla venenatis vel eu arcu. Donec posuere eleifend diam. Duis aliquet justo lacus, eu egestas erat eleifend sed. Sed non cursus urna. Sed pretium purus id blandit varius. Mauris turpis mi, lobortis eu tellus sit amet, maximus venenatis felis. Proin non varius enim, aliquet finibus velit. Praesent posuere pharetra ipsum eget varius. Phasellus non fermentum magna, ut iaculis augue. Praesent ut nisl nunc. + +Sed ligula tellus, interdum at sapien ut, dictum pellentesque nisl. Duis fringilla, sem nec elementum dapibus, nisl tellus maximus velit, eu varius sem nisl feugiat eros. Maecenas quis viverra lacus. Nulla nec commodo ex, nec placerat enim. Curabitur ultrices sagittis fringilla. Vestibulum sit amet enim sagittis, condimentum nisl sit amet, pulvinar orci. Ut at erat finibus erat bibendum convallis. Quisque euismod magna eget leo facilisis hendrerit. Cras venenatis, nisi quis sollicitudin volutpat, metus enim vestibulum nunc, at mollis leo leo nec est. Fusce fermentum tristique feugiat. Suspendisse sem est, condimentum ut ante at, egestas convallis ante. Vestibulum dictum, ex nec imperdiet laoreet, magna est ullamcorper augue, vel molestie quam odio vel ante. Donec dui dui, posuere a neque ac, condimentum vehicula magna. Curabitur suscipit, risus vitae bibendum posuere, mauris lorem viverra est, at tristique arcu quam quis leo. Donec sed posuere neque. Suspendisse iaculis aliquet condimentum. + +Morbi tempus condimentum diam eget bibendum. Aliquam varius magna quis lectus interdum, in elementum ligula tempor. Morbi vitae lorem sapien. Morbi luctus consectetur eros non aliquet. Pellentesque vestibulum sem sed ante accumsan faucibus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam ac suscipit justo. Nunc efficitur lectus eu arcu venenatis, vel accumsan ex suscipit. Vestibulum egestas ultricies tellus, et interdum enim pretium eu. Aenean rutrum est tincidunt rhoncus molestie. Phasellus hendrerit tellus et laoreet varius. Integer efficitur felis magna, nec sollicitudin arcu sollicitudin in. Curabitur non feugiat odio. Mauris nisi odio, luctus quis dolor sed, tristique luctus ex. Nullam libero enim, facilisis ut venenatis et, vulputate sed purus. + +Mauris a odio ut leo porttitor ultrices a et ex. Pellentesque vestibulum lacinia faucibus. In pellentesque eget augue at feugiat. Integer finibus augue dolor, in luctus lorem rutrum vitae. Donec pharetra lectus ac purus sollicitudin, vel tristique mauris pretium. Cras porttitor mi eu lectus consequat, a ultrices felis venenatis. In condimentum turpis in velit mattis laoreet. Aliquam sed mauris id nulla ultrices convallis vel vel velit. Suspendisse ut arcu finibus justo fermentum fringilla. Etiam in malesuada nisl. In hac habitasse platea dictumst. Duis a est lacinia, pretium dui eget, condimentum turpis. Integer ac placerat augue. Donec et eros felis. + +Integer cursus magna id quam sagittis consectetur. Aliquam erat volutpat. Quisque ullamcorper nisl nec massa dapibus facilisis vitae at nunc. Donec laoreet, libero in elementum tempus, enim odio porttitor felis, venenatis fermentum augue velit eu urna. Ut at ullamcorper enim. In molestie, velit et blandit maximus, erat nunc laoreet quam, vel finibus est mauris non sapien. Donec et dictum lorem. Nunc vestibulum, dolor eget tempus maximus, elit eros aliquam velit, vitae mattis mauris lectus ac tortor. Donec rutrum justo orci, id dictum metus vestibulum a. Etiam bibendum ipsum convallis, lacinia turpis vitae, dictum tortor. In velit dolor, scelerisque sed molestie nec, volutpat a turpis. Cras posuere commodo erat ut gravida. Quisque ante ipsum, volutpat a tellus non, dignissim ornare elit. Pellentesque sed porttitor dui, luctus rhoncus purus. In vitae ante pulvinar, consectetur orci quis, tempus velit. Curabitur tempus ligula id sapien ornare rhoncus. + +Maecenas eu nibh et elit accumsan blandit a at erat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam commodo feugiat condimentum. In non ante ut mauris eleifend lobortis. Phasellus eleifend vitae metus et semper. Nam sit amet rhoncus diam. Nunc molestie libero sed erat volutpat consequat. + +Aenean eget tristique odio. Vivamus quam tellus, dignissim sed faucibus sed, sagittis ut elit. Maecenas in ullamcorper sem. In at ipsum accumsan lectus vestibulum commodo nec non leo. Aliquam at suscipit felis. Nunc non egestas tortor. Donec sit amet eleifend eros. + +Sed eleifend nisi velit, in egestas erat condimentum eu. Pellentesque a tincidunt urna. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vestibulum dapibus mauris vitae elit auctor, id venenatis sem consectetur. Vivamus non leo pulvinar, blandit libero ut, vehicula arcu. Nullam elementum ex enim, at mattis massa pharetra eu. Nunc nulla magna, lobortis a magna sit amet, laoreet fermentum justo. Curabitur aliquam sollicitudin posuere. Aenean semper porta dictum. Mauris accumsan non nisi nec faucibus augue. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam in nibh eget ante viverra mattis. Etiam elit est, pharetra efficitur fermentum at, accumsan id eros. Aenean accumsan nisi facilisis tempor suscipit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nam magna erat, tincidunt eu placerat at, molestie at sem. In sit amet cursus mauris. Etiam ullamcorper lacus nisi, et porta metus semper id. Nulla mauris nunc, pharetra at facilisis a, consequat id metus. Sed convallis ligula non felis vulputate finibus. Curabitur nisl nulla, pharetra in justo a, dapibus ultricies dui. Morbi ultricies sollicitudin purus, quis pellentesque leo rhoncus sed. Curabitur sed eros quis lacus vulputate pretium. Vestibulum vitae urna nec arcu vulputate efficitur. Cras venenatis sodales dolor non ullamcorper. + +Donec eu placerat magna. Vestibulum justo lacus, luctus ac luctus sit amet, dapibus at orci. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec eu tortor ac justo pharetra hendrerit. Phasellus ornare lacinia vestibulum. Maecenas diam orci, molestie rhoncus fringilla vitae, aliquam eu purus. Maecenas vehicula felis dui, vel dapibus odio lobortis quis. Cras a velit non nisl efficitur tempor. + +Suspendisse magna enim, ultrices et elementum ut, venenatis ut purus. Curabitur convallis vel tortor id rhoncus. Pellentesque varius arcu non imperdiet eleifend. Vestibulum fringilla aliquet vehicula. Nullam et lorem ullamcorper, tincidunt ante eget, egestas ex. Donec laoreet, justo id tincidunt egestas, lectus diam faucibus tortor, nec venenatis felis leo a urna. In at tortor semper augue finibus ornare et vitae erat. Praesent vulputate, dui sed pulvinar porta, justo tortor feugiat tellus, eget accumsan velit turpis at orci. Nulla vitae velit facilisis augue sagittis fringilla nec sagittis nunc. + +Duis vel elementum odio, id eleifend mauris. Nam in ultrices nulla. Quisque pharetra blandit risus eget lacinia. Nulla mattis mi felis, a ultrices nisi egestas vulputate. Donec a augue ac ex laoreet semper at porta ante. Vestibulum arcu ipsum, vehicula vel mollis lobortis, ullamcorper sed augue. Ut viverra faucibus nunc, sit amet sodales diam gravida sollicitudin. Fusce finibus quam sed ornare consequat. Suspendisse viverra ultricies dui in volutpat. Maecenas blandit erat in rhoncus placerat. Phasellus eu nisi rutrum, bibendum nunc eu, viverra ex. Praesent sollicitudin mi sit amet dictum convallis. Vivamus purus ligula, interdum at tincidunt sit amet, pellentesque ut elit. Aenean vitae ultrices ex. In facilisis lectus est, nec vulputate diam tristique vel. In gravida tincidunt ex et viverra. + +Pellentesque mi justo, dignissim a nisl quis, tempor dictum lacus. Nam tristique varius dolor tincidunt pharetra. Nullam iaculis est sed quam dignissim cursus. Duis sit amet vehicula nisi, sed consectetur velit. Nullam non tellus nec dolor venenatis porta quis at quam. In pharetra id orci sed faucibus. Cras et malesuada erat. + +Quisque fringilla commodo metus nec feugiat. Donec et est urna. Maecenas ut magna dui. Vivamus eu sem venenatis, porta mi at, efficitur tortor. In lacinia hendrerit elit, in faucibus nulla ultrices et. Mauris accumsan nec orci et vestibulum. Aliquam eget justo velit. Mauris fringilla ullamcorper enim, in elementum enim eleifend a. Nulla id libero ac arcu tristique ultrices. Maecenas mattis orci vitae nisl laoreet auctor. + +Quisque id aliquam orci. Nunc molestie vitae quam eu consectetur. Vivamus molestie venenatis est, nec lacinia odio faucibus eget. Etiam ornare eu leo eget posuere. Quisque elementum ligula at euismod placerat. Maecenas lobortis leo diam, vel egestas odio iaculis ac. Integer dapibus mi metus. Sed at eleifend ligula, ullamcorper vestibulum eros. Suspendisse dignissim metus in vulputate iaculis. Nam rhoncus felis sit amet velit scelerisque semper. Ut sed augue eget nulla laoreet scelerisque. + +Aenean convallis ipsum id turpis gravida, et elementum ante facilisis. Donec vitae arcu id mauris mollis hendrerit. Mauris imperdiet cursus nibh at laoreet. Sed sit amet enim magna. Mauris blandit nisi quis arcu sodales, eget consequat orci euismod. Curabitur id bibendum diam. Ut pharetra tellus nec enim tristique, id efficitur eros accumsan. Suspendisse potenti. Praesent vel porttitor risus. Nulla vitae ex nec ex hendrerit euismod non mattis arcu. Aenean eget velit massa. Pellentesque venenatis arcu mauris, sit amet scelerisque sem lobortis ornare. Vivamus auctor porta eros, eget blandit lorem semper non. Nunc sed nibh commodo, hendrerit nunc at, malesuada nisl. Aliquam pretium leo sed iaculis vehicula. Vivamus interdum porta augue. + +Suspendisse potenti. Aenean sodales vehicula erat, vel sollicitudin eros eleifend id. Suspendisse hendrerit malesuada est, imperdiet tristique ex aliquet ac. Vivamus ultricies placerat lorem, ac placerat lectus sollicitudin ac. Etiam consequat odio vel bibendum pretium. Integer elementum nisl magna. Nam et vehicula risus, sed mollis tortor. Ut in molestie turpis. Nam luctus, elit molestie varius luctus, lacus ligula ullamcorper elit, non interdum ipsum neque non nibh. Curabitur vulputate facilisis suscipit. Sed vitae augue eu ex luctus lacinia ac vitae quam. Quisque non nibh ex. Praesent gravida efficitur dui, a vulputate nulla ultrices vitae. Duis libero dolor, facilisis in tempus vitae, pharetra non libero. Sed urna leo, placerat quis neque at, interdum placerat odio. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Vivamus in hendrerit elit, quis egestas sem. Sed tempus dolor finibus massa ultrices, sed tristique quam semper. Pellentesque euismod molestie facilisis. Vivamus sit amet ultrices elit. Ut eleifend, libero eu molestie maximus, ipsum elit pulvinar tortor, et aliquet nulla orci eu est. Nullam pretium, ligula at faucibus gravida, velit mauris pulvinar felis, sit amet tincidunt magna dui ut quam. Phasellus porttitor eu nunc id maximus. Suspendisse volutpat suscipit porta. + +Integer dictum augue purus, sed cursus eros blandit id. Vestibulum non nibh viverra, molestie lectus eu, vestibulum justo. Nullam a libero non nibh dignissim posuere id vel orci. Nulla viverra lorem eget condimentum scelerisque. Donec porta nunc sed sapien pretium dapibus. In hac habitasse platea dictumst. Suspendisse sit amet ligula elementum, accumsan ipsum vel, imperdiet massa. Fusce scelerisque non erat ac bibendum. Pellentesque consequat vehicula euismod. Morbi sapien nisl, ultrices ut scelerisque consectetur, ornare et orci. Maecenas efficitur eros a venenatis rutrum. Aenean bibendum dui in enim luctus posuere. Donec placerat porta eros eu dignissim. Fusce nulla velit, sodales eget nibh gravida, tincidunt venenatis urna. Aenean condimentum, massa in fringilla lobortis, turpis felis lacinia metus, sit amet facilisis nisl quam aliquet diam. Praesent fringilla vitae arcu nec lobortis. + +In tincidunt nisi ac dictum cursus. Sed dolor purus, posuere vel dolor vel, semper tempor elit. Integer nec scelerisque tellus, sit amet dictum magna. Donec hendrerit aliquet libero, a varius velit dapibus quis. Donec lectus turpis, egestas vel vestibulum vitae, iaculis quis eros. Maecenas id convallis metus. Duis quis tellus iaculis, lobortis massa vitae, condimentum eros. Pellentesque scelerisque nisl id hendrerit tempor. Donec quam augue, maximus eu porta ut, venenatis a ante. Curabitur dictum et nibh sed auctor. Nam et consequat odio. In vitae magna a dui porta pellentesque quis id magna. Sed eu nunc bibendum, imperdiet nunc sit amet, cursus diam. Vivamus in odio vel nibh sollicitudin molestie. Morbi sit amet leo et odio congue pharetra. Aliquam quis suscipit orci. + +Donec at ornare orci. Suspendisse nec tellus elit. Nam erat urna, laoreet at elit sed, tristique interdum ligula. Nullam dapibus orci sed lorem convallis fringilla. Cras urna ipsum, tristique maximus nisl quis, auctor mattis quam. Donec finibus consectetur lectus et interdum. Aenean posuere lectus vel turpis auctor finibus. Praesent molestie lectus ipsum, et tristique quam porttitor ac. Suspendisse aliquam pretium tortor, id egestas erat. Nulla mollis, orci at semper vulputate, nisl est pharetra diam, sit amet vulputate diam augue a sem. Donec in mauris felis. + +Nunc eleifend at elit a volutpat. Integer semper quis quam at faucibus. Donec pretium purus vitae erat pretium posuere. Sed ac aliquet nulla. Nam ut sagittis mauris. Sed quis sagittis risus, id pretium urna. Nam sagittis elementum ornare. Aenean ligula sapien, congue eget mi quis, viverra commodo nibh. Suspendisse non iaculis magna, nec volutpat neque. Donec eu dapibus eros. + +Nam sit amet iaculis massa. Nullam vel laoreet tellus, id cursus tortor. In hac habitasse platea dictumst. Curabitur in urna risus. Proin dui sem, interdum accumsan cursus ut, dignissim in purus. Nunc vitae consequat arcu. Proin volutpat elementum quam in posuere. Pellentesque luctus arcu in ornare tempor. Cras est turpis, viverra vel consectetur ac, dictum a quam. Donec sagittis tortor sed volutpat accumsan. Curabitur a tellus vitae arcu pretium viverra vel in ligula. Pellentesque turpis augue, porta vitae quam id, fermentum congue leo. Sed nec fermentum turpis. Nulla vehicula vulputate sem eu consequat. In tincidunt nisi et mi maximus, non facilisis justo porttitor. Proin eu sapien aliquam, accumsan nunc non, pulvinar leo. + +Nam vitae commodo sem. Ut laoreet in quam in suscipit. Nullam at faucibus diam. Fusce ut purus at ex lobortis elementum vitae quis sapien. Nam tempus consectetur ex, sed luctus felis. Donec porttitor mi dui, non luctus quam consectetur eu. Ut a egestas diam. Etiam sodales, nisl vitae gravida pulvinar, libero est condimentum tellus, vitae ullamcorper tortor justo a urna. Maecenas ac velit accumsan, laoreet leo eget, elementum libero. Maecenas dictum tincidunt blandit. Proin sed bibendum urna. Phasellus fermentum tincidunt tempor. Mauris iaculis, mi ac fermentum interdum, nibh odio pellentesque nunc, vel scelerisque sapien sem id purus. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Maecenas imperdiet sit amet lectus at hendrerit. Vivamus luctus, elit non lacinia hendrerit, risus velit finibus nulla, quis sagittis ipsum diam ut odio. Sed mauris sapien, vehicula efficitur gravida sed, gravida in lectus. Fusce eget consectetur turpis, in fermentum neque. Nullam turpis turpis, feugiat a accumsan in, euismod a augue. Vestibulum nec neque libero. Phasellus eget efficitur turpis, fermentum molestie nunc. Sed consequat elit urna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. + +Nunc egestas consequat blandit. Donec quis porta sapien. Sed augue nunc, efficitur vitae tellus sed, finibus bibendum sem. Fusce id sem quis quam pharetra ultrices. Phasellus non convallis velit. Quisque erat tellus, pretium eget porta in, ornare a arcu. Aenean nec lectus lorem. Suspendisse dolor lectus, ullamcorper ornare lorem a, consequat lobortis elit. Nunc dignissim est nec gravida facilisis. Proin faucibus erat vel eros volutpat, in vulputate neque sodales. + +Nunc vitae imperdiet ipsum, faucibus vehicula magna. Nulla nec nisl sapien. Curabitur et dui eget tortor efficitur accumsan. Aenean in pellentesque erat. Nam condimentum neque pulvinar, aliquam nisl eu, mollis lorem. Integer rutrum arcu eget felis semper euismod. Quisque vestibulum vel diam a tempor. Aenean mollis, tellus sit amet pretium pulvinar, nulla dolor ornare ligula, eget dignissim orci tortor ut sapien. Nam ut ipsum id lectus venenatis tempus vel non ex. Suspendisse blandit vitae nunc vitae porta. Suspendisse tincidunt est sit amet ultricies consectetur. Nulla fermentum hendrerit ex, vehicula rhoncus arcu lobortis ut. Vestibulum fermentum ornare diam at pellentesque. Praesent nunc lorem, porta et magna nec, sodales commodo justo. Duis aliquam sapien et rutrum tempus. Vestibulum malesuada felis eu ligula posuere luctus. + +Maecenas lacinia, lectus eget rhoncus aliquam, tortor est gravida sapien, vel aliquam arcu erat et magna. Praesent fringilla leo eget neque posuere imperdiet. In porttitor elit non enim gravida euismod. Aliquam tempus, orci at interdum dapibus, mauris lacus egestas sapien, ac ullamcorper ex nibh sed orci. Quisque iaculis enim et lectus egestas, malesuada posuere lectus interdum. Sed dignissim neque vel turpis dictum ornare. Vestibulum suscipit consequat maximus. + +Ut et ante sit amet leo rutrum volutpat. Sed malesuada quis sapien et ornare. Aliquam ac ex enim. Curabitur vel quam et orci posuere feugiat. Pellentesque nec metus eget sapien eleifend tincidunt non sit amet arcu. Cras posuere metus eget risus varius fermentum. Nullam orci eros, efficitur nec sapien nec, pretium laoreet erat. Nam gravida purus mauris, ac viverra orci hendrerit sed. Aenean ligula massa, posuere id faucibus vitae, malesuada quis augue. Morbi consectetur mattis mi, quis sodales diam bibendum eget. Aliquam sagittis neque at feugiat posuere. Donec gravida lectus a lectus fringilla tincidunt. Vivamus volutpat dui et turpis condimentum, ut tincidunt tortor lacinia. Ut laoreet, ante in pellentesque sodales, elit mauris scelerisque dui, quis tincidunt quam massa ac enim. Nulla porta porta sapien vel sollicitudin. + +Phasellus sed lectus posuere, mollis ante non, feugiat odio. Aenean a quam id mi ornare dapibus. Donec venenatis ipsum non velit accumsan, id elementum dolor imperdiet. Phasellus lacinia erat diam, sit amet convallis lectus ornare in. Curabitur lorem ligula, maximus eu varius quis, auctor quis odio. Etiam in orci porttitor, sodales ipsum at, rhoncus turpis. Nulla eget luctus nisi. Duis convallis est eget ligula fringilla viverra. Duis nec sapien quis dui luctus ornare sit amet quis erat. Quisque nec justo dui. + +Sed eleifend, tellus quis laoreet rhoncus, leo turpis imperdiet turpis, pretium varius odio tortor et leo. Morbi ut mi fringilla, porttitor sem ac, accumsan ante. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum cursus dolor accumsan dolor aliquet dignissim. Duis sed feugiat erat. Donec sed arcu accumsan, ornare nisl eget, lobortis nibh. Praesent pulvinar quis justo et hendrerit. + +Sed velit nibh, efficitur ut leo sed, eleifend iaculis nisl. Mauris ac diam euismod, luctus enim maximus, tincidunt sem. Ut tempus magna vitae blandit bibendum. Sed sapien tortor, ultrices et justo vel, pharetra faucibus dui. Vivamus et vestibulum arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Mauris tincidunt suscipit risus, vitae varius felis commodo in. Nullam semper tortor dolor, sit amet pharetra odio interdum hendrerit. Pellentesque sed justo eros. Cras quam purus, eleifend id tellus molestie, eleifend finibus lorem. Donec a volutpat libero, at vehicula tellus. Vivamus tellus orci, pharetra in nisi vitae, tincidunt dapibus nunc. + +Suspendisse ultricies sem laoreet quam molestie ullamcorper. Sed auctor sodales metus, eget convallis eros ultrices quis. Duis rutrum mi a tortor iaculis posuere. Suspendisse potenti. Pellentesque dictum faucibus dui a vestibulum. Donec elit nisl, aliquet at dolor non, viverra dignissim felis. Donec mi elit, condimentum sit amet magna id, efficitur sodales arcu. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque est elit, porttitor eget bibendum nec, auctor nec nulla. Pellentesque dignissim nisl vel orci commodo, ut ullamcorper justo viverra. + +Suspendisse pharetra gravida turpis sit amet efficitur. Nulla mattis tortor eget pharetra ultrices. Ut a turpis maximus, pharetra justo non, tempor quam. Nam et volutpat lectus. Vestibulum congue turpis augue, quis eleifend nulla euismod in. Vestibulum sed erat tempus, porttitor diam vel, elementum metus. Aenean facilisis molestie ante, sit amet ornare lacus mollis sed. Maecenas rutrum urna nec ex malesuada consequat. Nunc interdum pellentesque nisl sed viverra. Nam nec fermentum ipsum. Nulla facilisi. Maecenas congue dolor erat, a fermentum enim congue vitae. Integer metus libero, feugiat at eleifend eu, iaculis nec leo. Praesent eleifend convallis leo, eu posuere urna elementum eu. + +Aliquam dapibus dolor vel urna luctus venenatis. Suspendisse potenti. Donec vitae tellus nisi. Pellentesque vel neque dignissim, ornare tellus sed, sodales metus. Aliquam vitae maximus tortor. Nullam mattis, odio id porttitor euismod, est leo aliquet massa, bibendum pulvinar justo orci a magna. Morbi ut nisl congue, porttitor erat non, gravida turpis. In nulla risus, ullamcorper quis suscipit vel, tristique ut nulla. In malesuada odio augue, ac hendrerit nunc mattis a. Nam in quam ut elit ullamcorper mattis. Sed fringilla tempus felis, id pretium tortor varius non. Aenean quis sem quis risus mattis posuere vel quis nibh. + +Sed pulvinar commodo dui sit amet malesuada. Quisque porta tellus placerat congue efficitur. Cras id blandit enim. Praesent a urna felis. Aliquam ornare, nisl eget iaculis tempor, ligula mi vehicula dolor, ut eleifend massa massa vel est. Fusce venenatis laoreet lobortis. Proin tempus aliquet nunc ut porttitor. In est mauris, blandit eu mattis vitae, aliquam a orci. Cras vitae nulla nec ex cursus blandit. Aliquam fermentum, erat in aliquet dictum, metus urna fermentum quam, non varius magna lectus non urna. + +Donec faucibus nisl nunc. Integer rutrum dui enim, malesuada semper turpis pulvinar eget. Fusce consectetur ipsum tellus, sed maximus lorem auctor in. Morbi nec est in quam ultricies hendrerit. Sed aliquam sollicitudin elementum. Aenean aliquam ex sit amet dignissim venenatis. Duis malesuada leo nisi, id accumsan turpis pretium id. Sed ornare magna non pharetra pharetra. Ut auctor dolor neque, nec bibendum odio viverra in. Nulla convallis interdum condimentum. Proin vestibulum turpis in lorem ultrices, bibendum tristique ligula faucibus. In tincidunt auctor sem, vitae fringilla neque pulvinar eu. Etiam commodo pellentesque enim. Phasellus feugiat non sapien eget sollicitudin. Etiam consequat efficitur lacus vel maximus. Suspendisse vitae elementum diam. + +Mauris urna velit, efficitur at viverra at, interdum a velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean bibendum sagittis massa in interdum. Mauris facilisis dui eget ipsum euismod ultrices. Donec sit amet lorem iaculis, condimentum velit quis, lacinia justo. Integer faucibus metus sed eros semper, in congue tortor venenatis. Proin tincidunt maximus enim, accumsan facilisis tellus gravida eu. Phasellus interdum aliquam ante, sit amet rhoncus nisl ornare at. Suspendisse libero eros, cursus quis fermentum nec, tempor eget lectus. Aenean ullamcorper augue lacus, non iaculis dui rutrum id. Aliquam erat volutpat. + +Morbi hendrerit fermentum sodales. Proin rutrum congue auctor. Mauris pellentesque elit non velit condimentum tincidunt a sit amet velit. Etiam accumsan ante id neque commodo vulputate. Aenean nec mattis neque. Aliquam tempor urna quis nisl convallis congue. Praesent vitae porttitor ante. Mauris mattis vestibulum ante, nec auctor augue. Praesent sem leo, accumsan eu fermentum egestas, iaculis ac nulla. + +Etiam vel nisi congue, varius neque id, volutpat quam. Fusce placerat arcu hendrerit orci condimentum vehicula. Integer sem ex, facilisis et lacinia a, condimentum at ante. Morbi condimentum diam tellus, non lobortis arcu pellentesque sit amet. Phasellus imperdiet augue eget sollicitudin mollis. Vivamus sollicitudin arcu aliquam lectus tristique, in consequat diam egestas. Suspendisse aliquet non velit id feugiat. Sed eu est fringilla, gravida nulla ac, dignissim tortor. Phasellus pellentesque nisl non venenatis sagittis. Etiam facilisis nulla quis lorem scelerisque vehicula id at ex. Duis in risus enim. In eu vulputate massa, vel dignissim odio. Praesent ut mi tellus. In hac habitasse platea dictumst. + +Nunc malesuada turpis in fermentum lobortis. Donec blandit eu orci non laoreet. Suspendisse posuere blandit tortor, in accumsan velit cursus in. Integer suscipit justo nulla, in viverra orci suscipit et. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum a pulvinar lacus. Vestibulum mattis nisi vel nunc convallis maximus. Fusce aliquam, erat in volutpat gravida, elit odio feugiat ante, nec varius enim leo eget nisl. Donec sit amet ligula lobortis, sollicitudin dolor quis, blandit augue. Duis at interdum sem. Nullam eleifend ligula urna, vitae sollicitudin purus cursus nec. + +In commodo risus eu justo hendrerit, ut posuere mi eleifend. Suspendisse sollicitudin odio sem. Vestibulum at dapibus dui, vel dictum nisi. In hac habitasse platea dictumst. Sed ac pharetra ex. Ut ultrices augue ut vulputate condimentum. Phasellus convallis arcu tortor, ac tincidunt justo cursus vitae. Mauris dignissim dapibus imperdiet. Nullam id quam eget mauris cursus molestie finibus eu enim. Fusce laoreet orci eu nunc fermentum tincidunt. Pellentesque vitae ex ac nunc porta mattis sed ut ligula. Phasellus erat dui, consequat et lacinia vel, blandit nec dui. Donec ipsum magna, rhoncus ac viverra vitae, feugiat vel ligula. + +Cras ut nisl sed elit dictum semper a at magna. Aliquam laoreet viverra velit vel lobortis. Nam tempor lorem sit amet purus tincidunt accumsan. Vestibulum et vestibulum ligula. Donec sit amet neque faucibus leo rutrum semper. Maecenas scelerisque, lacus et lobortis congue, purus quam euismod risus, et mattis orci nisl eget est. In hac habitasse platea dictumst. Pellentesque eros velit, sollicitudin quis ante vel, blandit maximus mi. Suspendisse at eros id quam vulputate ornare. Duis placerat tellus vel odio ultrices, ac feugiat enim placerat. Vestibulum ut massa mattis, commodo orci euismod, cursus lacus. Suspendisse potenti. Sed venenatis cursus neque, at lacinia erat ornare et. Sed rutrum, dui at porttitor hendrerit, lacus magna fringilla quam, id mollis elit leo ut ante. Praesent vel diam sed urna mollis laoreet eget ut risus. + +Mauris varius odio lectus, sit amet consequat nulla sollicitudin sed. Suspendisse commodo rhoncus enim vitae pellentesque. Aliquam vulputate sollicitudin aliquet. Mauris interdum interdum orci, non varius sem ornare ut. Sed vel nibh nunc. Duis tincidunt quam quis lectus egestas, eu ultricies ante posuere. Aenean in mauris eros. Suspendisse potenti. Vivamus sit amet augue at velit accumsan fermentum. Phasellus vel dui sit amet felis convallis sodales. + +Nunc mattis vitae sapien ut dignissim. Nam fermentum sit amet massa eu accumsan. In ut ipsum sit amet ante pellentesque accumsan. Nulla egestas eros eget lacus rhoncus pharetra. Nam pellentesque laoreet ex in lobortis. Vivamus congue tincidunt molestie. Integer vel turpis augue. Cras vel molestie quam. Etiam vel mauris ut tellus finibus consequat. + +Curabitur velit erat, vestibulum blandit massa a, aliquam elementum tellus. Pellentesque id nisl tempor lorem condimentum dapibus. Quisque ut lorem at orci elementum dapibus quis ut enim. Praesent venenatis congue arcu eu sagittis. Nunc nunc massa, posuere ac gravida id, scelerisque nec velit. Suspendisse non lacus a orci dapibus congue. Sed leo velit, facilisis sed egestas ut, vehicula at turpis. Praesent a finibus felis. Quisque est mi, pellentesque sit amet varius eu, gravida id est. + +Donec auctor gravida urna ac suscipit. Etiam placerat ipsum vitae ante congue, at fringilla lectus ullamcorper. Donec viverra lacus ut erat posuere, dignissim porta purus tempor. Duis eget enim quis diam vehicula pulvinar. Donec tempus eros velit, sit amet pharetra ligula placerat in. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas sit amet pretium tellus, non dictum ligula. Nullam a ex purus. Vestibulum id ex rhoncus, pretium eros vel, consequat tellus. Vivamus lacinia dolor nec ipsum aliquam, euismod fermentum sem efficitur. Nulla facilisi. Pellentesque pharetra lacinia augue, et eleifend lorem aliquet eu. Ut ut porta ante. Duis ut auctor nisi, id porttitor erat. Proin sollicitudin sem quis justo sodales aliquam. Nulla pharetra gravida arcu vel tempus. + +Maecenas nec imperdiet nulla, vitae fringilla diam. Mauris maximus aliquet tellus at congue. Aliquam in dictum elit. Sed pretium mattis lectus, non pellentesque enim dignissim vel. Sed quis elementum diam, a porttitor enim. Cras cursus eros nisl. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ornare sapien vel diam vestibulum, at commodo metus lobortis. Sed euismod iaculis scelerisque. Pellentesque venenatis malesuada dolor, at tempus eros venenatis sit amet. Fusce a massa non libero blandit suscipit. + +Nulla facilisi. Sed nec leo nisl. Proin condimentum nunc at risus semper, in laoreet dui congue. Integer in dolor vitae ex maximus porttitor. In efficitur vulputate metus, ac egestas diam pharetra ac. Sed tristique tempus ligula dignissim convallis. Ut tincidunt laoreet fringilla. Praesent nunc est, lacinia tristique odio in, varius rhoncus ipsum. Vivamus bibendum justo at metus ullamcorper imperdiet. Quisque elit nibh, rhoncus a placerat eget, vehicula quis ante. + +Morbi fermentum turpis enim, eu interdum dui interdum sit amet. Sed vulputate lacus nec ligula gravida euismod. Duis in nisl fringilla, sollicitudin diam ac, laoreet tortor. Sed eget porttitor augue. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed eu enim convallis augue vulputate convallis. Praesent laoreet, leo at ornare luctus, nisi felis semper sapien, sit amet sodales augue mauris eu lorem. Fusce ornare volutpat malesuada. Curabitur vehicula, eros id fringilla placerat, tellus elit venenatis lorem, ac imperdiet purus ex blandit felis. Nunc suscipit elementum risus ac dictum. Aliquam bibendum dignissim ipsum, sit amet posuere sem viverra ut. Vestibulum egestas in ex ac volutpat. + +Donec ut faucibus velit, nec suscipit metus. Nullam dui ligula, commodo eget est venenatis, ullamcorper porttitor lectus. Suspendisse dictum, metus vitae commodo ultricies, enim quam pretium enim, a efficitur tortor purus sed ipsum. Nam sit amet lacus vel elit consectetur ultrices. Vestibulum ac nibh in metus porttitor vestibulum. In blandit et est non efficitur. Aenean vestibulum tortor sit amet mattis semper. Praesent sit amet turpis ac neque malesuada tincidunt nec nec urna. Mauris posuere elit nisl, ac ullamcorper nisi pulvinar in. Nulla ac elit in neque ullamcorper facilisis sed et dui. + +Quisque molestie euismod dui, non scelerisque arcu dictum a. Donec eu nulla nisl. Aliquam neque orci, ultrices in nunc vitae, sollicitudin eleifend augue. Etiam at mattis velit, a efficitur dui. Nam bibendum enim non elit tristique aliquet. Vestibulum eget nibh lacus. Pellentesque id sapien dui. Nullam urna leo, faucibus et efficitur vel, ullamcorper iaculis mi. Donec sit amet bibendum justo, id dapibus sem. Pellentesque dignissim varius tellus nec egestas. Praesent eu orci vel ipsum pharetra maximus. Cras nisl ligula, sollicitudin in ligula vel, posuere sagittis eros. Phasellus porta tristique mauris vel eleifend. Mauris sit amet volutpat ipsum, sed vestibulum orci. + +Ut rutrum augue quis rhoncus tincidunt. Aenean sollicitudin lacus at erat varius ullamcorper. Integer vitae orci vel nisi vulputate cursus eget nec ex. Mauris elementum, augue ac accumsan convallis, libero leo porta ante, sit amet elementum felis ligula non enim. Ut venenatis semper posuere. Vestibulum nec nisl nisi. Ut a sapien ac orci finibus dictum sed at ex. Phasellus ac lorem nisl. Quisque vitae tempor lacus. Vestibulum in mauris diam. Proin mattis ligula vitae ipsum bibendum, at dapibus nunc placerat. Nam iaculis justo in accumsan tristique. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris pretium velit et tristique pellentesque. Nunc in sapien a purus congue rutrum. Nam placerat risus in ante rutrum rutrum. In in ligula magna. Duis orci ante, vehicula elementum consectetur vitae, lobortis vitae arcu. Ut feugiat tempus metus quis sollicitudin. Etiam cursus venenatis augue at fermentum. + +Vestibulum id vehicula massa. Proin ut ligula et sapien placerat tristique non nec nibh. Etiam non lacus placerat, molestie ex eu, venenatis elit. Sed posuere, ante et ullamcorper luctus, elit lectus blandit tortor, nec consequat massa elit a tortor. Fusce urna felis, porttitor at ultrices vel, eleifend porta ipsum. Pellentesque nisl nibh, molestie sit amet sem eu, dapibus pharetra nulla. Phasellus viverra augue eu augue volutpat dignissim. Integer bibendum faucibus varius. Curabitur non turpis purus. + +Sed pretium eros nisl, sed ullamcorper magna faucibus quis. Etiam ornare euismod tellus, vitae accumsan eros bibendum ut. Morbi a ex sed risus faucibus rutrum et ut urna. Nullam non tortor commodo, facilisis massa non, tristique metus. Curabitur placerat, arcu in egestas ullamcorper, nisl nisl luctus felis, ac dapibus erat velit sed erat. Proin sagittis felis vitae sem posuere semper. Vivamus fermentum eu nisi ac facilisis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque pellentesque lacinia ex tempus lacinia. Aliquam erat volutpat. Nam aliquet mattis risus non pretium. Suspendisse potenti. + +Suspendisse et sapien ornare, elementum erat vel, ultrices nisi. Curabitur interdum dignissim tincidunt. Cras eget est a velit consectetur finibus et sed nisl. Curabitur vestibulum semper posuere. Aliquam porta diam commodo nulla tempus imperdiet. Sed id dictum neque. Ut sit amet risus aliquet, dignissim velit sed, tristique ipsum. Nam a sapien id magna commodo tincidunt quis ac tellus. Nunc nec pellentesque orci. In justo purus, vulputate quis urna a, fringilla blandit sem. Cras porttitor quam vitae metus vehicula faucibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut eleifend orci lectus, vitae semper eros hendrerit id. Fusce nec tellus condimentum, vehicula elit quis, gravida erat. Maecenas volutpat interdum velit id vestibulum. + +Pellentesque id metus metus. Nullam ac metus non nisi facilisis aliquet quis a sem. Morbi cursus consectetur aliquam. Morbi id sapien nibh. Etiam tempus tempor tempor. Nam scelerisque condimentum purus. Proin nec cursus eros, in condimentum dolor. Nam in sagittis urna. Ut eget ornare erat. Vivamus nec elit ut sapien tristique aliquet a nec urna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Maecenas varius pellentesque diam. + +Ut eget libero iaculis urna porta iaculis vel ac eros. Sed sed mi et libero porta consequat a in libero. Sed in imperdiet ipsum, sit amet ultricies dui. Curabitur sit amet consectetur eros. Praesent nunc tellus, feugiat ac laoreet consectetur, accumsan nec magna. Praesent at elementum orci. Donec dapibus venenatis libero, id tincidunt libero euismod ac. + +Aenean sit amet ex placerat, molestie sapien a, volutpat turpis. Vivamus elementum lacinia nisi a convallis. Donec a sagittis turpis. Curabitur pulvinar laoreet dolor id consequat. Aliquam aliquam feugiat magna, vitae sagittis lorem mattis ut. Donec sed auctor nulla. Ut convallis, neque vitae faucibus efficitur, nisi justo pharetra odio, vitae tristique purus lorem et nisi. Quisque eleifend aliquam arcu nec maximus. Nunc mauris elit, finibus nec gravida non, maximus nec nibh. Sed eu ipsum in arcu gravida maximus. Maecenas massa dolor, ullamcorper eget odio eu, congue ornare leo. Suspendisse venenatis ultricies imperdiet. Nam finibus orci vitae sagittis finibus. Pellentesque sit amet dapibus diam. Nam eu nunc elit. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam euismod turpis nec urna lobortis, ut malesuada ex suscipit. Fusce sed viverra risus. Pellentesque tristique nulla pulvinar tincidunt accumsan. Nullam vitae nibh imperdiet erat rutrum pellentesque et sit amet quam. Aenean laoreet ultricies hendrerit. Duis elementum tellus a feugiat vulputate. Aliquam aliquam enim placerat dolor viverra, non suscipit lorem ultrices. + +Proin interdum vitae lorem quis viverra. Praesent nec tempor dolor, vitae sagittis turpis. Etiam sit amet imperdiet sapien, ac laoreet arcu. Proin aliquam, risus nec maximus dapibus, dui diam elementum dolor, vitae tempor augue lorem vel justo. Aenean ac egestas dolor. Ut aliquet, felis at fringilla venenatis, leo nisl ullamcorper ex, vitae pellentesque turpis orci quis lectus. Nullam vitae suscipit metus. Integer congue, ante in lacinia rhoncus, erat lorem interdum elit, egestas suscipit lectus nunc non orci. Nunc vel viverra quam. Mauris sit amet sodales tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam quis sollicitudin libero. In nec venenatis orci. + +Integer non quam fringilla, tristique nulla id, gravida arcu. Aenean scelerisque lacinia magna. Praesent nunc sem, lobortis non convallis rhoncus, rutrum vitae ante. Sed et orci ut erat viverra bibendum a et lectus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce sit amet eros eget dolor pharetra vestibulum. Nullam vestibulum, massa dictum finibus egestas, orci nulla pulvinar sem, nec tempor libero lacus eu ante. Nam lacus erat, gravida eu placerat sit amet, facilisis sed urna. Suspendisse efficitur felis non ornare dictum. Nam sit amet nulla enim. Nulla eget scelerisque est. Suspendisse lacinia velit arcu, id lobortis felis facilisis in. + +Suspendisse potenti. Nulla porttitor metus ut nunc dictum tristique. Cras sit amet tortor eget ligula tristique efficitur. Ut at nisi id purus imperdiet laoreet. Sed sit amet malesuada urna. In nunc dolor, luctus ac condimentum ut, dapibus vel metus. Suspendisse pretium urna eget libero convallis vestibulum. Integer ut mauris hendrerit ex posuere euismod sed sed odio. Nulla egestas libero sit amet magna venenatis faucibus. Pellentesque semper vestibulum elit, et pretium felis scelerisque non. Suspendisse aliquet leo arcu, sed dapibus ex semper non. Donec lacinia dictum dignissim. Maecenas ipsum nunc, aliquet eget consectetur sit amet, aliquet vitae odio. + +Proin consectetur blandit feugiat. Nulla ac dictum quam. Vivamus suscipit scelerisque ipsum, vitae consequat neque sagittis id. Donec eu augue hendrerit, varius ipsum at, cursus massa. Morbi id augue id ipsum porttitor mollis. Phasellus nec libero eu arcu finibus dapibus. Nullam nec pharetra ante. Aenean sit amet urna eget justo tempus pharetra ut nec mi. Pellentesque viverra, ligula nec elementum ornare, ante elit eleifend enim, nec dignissim ligula elit in nibh. Vestibulum facilisis, felis eu condimentum dapibus, ante risus tincidunt urna, ut posuere dui augue ut ante. + +Nam arcu lacus, accumsan ac dolor in, egestas euismod est. Sed consectetur mauris et enim tincidunt semper. Donec sit amet pellentesque diam. Fusce viverra arcu a placerat fermentum. Nullam euismod dui eget egestas ultrices. Duis euismod viverra tortor eget eleifend. Pellentesque vitae neque dapibus, bibendum mi eu, auctor velit. + +Pellentesque congue consectetur turpis, eget auctor dui suscipit vel. Aliquam a sollicitudin turpis. Praesent nec blandit tortor. Suspendisse non nunc at urna tincidunt elementum. Integer eu elit nec urna maximus blandit quis at tortor. Nulla laoreet elit a purus tempus, et tincidunt lorem sagittis. Aenean semper erat eu neque hendrerit ornare. Cras posuere lorem nec orci vulputate finibus. Fusce tempor ex ac lacus gravida venenatis. + +Phasellus laoreet, libero vel fermentum euismod, sem diam accumsan quam, vitae gravida arcu est at sem. In bibendum, felis at viverra congue, felis nunc fringilla libero, ut scelerisque erat massa ut neque. Suspendisse potenti. Cras faucibus dolor vel tortor blandit, quis imperdiet magna semper. Nullam ut urna dapibus, vulputate est a, hendrerit purus. Vivamus vestibulum nisi a tempus placerat. Morbi vitae ultricies lacus. + +Nunc vel efficitur urna. Fusce bibendum suscipit mauris, quis imperdiet nisl bibendum non. Nam sed nisi imperdiet, pellentesque sem sed, pellentesque diam. Praesent luctus feugiat odio, eget fermentum lectus ullamcorper non. Phasellus pulvinar lectus sed ligula semper lobortis. Pellentesque fermentum ultricies fermentum. Quisque id turpis vel orci rutrum rhoncus id vel metus. Vivamus ex leo, accumsan eu luctus sit amet, tincidunt vitae erat. Sed eu mollis odio, ut ultricies lacus. Duis enim odio, pellentesque non velit vel, aliquam blandit erat. Mauris feugiat felis fermentum purus blandit, id rhoncus lorem tempus. Integer cursus, nunc vitae laoreet commodo, nunc lacus venenatis orci, quis eleifend risus est nec quam. Nulla tincidunt nunc ac purus tincidunt, eu molestie ipsum sollicitudin. + +Vestibulum ac varius velit, non suscipit nulla. Vestibulum a sagittis tortor. Suspendisse vestibulum felis quam, eget blandit nulla dictum vel. Praesent eu tellus ut lacus facilisis ultrices. Etiam fringilla nulla nec leo viverra faucibus. Sed nec sollicitudin nisl. Aenean libero massa, ornare sed elit ut, interdum fermentum arcu. + +Aliquam maximus diam et elit dapibus, eget condimentum sapien finibus. Etiam gravida nunc dapibus facilisis feugiat. Sed quis massa ligula. Nunc eleifend dolor lacus, at dapibus nisi vulputate eget. Etiam vel euismod urna, quis molestie nibh. Vestibulum bibendum erat non dictum sollicitudin. Suspendisse sed placerat lacus. Cras sollicitudin quam justo, a condimentum urna feugiat a. Quisque mauris libero, ultricies at ligula a, facilisis euismod ante. + +Morbi hendrerit maximus sapien ut auctor. Nullam nisi erat, aliquam ac metus eu, fermentum laoreet augue. Nam ultricies mi at vulputate laoreet. In ipsum est, blandit sit amet lorem id, egestas aliquam odio. Praesent venenatis lobortis mi. Aenean eu lacus consequat, mattis enim et, pellentesque leo. Duis convallis facilisis placerat. Donec porta, enim eget placerat congue, nisi augue faucibus odio, et fringilla purus elit vitae felis. + +Nulla et tellus a libero pellentesque eleifend vitae mattis nisi. Nunc placerat neque et sapien lobortis, aliquet pharetra nunc vulputate. Suspendisse potenti. Etiam ullamcorper lacinia varius. Suspendisse sit amet malesuada magna. Nam molestie mi nec diam tempus laoreet. Suspendisse urna tellus, scelerisque vitae purus et, dapibus fermentum felis. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec scelerisque eget neque sed hendrerit. Donec at ligula augue. Donec vulputate massa nunc, a feugiat nunc elementum ut. Donec pulvinar libero sit amet ante faucibus sagittis. Mauris quis diam ultrices, tristique sapien nec, tempus urna. Morbi hendrerit odio sit amet leo lacinia, non egestas diam condimentum. Suspendisse efficitur sapien eget elit euismod tristique. Duis posuere vestibulum risus id sodales. Ut eget mi in urna rutrum molestie non nec dolor. Curabitur sollicitudin pharetra lacus, in sodales tortor tempor vitae. Nullam rhoncus arcu vel euismod varius. + +Aenean malesuada, purus quis volutpat sollicitudin, lectus risus lacinia mi, a facilisis ante lacus a dolor. Aenean vel aliquam tortor. Vivamus ornare, tellus at malesuada suscipit, quam dolor egestas erat, id convallis enim augue a enim. Aenean ultricies sodales placerat. Vivamus vel erat ac mi vulputate commodo nec eget urna. Suspendisse potenti. Mauris quis dapibus est, id tristique libero. + +Suspendisse ex diam, imperdiet quis dui id, interdum sodales elit. Maecenas at nunc ligula. Etiam imperdiet convallis magna sit amet tristique. Proin hendrerit laoreet magna, eget malesuada elit rhoncus et. Curabitur eget odio imperdiet, molestie sapien pretium, efficitur turpis. Aliquam sed congue arcu. Pellentesque vitae mauris id neque pulvinar tempor. Donec lobortis tellus id gravida dictum. Nulla et varius ex. Vestibulum pharetra eu quam dapibus finibus. Nullam accumsan sagittis turpis. Mauris fermentum orci et tortor tempus, ac tristique odio sollicitudin. Duis nec elit et magna imperdiet molestie eget vel ante. + +Suspendisse tempus augue nec massa molestie aliquam. Pellentesque vestibulum, erat sit amet fringilla fermentum, sapien lorem tempor felis, at efficitur augue erat quis nisi. Proin nec felis sagittis, sollicitudin turpis eu, fringilla leo. Vestibulum at augue nec dui vestibulum aliquam a at metus. Duis ullamcorper eleifend bibendum. Maecenas viverra mauris lacus, et consequat nisl pharetra eu. Praesent rutrum diam eu mi aliquet, non pellentesque est suscipit. Vestibulum massa leo, blandit eget mi at, cursus faucibus nunc. Praesent nec bibendum libero, vitae cursus libero. Sed consequat vitae eros nec maximus. + +Pellentesque et tortor eget lectus feugiat venenatis. Integer ornare nisl quam, nec imperdiet justo finibus at. Morbi malesuada, ante sit amet rutrum tempor, risus lorem tristique urna, egestas varius purus ex ut sem. Etiam sit amet feugiat sapien. Etiam quis risus commodo, eleifend velit quis, tincidunt enim. Mauris fermentum lacinia velit quis efficitur. Nunc sit amet lacus sit amet urna viverra feugiat. Nullam sagittis rhoncus suscipit. Aliquam eu sem ligula. Sed sodales risus et tortor lacinia tristique. Nulla massa augue, malesuada sit amet euismod ac, euismod placerat nulla. Sed lobortis ex nec lacus facilisis, nec semper mauris ultrices. Quisque ornare non felis vitae pellentesque. Donec mattis ut magna sed imperdiet. Integer viverra tempus feugiat. + +Donec laoreet aliquam imperdiet. Fusce justo neque, dictum vitae fringilla vitae, euismod sed augue. Fusce sodales, lectus a congue bibendum, ante eros pharetra tortor, eu sollicitudin libero ipsum sit amet felis. Curabitur sed condimentum quam, in iaculis ante. Ut feugiat dictum odio, vitae hendrerit lacus volutpat sed. Sed scelerisque et metus nec gravida. Mauris mattis porttitor magna, ut maximus justo sagittis vitae. Donec at metus ut leo gravida vehicula in sed diam. Vestibulum dignissim suscipit sagittis. Nam varius et arcu sit amet lacinia. Sed eget elit rhoncus, elementum dolor a, vehicula orci. Nam vitae tellus sapien. Phasellus massa lorem, commodo quis placerat in, semper faucibus tortor. + +Vivamus id dolor mi. Curabitur sagittis posuere quam in mollis. Phasellus finibus ipsum vel elit tincidunt, a bibendum ligula vulputate. Suspendisse quis purus nisl. Integer mi sem, posuere quis nisi a, consequat consequat magna. Vestibulum bibendum mauris in risus auctor fringilla vel at lacus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus venenatis mauris at mattis eleifend. Donec tempus ipsum ac nisl convallis ultrices. + +Nullam eu nibh ut erat vehicula accumsan. Vivamus vitae faucibus libero. In in luctus odio. Nulla nec enim pharetra, fermentum erat in, tempor turpis. Sed ac magna suscipit, dignissim elit id, faucibus libero. Maecenas placerat in dolor et faucibus. Sed maximus purus dolor, non egestas massa rutrum non. Nunc ut rhoncus lacus. Sed sit amet dolor eu sapien sagittis molestie. Aenean ipsum erat, rutrum a diam et, varius porttitor ligula. Phasellus fermentum fermentum felis. + +Phasellus odio massa, lacinia ac hendrerit vestibulum, placerat sit amet erat. Praesent eget justo scelerisque, congue est in, pharetra mi. Etiam blandit turpis dui, a faucibus ipsum viverra vitae. Praesent sed scelerisque arcu. Cras nec venenatis ipsum. In et tempus metus. Pellentesque cursus quis nibh quis porttitor. Duis leo lacus, pretium eget rutrum eu, tincidunt lobortis tellus. Cras in erat tristique arcu faucibus luctus sit amet tempus erat. Nullam placerat, est a interdum iaculis, purus justo convallis arcu, at mattis erat sem tempor velit. Curabitur sapien sapien, tempor eget sodales vitae, blandit ac libero. Proin eget sem et arcu malesuada convallis non a est. + +Ut id sagittis urna. Morbi est mauris, molestie quis magna ut, convallis porta justo. Etiam in vulputate sem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nullam consectetur enim nisl, sit amet pulvinar turpis elementum at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum convallis cursus libero vel feugiat. + +Curabitur vel scelerisque metus. Etiam aliquet faucibus quam, vel aliquet est mattis quis. Pellentesque pulvinar sodales augue, a dignissim sem tristique vitae. Pellentesque dolor quam, pharetra vitae rhoncus ut, blandit at elit. Sed dignissim diam turpis, ac condimentum justo iaculis vel. Donec facilisis eros sit amet est dapibus, vel pellentesque eros consequat. Integer euismod mollis metus, vel rutrum risus imperdiet ac. Fusce semper dolor sit amet mi congue accumsan. Maecenas sagittis magna justo, vel varius ante scelerisque id. Fusce at urna sapien. Aliquam dui sapien, facilisis eu magna laoreet, feugiat tincidunt elit. Fusce iaculis sit amet erat id dignissim. Suspendisse sollicitudin laoreet consequat. Pellentesque eu mollis quam, vel dapibus libero. Duis mattis tortor vitae orci vehicula, et viverra ipsum lobortis. Quisque iaculis sapien est, id sagittis tortor rutrum at. + +Duis ut condimentum risus, et venenatis nulla. Praesent urna ex, faucibus eu mollis nec, lobortis vitae neque. Vivamus a malesuada tellus. Quisque bibendum dolor nec massa porta, nec malesuada magna auctor. Phasellus non auctor turpis, et bibendum risus. Ut pellentesque justo non neque convallis, ut imperdiet diam sagittis. Nunc arcu nisi, rutrum sagittis neque elementum, finibus consequat felis. Proin euismod elit eu urna tristique ultrices. + +Curabitur id hendrerit ipsum. Aliquam tortor nisi, dignissim vel sodales a, ultricies a ipsum. Etiam commodo arcu sed volutpat varius. Proin vehicula lacinia tempor. Vestibulum feugiat nibh eu ante tempor efficitur. Etiam eleifend a orci eu euismod. Cras a tortor risus. Cras nec urna non urna malesuada maximus vel et ipsum. In ullamcorper porttitor dolor, at fringilla tortor tristique ac. Nulla gravida tortor nec dolor convallis, accumsan sollicitudin dui luctus. Vestibulum euismod vestibulum semper. Nam semper lacus dolor, vitae rhoncus nisi blandit nec. Phasellus turpis dolor, posuere sed sodales sit amet, interdum ut arcu. + +Sed blandit nulla et sem vulputate, et semper lacus posuere. Proin velit lorem, sagittis tincidunt aliquet vitae, fermentum sed orci. Ut interdum ultrices dolor eu tempor. Quisque pretium vulputate dui at blandit. Aenean pretium urna sed purus dapibus aliquam. Mauris tristique augue pretium magna scelerisque, vel cursus sapien porttitor. Nullam neque justo, aliquam non neque id, lobortis facilisis nibh. Duis molestie dui eu augue tristique porttitor. Sed posuere justo massa, efficitur vulputate erat posuere non. Cras quis condimentum tellus. + +Etiam eleifend ipsum ut justo viverra porttitor. Nam bibendum enim nec ligula vestibulum, vel fringilla velit posuere. Praesent leo enim, varius sit amet nulla ut, sodales sollicitudin nunc. Phasellus hendrerit varius ex quis tempus. Suspendisse maximus laoreet enim, ut bibendum tortor. Ut non magna vehicula augue condimentum scelerisque. Aenean efficitur elit vel rhoncus euismod. Vestibulum sit amet sollicitudin dui. Quisque ac diam nibh. Nullam quam enim, volutpat eu turpis et, posuere consectetur neque. + +Ut tincidunt semper dictum. Etiam varius enim sit amet metus consequat malesuada in at ligula. Cras nisl dui, tincidunt a urna et, porttitor rhoncus velit. Mauris interdum imperdiet lectus ac mollis. Aliquam sollicitudin ornare mi, a varius nulla faucibus aliquet. Nunc fermentum tempor ullamcorper. Pellentesque laoreet libero condimentum pellentesque pharetra. Nullam sed eros vel erat vestibulum dictum. Nulla nec interdum dui, quis finibus nunc. + +Sed ac turpis in mi ultricies finibus sit amet et diam. Pellentesque sagittis non ligula et convallis. Suspendisse interdum est aliquet erat rhoncus semper. Donec pretium turpis ante, vel posuere mauris facilisis vel. Vivamus nibh ligula, pharetra sit amet hendrerit nec, vulputate ut sapien. Nulla ullamcorper condimentum metus vitae imperdiet. Ut eget ex purus. Nulla dapibus malesuada pharetra. Praesent sit amet ornare turpis. Nulla pulvinar felis eget arcu mollis vulputate. Vestibulum eget semper felis. Donec viverra justo id mi tempor, a mollis ipsum porttitor. Maecenas aliquet volutpat ante eget tempor. Duis ipsum nisi, scelerisque quis metus vitae, blandit faucibus nisl. Mauris rutrum nisi sed lorem dictum ultricies. + +Nulla dictum arcu augue, aliquet ornare ligula suscipit ut. Integer libero erat, bibendum quis arcu at, consequat luctus sem. Ut ut erat tincidunt, porta erat efficitur, bibendum neque. Maecenas eget convallis sapien. Nullam facilisis ex et lorem fringilla, ut convallis leo dictum. Duis porttitor, ex eu venenatis faucibus, erat augue dapibus leo, sit amet scelerisque neque dui sed arcu. Praesent at diam nec sem sodales condimentum. Vivamus vehicula urna tellus, a semper ipsum cursus id. Duis auctor enim erat, laoreet volutpat dui rhoncus maximus. Suspendisse pellentesque euismod lacus, at auctor purus. Suspendisse volutpat imperdiet diam, id laoreet est egestas at. + +Fusce lobortis ex vel condimentum convallis. Vivamus fermentum convallis dolor quis rutrum. Donec tempus ornare maximus. Mauris quam neque, dignissim rutrum maximus sed, volutpat sed lectus. Donec id augue gravida, pretium purus eu, sagittis tellus. Maecenas tincidunt gravida felis nec pulvinar. Fusce turpis urna, sodales non rhoncus nec, sodales ac ipsum. Suspendisse risus felis, imperdiet id malesuada fermentum, ultricies et erat. Suspendisse potenti. Pellentesque tellus ipsum, dapibus eget pellentesque eleifend, venenatis sed risus. Ut sed mollis nisl. Aliquam lobortis aliquam ipsum, et ornare magna bibendum ut. Proin quis justo vitae neque tincidunt maximus ultricies et purus. Nullam non semper turpis. Quisque non mauris odio. + +Donec commodo tempus egestas. Vestibulum at diam vel mi tincidunt finibus. Donec aliquam magna id eros tristique finibus. Cras ut enim sapien. Proin gravida risus a nisi dapibus, ac vulputate nunc laoreet. Donec at leo vel nulla iaculis feugiat sed et ante. Nulla a arcu at ligula sollicitudin semper sed eget est. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Ut dolor magna, luctus id elit ut, interdum viverra lacus. Aenean sed tempor metus. Aenean arcu dui, eleifend quis est sed, luctus lacinia nulla. Morbi id luctus libero. Maecenas sodales leo eu sapien maximus porta. Praesent gravida augue eu ligula pretium cursus. + +Duis aliquam luctus tortor, eu facilisis quam sodales sed. Phasellus feugiat venenatis lorem, in scelerisque est efficitur quis. Cras quis nisl nisl. Quisque magna felis, tempus nec pharetra et, tempor id ligula. Pellentesque sed dignissim velit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris convallis iaculis posuere. Nullam orci velit, venenatis eget enim quis, molestie consequat dolor. Nulla egestas risus vestibulum sagittis blandit. Nullam dui lacus, tempus euismod sapien a, eleifend sodales justo. In id tortor neque. Vivamus faucibus ante ac odio vestibulum, vitae condimentum nibh consectetur. In rutrum hendrerit est, sit amet mollis ex aliquet tempor. + +Donec non tincidunt sem. Nullam dignissim felis nibh, et accumsan felis tristique sed. Maecenas id massa erat. Ut justo ligula, aliquet eu condimentum a, aliquet eget ex. Phasellus et neque eu nisl consectetur ullamcorper. Sed gravida efficitur nisi, vitae posuere nisi tincidunt sed. Duis vitae molestie metus, vitae finibus urna. Integer at lacus vitae turpis convallis feugiat a dignissim libero. In dolor nibh, aliquam eu malesuada in, tincidunt vitae nisi. Nullam at lectus risus. Integer vitae ligula interdum, congue nibh id, tincidunt dolor. + +Mauris risus quam, mollis eu consequat vitae, molestie sit amet risus. Vestibulum congue vulputate nibh sit amet ullamcorper. Nullam elit justo, hendrerit euismod elit nec, gravida tincidunt ipsum. Aenean tellus tortor, commodo vitae cursus vitae, finibus quis mi. Nam in tellus scelerisque, cursus magna a, elementum tellus. Praesent ut lacinia justo. Donec consectetur, mi nec faucibus viverra, nisl justo suscipit est, in consequat ex urna vitae orci. Fusce molestie feugiat ex sed dictum. Phasellus interdum eros dapibus sodales volutpat. Morbi elementum sapien ante, quis sagittis justo fermentum at. Maecenas vestibulum massa quis eleifend rhoncus. Praesent auctor ex at urna pellentesque facilisis. + +Duis tincidunt porta quam, in commodo orci dapibus interdum. Donec mattis erat ante, nec faucibus magna pharetra id. Aliquam dignissim ultricies auctor. Duis quis ex mi. Phasellus ipsum mi, volutpat quis augue nec, dapibus aliquet lectus. Aenean id eros mi. Fusce rhoncus iaculis magna, commodo commodo nibh pellentesque ut. Donec consectetur lacinia erat ut luctus. Mauris efficitur erat vitae sapien fringilla sollicitudin. Maecenas a egestas ipsum. Aenean placerat congue augue, ut condimentum lorem accumsan in. Morbi ac quam nunc. + +Nunc posuere faucibus semper. Integer at convallis ex. Duis a purus molestie, dignissim mi quis, consequat nulla. Proin tempus congue ligula, sit amet ultricies enim consequat ut. Nunc ac est at nisl euismod cursus. Pellentesque vulputate molestie ipsum. Praesent varius, lacus non lobortis blandit, nibh lacus scelerisque nisi, sit amet interdum ligula tellus ac purus. Vestibulum sed quam tempor, pharetra tellus ullamcorper, tincidunt est. Nulla tempus tortor nec erat tempus eleifend pellentesque eget purus. Duis gravida dui justo, ac commodo ipsum mollis et. Nullam vitae nibh enim. Ut sodales mi eu diam sagittis, quis luctus tortor euismod. + +Maecenas a augue ac augue varius rhoncus. Fusce nunc turpis, mattis eu iaculis a, dapibus nec purus. Pellentesque imperdiet sit amet purus a blandit. Morbi augue tellus, venenatis nec tortor in, consectetur tincidunt nulla. Aenean purus libero, volutpat quis neque vitae, mattis efficitur eros. Donec sodales venenatis augue, sed faucibus magna accumsan convallis. Pellentesque efficitur elementum imperdiet. Cras at condimentum leo. Curabitur feugiat ut nisi facilisis commodo. + +Sed commodo sapien quam, quis vulputate orci lobortis nec. Aenean luctus dictum ipsum, non consequat sapien molestie vel. Proin blandit libero tortor, vitae efficitur tortor hendrerit at. Sed eleifend eu velit eu tempor. Nunc auctor tincidunt mollis. Ut molestie, erat ut lobortis convallis, odio felis sollicitudin elit, vitae ultricies lorem neque et dui. Sed venenatis tempus ullamcorper. Integer eget elementum nulla. Maecenas a placerat ipsum. Etiam sagittis sagittis rhoncus. Aliquam faucibus magna id lacus pharetra, nec interdum lacus sodales. + +Mauris ipsum sem, venenatis non elit in, feugiat pretium lacus. Fusce eget tincidunt dolor. Nam pharetra lacus vitae diam bibendum, ac placerat tortor aliquet. Sed eget gravida orci. Ut in orci lorem. Morbi condimentum ligula dolor, in dictum mi vestibulum sed. Suspendisse eu velit finibus, tincidunt dolor malesuada, mattis est. Morbi iaculis sapien vitae metus facilisis fringilla. Donec sed volutpat neque. Mauris ipsum metus, venenatis cursus mattis quis, convallis ultricies purus. Aliquam quam magna, sagittis at mi quis, lacinia iaculis turpis. Praesent viverra, ex nec euismod lacinia, urna risus pretium odio, sit amet mattis metus eros non lectus. + +Nam sed vehicula est, ac aliquet mi. Aenean iaculis placerat orci, ut lobortis dui vestibulum convallis. Vestibulum eleifend ante sed lorem interdum lobortis ut vel tortor. Sed auctor id nisl sed bibendum. Fusce maximus vulputate mauris, tempus sollicitudin nunc efficitur vitae. Nulla pellentesque molestie leo, quis hendrerit enim. In vel dapibus nisi. Nam at dui quis eros porttitor volutpat in id magna. Maecenas quis quam a justo cursus aliquet viverra sit amet mauris. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut hendrerit est at augue pretium condimentum at ut velit. Suspendisse non gravida metus. + +Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Praesent lacinia commodo elit, sed fringilla ipsum imperdiet ut. Proin a dictum ante. Suspendisse potenti. Praesent ac nulla volutpat, ultricies diam vel, auctor risus. Nullam aliquam elit vel quam suscipit molestie vel ac dolor. Ut pharetra finibus elit, id vulputate ex dignissim in. Ut ac finibus urna, a luctus magna. + +Fusce suscipit convallis eros nec blandit. Cras quis lectus nibh. Donec pulvinar rhoncus pulvinar. Vivamus nulla nisi, vestibulum vel neque et, dictum gravida ex. Vestibulum in arcu vitae nibh auctor rhoncus ac in massa. Sed semper enim ac dui sollicitudin porttitor. Etiam ante erat, laoreet sed dolor eget, cursus commodo lorem. Curabitur maximus tincidunt nulla at molestie. Phasellus ultrices felis a cursus facilisis. Nullam a semper tortor. Nulla ac vulputate justo. Mauris maximus nisi quam, elementum eleifend nulla condimentum nec. Aenean congue tincidunt mi, id mollis orci blandit vitae. Integer placerat orci at nisl vestibulum lacinia. + +Sed egestas posuere egestas. Quisque pulvinar velit commodo felis accumsan, a consequat purus laoreet. Ut at arcu at augue sodales rhoncus. Ut non nisl dui. Sed sed nulla quis orci eleifend auctor ut et sem. Aliquam erat volutpat. Donec egestas tincidunt leo in interdum. Donec varius odio nulla, id porttitor erat venenatis ut. Nulla ac nisl ut enim placerat congue. Fusce consequat purus eget risus luctus finibus. Donec in blandit odio. Sed vestibulum nisl ut diam vestibulum volutpat. Donec magna quam, tempor eget velit quis, blandit tristique lacus. Pellentesque et orci dui. + +Integer tempor mollis purus ut volutpat. Pellentesque efficitur cursus neque in ullamcorper. Integer ac tincidunt lacus, at venenatis tellus. Curabitur convallis commodo enim a convallis. Aenean sagittis sodales nibh, sed eleifend diam ultricies at. Vestibulum erat mauris, lobortis ac tempor a, viverra non sem. Nulla non ipsum mollis, dignissim elit a, laoreet enim. + +In laoreet purus eget orci rhoncus viverra. Quisque vel metus quis nulla hendrerit viverra. In consectetur velit vitae purus vestibulum, in hendrerit odio sollicitudin. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ut est sed ligula tincidunt fermentum nec ac nulla. Nam in sagittis velit. Vivamus vel dapibus erat, ullamcorper sollicitudin metus. + +Quisque pulvinar nulla eget mi mattis, ut convallis nulla ullamcorper. Vivamus placerat mauris vel elementum aliquet. Sed ac accumsan eros. Vivamus vitae rhoncus urna. Fusce gravida cursus varius. In posuere finibus leo cursus molestie. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris vulputate mi ut nunc finibus convallis. Praesent luctus erat eu urna facilisis convallis. + +Nam efficitur tempus augue varius porttitor. Pellentesque scelerisque dolor scelerisque, dignissim massa eget, iaculis nibh. Praesent viverra molestie dui a pulvinar. Mauris malesuada mattis felis, vitae sagittis metus pellentesque viverra. Phasellus faucibus congue blandit. Pellentesque mattis, justo sed imperdiet rutrum, metus metus porta nisi, vel malesuada lorem massa at dolor. Nullam nisi eros, tristique quis mollis ac, hendrerit nec leo. Praesent viverra molestie vestibulum. Nullam eu aliquam risus, at dignissim diam. Quisque blandit fermentum erat, sed ullamcorper augue pellentesque in. Integer eleifend libero non lacus sagittis auctor. Aliquam volutpat interdum accumsan. + +Etiam in eros porta, bibendum lacus eget, feugiat orci. Integer massa dui, commodo ac luctus nec, ornare id velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis dignissim scelerisque ex. Aliquam tempus cursus nibh, sed scelerisque libero sagittis ut. Morbi finibus vestibulum nulla, nec aliquam urna venenatis vulputate. Pellentesque ultricies, lorem a dignissim bibendum, augue nisi eleifend libero, cursus ultrices nulla purus a nunc. Quisque dictum pulvinar turpis vitae finibus. Etiam eget ultrices diam. Sed commodo enim ante, fermentum rhoncus tortor tincidunt ac. Suspendisse fermentum aliquet erat non posuere. Aenean vel dapibus lorem. + +Aenean lorem libero, rutrum vel egestas vel, pellentesque ut ante. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis diam, pellentesque sit amet ultricies sed, vestibulum in leo. Morbi aliquet, quam ut fringilla sollicitudin, justo risus suscipit mi, in vulputate neque erat ut turpis. Duis auctor dui non urna sagittis dictum. Vestibulum nec felis vel sapien congue volutpat a a orci. Phasellus tincidunt libero ac lorem feugiat, ac elementum lectus eleifend. Praesent sit amet est id massa convallis congue. Integer ac nunc eget orci pharetra vehicula. Mauris et ultricies diam. Nunc et pellentesque lacus, eget bibendum sapien. Morbi condimentum mi ac metus euismod convallis. Proin consequat rhoncus varius. Maecenas feugiat elementum vulputate. + +Vestibulum tempor feugiat dapibus. Ut ornare in augue non euismod. Nulla faucibus gravida condimentum. Curabitur pharetra dui non lorem sagittis iaculis. Quisque vitae nibh magna. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce quis pharetra sem, sed aliquet lectus. + +Fusce volutpat tempor tincidunt. Pellentesque ultricies imperdiet tincidunt. Integer porta dictum lorem, non luctus tellus bibendum sit amet. Quisque posuere felis et est dapibus, commodo rutrum leo molestie. Fusce sodales felis sed sem pharetra pretium. Praesent nec sollicitudin nisl. Donec volutpat convallis elementum. Duis id libero augue. Nulla facilisi. + +Nulla viverra, urna nec efficitur vulputate, metus odio lacinia purus, in sagittis elit erat vel massa. Fusce ligula leo, finibus non felis dignissim, dictum ullamcorper odio. Phasellus vel eros eu sem venenatis sagittis a nec nulla. Pellentesque sapien elit, lobortis quis dictum et, maximus vitae metus. Curabitur quis aliquam eros. Quisque cursus condimentum urna vel efficitur. Etiam aliquet lorem ut varius suscipit. In viverra molestie felis vitae vehicula. Curabitur cursus, nunc sodales faucibus ullamcorper, urna magna dapibus velit, accumsan blandit mi quam nec justo. Donec ac dui lorem. Sed eu sagittis nulla. Suspendisse ut ante lacus. Fusce non tristique felis. Nulla convallis consectetur arcu, semper egestas urna efficitur quis. Nulla ac turpis at leo pretium maximus. Morbi cursus diam ac purus imperdiet, non commodo tellus cursus. + +Mauris ut eros suscipit, suscipit nisi vel, porttitor sapien. Morbi in nulla sapien. Cras maximus pretium justo, vel eleifend urna vulputate sed. Aenean egestas nisl ex. Curabitur sed pharetra ligula. Nulla blandit lorem id mauris tincidunt, at elementum risus aliquet. Quisque id interdum dui. + +Aenean nec elit condimentum ex viverra hendrerit. Pellentesque blandit nibh elit, a ultrices orci vestibulum vitae. Donec ultricies malesuada justo ac luctus. Pellentesque laoreet nunc a sem varius, ac dapibus ligula volutpat. Aenean sem felis, aliquam nec ullamcorper quis, ullamcorper non ante. Pellentesque libero leo, sagittis nec tempus a, tincidunt eget risus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec elementum massa vel diam suscipit suscipit. Suspendisse eget neque porta, cursus orci eget, imperdiet libero. + +Vivamus faucibus vulputate sapien, non pulvinar ex sodales vitae. Maecenas consequat est urna, id faucibus eros dictum at. Vestibulum tortor mi, porta vitae sagittis sed, convallis quis enim. Vestibulum gravida justo pulvinar sem ornare, efficitur dictum neque varius. Nullam egestas congue tellus vitae interdum. Nam lectus erat, porta vitae lorem non, mollis semper velit. Nulla vestibulum tincidunt elit. Phasellus sed vulputate leo. + +Sed efficitur quam ac iaculis volutpat. Praesent nec feugiat ex. Aenean at ligula iaculis, imperdiet lacus et, condimentum magna. Integer euismod leo id mauris condimentum, quis semper lacus blandit. Sed volutpat neque urna, sit amet ullamcorper est dignissim non. Vestibulum tristique sollicitudin risus, sed hendrerit massa finibus id. Vestibulum sit amet risus non eros vehicula malesuada. Nam facilisis lacus sed quam pulvinar, at fermentum lectus tincidunt. + +Vivamus laoreet sem nec odio blandit, ut ornare sem egestas. Suspendisse potenti. Nulla aliquam pretium volutpat. Maecenas a orci suscipit, aliquam eros a, maximus urna. Aliquam eu gravida justo, et hendrerit justo. Suspendisse a commodo lorem, quis viverra nisl. Nulla vel pellentesque nisl. Nulla ligula tellus, vehicula sit amet turpis in, sodales tincidunt tellus. Proin ut nibh sed ipsum porta dignissim vel sed mauris. Interdum et malesuada fames ac ante ipsum primis in faucibus. In hac habitasse platea dictumst. Vestibulum efficitur nulla rutrum, accumsan lacus at, dignissim tortor. Morbi egestas metus ut nibh tincidunt posuere at sed elit. Integer eget hendrerit nulla. + +Donec sagittis sagittis tempus. Proin iaculis neque vehicula commodo faucibus. Pellentesque erat ante, vehicula sed efficitur vitae, varius non sem. Mauris pellentesque, felis nec egestas scelerisque, nulla nunc fringilla arcu, feugiat fringilla quam neque in elit. Nullam ut ex odio. Mauris enim risus, consequat at suscipit at, pulvinar ut arcu. Fusce mollis sem sed tellus tempus pellentesque. In pharetra, libero sed tristique vestibulum, massa velit egestas risus, at mollis augue lorem sit amet turpis. Mauris risus dui, sagittis vitae congue ut, condimentum ut augue. Quisque non sollicitudin purus, sit amet consectetur sapien. + +Sed scelerisque ipsum sed augue varius, sit amet ultricies purus posuere. Etiam vehicula at nunc in posuere. Cras eget risus a sapien mollis facilisis. Morbi vel purus auctor, faucibus mauris non, malesuada leo. Donec venenatis consectetur libero in pretium. Ut efficitur molestie metus id scelerisque. Nunc dolor justo, pharetra vel sagittis vitae, placerat ut justo. Donec tempor fermentum est semper auctor. Nullam tincidunt risus non risus elementum varius. Suspendisse euismod augue metus, nec pellentesque enim sagittis ac. Morbi et lectus quis justo commodo varius commodo vel risus. In bibendum purus in erat ullamcorper, sit amet dignissim sapien scelerisque. Quisque tempus elementum rutrum. + +In viverra sollicitudin pretium. Sed finibus scelerisque sollicitudin. Nulla lobortis purus in lectus iaculis, a ornare ex accumsan. Morbi malesuada mollis placerat. In hac habitasse platea dictumst. Pellentesque sed dui quis odio scelerisque molestie eu nec libero. Proin viverra magna ligula, at luctus lacus posuere sed. Nullam non congue mi. Praesent non mattis neque, sit amet tempus diam. Mauris eget cursus lacus, id dictum mi. + +Sed semper velit in vehicula tristique. In lobortis est augue, et maximus tellus iaculis ut. Proin quis ex maximus erat iaculis imperdiet. Curabitur ultrices turpis ac nibh congue ornare. Fusce a aliquet felis, nec sodales tellus. Nunc mauris ante, feugiat et facilisis at, pharetra ultricies dui. Integer felis metus, porttitor ac ipsum vitae, placerat varius ex. Morbi in dui a nunc aliquam posuere. Nulla tempus tortor ac lorem consectetur sodales. Praesent sit amet placerat diam. Curabitur sodales mauris ante, et tincidunt lacus ultricies quis. Nulla eu finibus nisl, sit amet volutpat leo. Donec ligula enim, feugiat non aliquet nec, congue interdum lorem. Pellentesque a nisi blandit, semper massa sit amet, auctor eros. + +Aenean ligula libero, commodo a erat at, tristique facilisis quam. Sed congue fringilla lacus, vel iaculis quam suscipit ornare. Curabitur non pretium mi. Donec condimentum odio dui, id malesuada ipsum porta ac. Aliquam imperdiet cursus ullamcorper. Duis cursus tincidunt nibh sit amet cursus. Aliquam urna orci, sodales ac ipsum at, placerat lobortis neque. Sed sit amet convallis felis, vestibulum finibus arcu. Phasellus venenatis egestas felis, id aliquam turpis iaculis non. Proin a lacus ut felis malesuada sodales. Duis elementum, eros ac placerat bibendum, quam nisl varius mauris, vel venenatis neque augue et justo. Nunc varius sem velit, vel tempus mi aliquet id. Fusce purus massa, mollis id nulla non, dignissim cursus massa. Sed sollicitudin interdum felis, nec eleifend nisl feugiat eget. Nulla rutrum id odio ut consectetur. Maecenas fermentum turpis vitae libero ullamcorper suscipit. + +Vestibulum auctor lectus ligula, non sollicitudin odio maximus nec. Cras faucibus, felis sed suscipit tempor, risus lorem consectetur est, eget pulvinar nibh dui eu dolor. Vestibulum pellentesque, ex aliquam vulputate laoreet, libero lorem rhoncus risus, vitae congue velit justo vitae nisi. Nullam placerat venenatis tortor, sit amet lacinia augue placerat nec. Quisque vulputate lectus vel pulvinar condimentum. Nullam at libero iaculis, mattis purus vel, tincidunt diam. Suspendisse eu ullamcorper eros. Nulla id eros odio. Ut enim leo, ultricies quis mauris sed, interdum commodo felis. Etiam enim lacus, scelerisque a interdum eget, mollis vel tellus. Phasellus vel vulputate orci. Nulla ornare leo sed arcu rhoncus convallis. Duis libero arcu, pulvinar ut tellus vitae, aliquam euismod magna. Donec semper nisi eget nulla tempus, sed condimentum massa sollicitudin. + +Suspendisse bibendum ante vitae ullamcorper semper. Praesent dui est, elementum nec lacus in, pellentesque accumsan sapien. Cras quis urna at lacus posuere commodo eget nec arcu. Nunc varius ante quis ligula rhoncus, ut dignissim metus commodo. Integer volutpat gravida nunc eget faucibus. Quisque imperdiet, mauris vitae cursus malesuada, lacus mauris tempus sem, ut accumsan purus lectus quis nisi. Vestibulum aliquam dui sed nisl laoreet, id posuere quam imperdiet. Aliquam ultricies non enim vel tempor. Donec sed feugiat justo, vel rutrum enim. Phasellus lorem quam, dapibus a tincidunt vulputate, pellentesque non lorem. + +Nunc quam diam, condimentum sit amet enim semper, hendrerit laoreet magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam et pulvinar leo, ac fermentum lectus. Sed erat ante, mattis ac tempor vitae, euismod at tortor. Ut sagittis fringilla scelerisque. Ut pulvinar molestie leo eu faucibus. Sed quis efficitur purus. Pellentesque nibh nisi, porta id orci id, sagittis sodales tellus. Ut venenatis velit a lectus lacinia, at ultrices nisi commodo. Vivamus eros orci, ornare vel ullamcorper elementum, posuere id ligula. Aliquam non massa pellentesque, semper ipsum scelerisque, dapibus leo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nulla pretium leo diam, sit amet bibendum lacus tempus quis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer quis pulvinar nibh. + +Mauris sed eros nisi. Cras malesuada, turpis vitae laoreet porta, sapien odio maximus nulla, ut efficitur mauris odio tincidunt elit. Nam ut urna eros. Sed at vestibulum risus. Nulla luctus pulvinar rhoncus. Pellentesque maximus ligula a est ullamcorper, sed tempus tortor ultrices. Curabitur ligula odio, mollis sit amet risus quis, tempor auctor magna. Suspendisse vel quam quis lorem commodo facilisis. Donec eu ipsum suscipit, molestie dui sed, fringilla dui. Proin placerat ex a lorem sodales convallis. Sed molestie quam mi. + +Fusce laoreet nec dolor id bibendum. Nunc condimentum vulputate massa lacinia fermentum. Donec maximus cursus eros sed porta. Nunc eu porta turpis. Nulla cursus et nisl eu elementum. Ut rutrum scelerisque aliquet. In tellus erat, rhoncus id tempus vitae, vestibulum non quam. + +Vivamus luctus elit a efficitur dapibus. Praesent magna mi, pellentesque sed accumsan dapibus, blandit at lectus. Praesent sodales erat diam. Pellentesque placerat lobortis enim, a dapibus ligula molestie ut. Phasellus dui augue, suscipit ac urna non, iaculis eleifend augue. Maecenas sed elit iaculis, pretium ligula lacinia, aliquam libero. Nulla sem mi, pellentesque viverra lorem vel, luctus vulputate augue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque fermentum nunc ex, at lobortis turpis congue vitae. Suspendisse iaculis elementum enim commodo facilisis. Vestibulum non neque tellus. Proin vitae sodales urna. Mauris interdum purus et neque tempus, vitae mattis libero finibus. Suspendisse iaculis condimentum nibh, eu mattis enim vehicula a. + +Fusce dictum urna non lectus ullamcorper vestibulum id in elit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc fermentum odio non ante sollicitudin posuere. Praesent vulputate quam nec magna euismod pellentesque. Etiam tempor nunc eget velit volutpat eleifend. Mauris sodales nunc ullamcorper, gravida purus eget, rutrum sem. Nam a sapien rhoncus, scelerisque lacus ac, condimentum ipsum. Pellentesque suscipit, ligula ut rhoncus ullamcorper, ligula augue rutrum lorem, eget aliquam risus tortor eget metus. Curabitur scelerisque bibendum purus vel vulputate. Morbi et odio at neque sodales suscipit. Nam at pretium nulla. In scelerisque vulputate suscipit. Nulla facilisi. Pellentesque eu sem eu ligula efficitur viverra. Pellentesque gravida consequat urna id bibendum. + +Nulla consectetur facilisis est nec aliquam. Proin hendrerit magna suscipit ante accumsan hendrerit. Nulla ut sem id neque tempor vehicula. Sed eget massa eget sem placerat tincidunt. Ut eleifend, justo sit amet egestas lacinia, ex velit pharetra nulla, nec aliquet elit neque ut turpis. Quisque in gravida velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed euismod malesuada convallis. Ut ultricies, magna et blandit fringilla, dolor dolor maximus sapien, in aliquet augue est vel odio. Nulla commodo elit massa, euismod tempor turpis aliquet eget. Morbi non odio et tortor egestas ultrices. Etiam semper, ipsum et dictum pharetra, eros purus bibendum lacus, vel laoreet lacus sem vehicula nibh. Nunc enim nulla, pulvinar sit amet lobortis sed, porttitor molestie risus. Morbi tincidunt diam mi, nec auctor sem rutrum at. + +Donec pellentesque odio odio, eu condimentum risus consectetur quis. Vivamus ullamcorper, mauris non semper rutrum, dui risus suscipit tellus, ut tempor velit risus vitae nibh. Duis tempor nibh at tristique rutrum. Cras congue nisl at sem sollicitudin efficitur. Aenean auctor purus vel libero fermentum elementum. Mauris convallis orci id interdum accumsan. Aliquam at metus risus. Sed accumsan, quam id dignissim congue, velit risus eleifend ligula, ut fringilla elit erat at neque. Aliquam tempor, quam ut ultrices volutpat, orci lectus vulputate turpis, eget pharetra purus nisi non lorem. Ut condimentum convallis justo, a volutpat eros. Sed tempus turpis leo, in convallis est fringilla sed. Vivamus eu laoreet tellus. Quisque ullamcorper ullamcorper leo. Nam fermentum egestas facilisis. Nulla egestas ligula feugiat urna molestie, faucibus convallis erat accumsan. Cras pellentesque ipsum lectus, vitae luctus dolor suscipit nec. + +Vivamus vel nibh sed mi tincidunt cursus quis facilisis tellus. Donec eget est eu velit pretium molestie. Maecenas lacinia risus turpis. Sed tristique id risus sit amet venenatis. Duis maximus, metus ac molestie convallis, quam ex dapibus sem, at rutrum metus nisi quis lectus. Suspendisse sodales in nisi at hendrerit. Maecenas suscipit lobortis vulputate. Nunc convallis sit amet elit eget porta. Mauris tincidunt massa in augue finibus iaculis. Vestibulum imperdiet, orci vel bibendum laoreet, ligula quam mollis lacus, a eleifend nisi tellus vitae ipsum. Cras non ante sollicitudin, auctor dolor id, condimentum tellus. Morbi malesuada leo nec lectus scelerisque, nec interdum lacus ornare. Integer ultrices ligula nunc, sed blandit urna aliquam eget. Proin consequat viverra ex non rhoncus. Phasellus id nibh at tortor sodales blandit. Donec dignissim ipsum vel ligula malesuada rhoncus. + +Nullam mauris sem, dapibus ac nisl at, hendrerit faucibus nisi. Mauris dictum fermentum pellentesque. Praesent in gravida odio. Vestibulum pharetra iaculis est quis tincidunt. Sed venenatis rhoncus lacus, non porta ante vulputate at. Duis fringilla ipsum at urna euismod molestie. Duis a porttitor ante. Mauris pharetra elit et metus auctor, a eleifend sapien porta. Vivamus ut tincidunt purus, lobortis euismod tellus. Nullam non nulla gravida, laoreet tellus ac, placerat urna. + +Sed justo sapien, scelerisque nec nisl vel, efficitur aliquet purus. Phasellus eget posuere nisl. Integer porta vel purus nec ornare. Pellentesque rutrum in nulla at hendrerit. Nullam elementum sodales volutpat. Duis tempus, purus sagittis auctor ullamcorper, dui erat hendrerit nulla, id finibus eros dui quis risus. Quisque posuere nunc quis augue placerat lacinia. Nunc quis lorem vulputate, tincidunt enim nec, rhoncus odio. Ut porttitor porta velit ut vulputate. Duis convallis sagittis magna nec gravida. Ut eu luctus sem, non placerat erat. Vivamus viverra ut ligula ac finibus. Cras ligula urna, tincidunt id risus eu, dapibus viverra lorem. Aliquam erat volutpat. Sed dolor nisi, faucibus non ligula faucibus, laoreet bibendum diam. Vivamus sagittis lacus ut condimentum tincidunt. + +Proin tristique mi ullamcorper dolor accumsan mollis. Nulla facilisi. Pellentesque iaculis mattis augue ac rutrum. Mauris in nulla at ligula fringilla pulvinar. Nam commodo ornare nibh ac viverra. Maecenas eget augue a risus mattis ullamcorper non at nibh. Aenean nec erat diam. Pellentesque augue nisl, venenatis mollis dolor non, bibendum malesuada leo. Pellentesque elementum purus ornare mauris iaculis porttitor. Sed mollis tempor dui eu elementum. Donec porttitor tellus non mattis gravida. Mauris venenatis suscipit convallis. Sed dolor sapien, pellentesque et tellus a, tempus cursus magna. Nunc lobortis leo magna, at maximus orci ultrices eget. Nullam vulputate dictum nisi, vel egestas orci. + +Nam nibh mi, ornare sit amet nibh vel, lobortis lacinia leo. Ut egestas mi vel nunc luctus vestibulum. Nullam id tempus felis, eget convallis erat. Aliquam venenatis ut tellus id fringilla. Aliquam erat volutpat. Sed vehicula, odio nec mollis dapibus, ligula sapien consequat risus, ut volutpat diam neque ut neque. Mauris venenatis libero vitae sem elementum, sit amet maximus massa varius. Maecenas malesuada mi id leo euismod, eu maximus sapien vehicula. Nulla facilisi. Duis nec venenatis ante, non pulvinar lacus. In sollicitudin sit amet velit suscipit egestas. Maecenas a lectus euismod, dictum nulla at, malesuada lectus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec lorem massa, laoreet non elit nec, sollicitudin blandit felis. + +Cras dignissim fermentum tellus ut fringilla. Maecenas maximus eros pretium sagittis venenatis. Maecenas id enim ac est semper efficitur ac hendrerit leo. Cras eu arcu tincidunt risus pellentesque aliquam. Pellentesque vel sodales libero, non rhoncus enim. Integer vulputate vulputate libero, eu consectetur eros euismod vitae. Fusce magna nibh, vehicula sed turpis eget, malesuada ultricies sem. Sed convallis sem nisl, rhoncus mollis mauris bibendum ut. Pellentesque vitae massa a justo dapibus laoreet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Sed mollis egestas nisl vel ultricies. Phasellus sit amet varius leo, sed vehicula sapien. Etiam in urna eget neque aliquet ultricies in nec quam. Aliquam at feugiat elit. + +Maecenas placerat nisi ultrices neque pulvinar, et ultrices ante tempus. Vestibulum laoreet quam lacus, eget commodo magna dictum consectetur. Aliquam erat volutpat. Aenean tincidunt ipsum sit amet justo aliquet tempus. Quisque blandit sit amet est facilisis dictum. Sed non consequat dui. Integer purus diam, gravida quis tortor lobortis, iaculis vehicula sem. Morbi pellentesque est elit, vitae interdum elit laoreet et. Vestibulum in blandit ante, eu blandit velit. Morbi sagittis eu est eu vulputate. Nullam ac mi feugiat nibh feugiat suscipit. Sed interdum tincidunt efficitur. Integer dictum sem erat, ut pharetra libero lacinia in. Mauris at hendrerit odio, a facilisis lacus. Donec blandit massa ac nisi ultrices faucibus. + +Aenean tempus id ligula at mollis. Cras id dui magna. Integer id neque tincidunt, rhoncus nunc et, laoreet nibh. Pellentesque consectetur odio ligula, et consectetur tortor scelerisque non. Cras quis ex pharetra mi ornare lobortis. Phasellus fringilla interdum felis, vel aliquam ligula. Mauris cursus varius turpis in iaculis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent eget mi id nulla semper dapibus. + +Vivamus lacus augue, eleifend dignissim ipsum eu, interdum accumsan massa. Nam id quam vel ligula consectetur volutpat id eget eros. Aliquam sed felis velit. Duis egestas velit ac dolor blandit, ut elementum mi tincidunt. Integer varius nisl a sapien pellentesque, et pellentesque ante elementum. Fusce ac orci interdum, faucibus nisl ut, sagittis nisi. Vestibulum sed diam eu magna congue ornare. Integer dignissim ligula sit amet mauris pretium vulputate. Duis ac tincidunt magna. Sed vehicula, ante nec vulputate venenatis, mauris odio blandit dolor, vitae lacinia nibh lorem et metus. + +Sed non sapien vel lorem tempor semper ac ac tellus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas auctor ante sit amet suscipit vulputate. In ac cursus turpis. Donec eu sem bibendum, blandit est nec, blandit tortor. Pellentesque scelerisque justo vitae magna cursus, vitae egestas libero interdum. Pellentesque vitae ligula vel mauris vehicula convallis. Nunc ornare lectus sit amet lorem aliquet dignissim. Cras nec ligula pretium, semper nulla a, pulvinar lacus. Sed ac augue bibendum, posuere ipsum eu, venenatis quam. Sed elementum nunc quis odio pellentesque, a vestibulum ex congue. Cras id malesuada arcu. Mauris ut egestas nulla, sed tempus ligula. Donec ac elit ut nisi pulvinar elementum. Suspendisse id auctor lectus, vitae ultricies lacus. + +Vestibulum pulvinar ornare cursus. Curabitur accumsan sollicitudin mi in vestibulum. Vestibulum vulputate tincidunt luctus. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus a mattis neque, id malesuada odio. Aliquam id auctor magna. Vestibulum quis nibh lacinia, tincidunt felis sed, vestibulum ex. + +Donec placerat ipsum diam, vel imperdiet velit eleifend ac. Quisque dapibus erat non dui convallis eleifend. Praesent pellentesque felis id suscipit rutrum. Donec quis lacinia turpis. Nulla justo eros, fringilla vel fringilla in, euismod sollicitudin arcu. In ut lobortis arcu, at rhoncus elit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. + +Pellentesque iaculis quam nec interdum eleifend. Fusce mauris mauris, imperdiet sollicitudin volutpat eu, sollicitudin blandit leo. Vestibulum hendrerit diam gravida erat imperdiet, blandit dignissim dui tempor. Phasellus viverra ante quis aliquam finibus. Duis sed condimentum augue, nec sollicitudin dui. Ut ullamcorper metus ac ligula accumsan, ac fringilla metus gravida. Morbi rhoncus est nunc, at lacinia ex fringilla sit amet. Integer lobortis ultricies nisi vitae accumsan. Nulla nec sagittis ante. Mauris bibendum nisi ut magna vulputate maximus. Praesent in elit eu metus dignissim cursus. Ut a tellus felis. Mauris eget ornare diam. + +Suspendisse potenti. Nam nec tortor ex. Duis eu elementum ligula. Pellentesque efficitur sodales orci, vitae sollicitudin odio tempor ac. Nam lacus ante, lobortis vitae lobortis eu, eleifend et velit. Nulla et lectus eu nibh tristique malesuada a sit amet felis. Proin molestie, lectus vitae sagittis malesuada, massa velit finibus est, id vestibulum dolor felis eget lectus. Phasellus varius pellentesque neque, a malesuada ex mattis eget. Nulla facilisi. Phasellus interdum placerat lobortis. Sed et odio tristique, viverra libero et, sagittis diam. Proin tristique lectus id bibendum maximus. Integer ornare euismod ligula nec malesuada. Nulla facilisi. Nullam bibendum hendrerit pharetra. + +Duis pharetra auctor felis, eget aliquet justo commodo at. Donec quis nibh non arcu sodales efficitur. Aenean lorem sapien, tincidunt eu sapien nec, convallis tempor diam. Curabitur volutpat, felis ut congue euismod, lacus nisl euismod ipsum, vel consectetur orci nibh sed ligula. Curabitur consectetur vitae mauris sed tempor. Fusce vel pretium nibh, et tristique dolor. Aliquam semper a ante non finibus. Praesent euismod urna augue, at feugiat orci commodo ut. Duis imperdiet purus non augue cursus gravida. Maecenas sodales purus et sollicitudin venenatis. Nam ultrices lorem lectus, ut pretium turpis hendrerit eget. Vestibulum vel lacinia lectus, at dignissim diam. Vivamus ut tortor ac tortor blandit finibus. Etiam porttitor tortor sit amet elit lacinia gravida. Pellentesque pretium, orci vitae tempor vehicula, nisi tellus tincidunt sapien, id egestas felis quam a nunc. Pellentesque sed vestibulum nisl. + +Mauris congue, justo vel dapibus pretium, ipsum augue consectetur leo, at aliquam ipsum neque non mauris. Nam sollicitudin in urna eu blandit. Aliquam erat volutpat. Morbi eget interdum ante. Pellentesque congue gravida arcu, eget ornare ante elementum nec. Integer a auctor dolor, in varius sem. Etiam dictum nibh magna, at volutpat nunc blandit eu. Curabitur at sem ipsum. + +Nulla porttitor erat eget volutpat iaculis. Pellentesque egestas, nisl vel ultrices efficitur, ex magna condimentum urna, in tincidunt massa turpis eget metus. Etiam vitae magna elementum, fermentum justo ut, pretium velit. Aliquam aliquet venenatis malesuada. Quisque consequat enim metus, pellentesque blandit leo rhoncus id. Vivamus vestibulum, est in condimentum mollis, ligula eros blandit lorem, vitae dignissim lectus mi eget nulla. Cras posuere leo at neque convallis, sed pretium massa ultrices. Morbi tempus porttitor turpis. Nam vitae mauris et massa venenatis tincidunt. Quisque vestibulum lacus ligula, in sodales felis elementum vel. Sed a tellus condimentum, sodales nisi id, aliquet elit. + +Vestibulum ac ex eu turpis lacinia condimentum nec eget ipsum. Nulla non imperdiet erat, at malesuada lorem. Praesent efficitur imperdiet augue vel laoreet. Sed dignissim, ante non porta feugiat, enim nunc rhoncus lorem, eu luctus turpis risus sit amet orci. Vivamus bibendum pharetra venenatis. In at gravida nisi. Vestibulum pulvinar sapien ac augue scelerisque, vitae blandit nibh malesuada. Nunc vitae est faucibus, accumsan risus a, laoreet urna. Cras imperdiet lacus in augue facilisis dignissim. Duis sed nisi quis diam mollis rutrum. + +Vestibulum eget egestas neque. Praesent viverra, velit quis porttitor euismod, sem libero venenatis mauris, id congue urna nulla sed lorem. Nulla mollis metus nec diam malesuada aliquam. Duis ac risus nunc. Cras sollicitudin urna nunc, id sodales quam gravida sit amet. Fusce in vulputate orci, in venenatis lorem. Donec a sagittis ipsum. Quisque consequat sapien tellus, sed efficitur lacus aliquam eu. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean in neque at augue elementum commodo pellentesque eget ligula. Nullam condimentum efficitur tincidunt. Phasellus posuere tincidunt odio sed facilisis. Aenean eu risus at est euismod facilisis. Curabitur elit purus, malesuada quis blandit id, rutrum vitae dui. Praesent porta rutrum erat, ullamcorper viverra nunc. Cras ut velit dui. + +Etiam posuere pulvinar mi at ullamcorper. Pellentesque finibus, tellus non convallis commodo, orci nibh dapibus nisl, at aliquam purus nulla eget dui. Praesent fringilla urna nec nulla pellentesque, nec rhoncus turpis ultricies. Sed laoreet velit pellentesque libero varius, ac interdum urna viverra. Phasellus sed consectetur massa. Morbi quis velit nec ipsum varius tempor. Proin id sodales felis. Aliquam lacinia risus quis ligula condimentum sodales. Nulla vel arcu aliquet neque iaculis aliquet. Cras sed lorem eu turpis tincidunt sodales. Sed pulvinar elementum ligula, nec faucibus nisl. Fusce nec tellus eget dui tempor sagittis. In vitae enim in ex viverra commodo. Duis est erat, fringilla ac semper a, dapibus in tortor. + +Maecenas commodo vulputate iaculis. Aliquam non facilisis est. Donec pellentesque vitae nibh nec volutpat. In commodo metus placerat lorem commodo, non lacinia nibh bibendum. In viverra rhoncus erat. Mauris nec nisl blandit, elementum justo nec, accumsan libero. Aliquam elementum, velit et ullamcorper convallis, turpis lorem elementum lorem, quis consequat tortor eros ut erat. Curabitur et enim quis felis vulputate congue et vel purus. Sed elementum interdum ipsum, sed tincidunt arcu scelerisque et. + +Aenean interdum elementum mauris ut porta. Mauris vel purus ac odio vulputate pulvinar at quis odio. In turpis turpis, convallis in augue id, elementum vulputate lorem. Sed tincidunt fermentum vulputate. Nunc ipsum ipsum, molestie vel convallis id, pharetra vel arcu. Maecenas vel dui elit. Sed blandit dolor sit amet risus commodo faucibus. Duis rhoncus felis arcu, vel aliquam nisi faucibus sed. Suspendisse cursus eget nunc ut bibendum. Fusce non ligula risus. Curabitur vitae cursus metus, quis fringilla diam. Phasellus turpis ante, pulvinar ac turpis tristique, sollicitudin congue lectus. Proin quis ipsum at ipsum euismod euismod. + +Nunc ut fermentum nunc. Donec id commodo lacus, at hendrerit justo. Donec cursus purus sodales nunc commodo, quis bibendum elit hendrerit. Quisque quis pellentesque nibh, ac vulputate neque. Aenean non placerat felis, eget feugiat ligula. Vivamus a eros accumsan, cursus neque at, ultricies magna. Fusce tincidunt tellus vitae mi rutrum laoreet sed quis ligula. Nullam ullamcorper ligula ligula, vel fringilla metus aliquet a. Morbi aliquet, mauris vel interdum venenatis, ex arcu venenatis tortor, at tincidunt dui ipsum et arcu. Nulla blandit gravida nulla ac iaculis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed ullamcorper lectus in sapien lobortis, eget posuere massa varius. + +Cras lacinia nunc faucibus mauris placerat pretium eget non sapien. Morbi viverra bibendum posuere. Aliquam accumsan sagittis dolor non iaculis. Sed nunc odio, lobortis in dolor ac, rutrum fermentum velit. In venenatis, velit in molestie semper, est magna condimentum dui, aliquam auctor lorem nulla vel ligula. Morbi vehicula turpis turpis, quis mollis justo tempus vitae. Proin luctus lacus in mauris porttitor aliquet. Duis vitae nunc ex. Nullam eget erat vitae nulla iaculis rhoncus. Sed lacus dui, suscipit eu leo vitae, tincidunt dignissim risus. Praesent ut massa ut arcu sagittis consequat. Sed sit amet tincidunt turpis. Nulla bibendum, felis eget posuere dictum, libero mi tristique elit, at venenatis neque elit ac quam. Curabitur nisi sem, scelerisque nec nunc ac, pulvinar pharetra odio. Curabitur egestas pellentesque arcu sed suscipit. In mattis dolor vel dui mollis feugiat. + +Sed commodo, dui ac vestibulum dictum, tellus libero tincidunt lacus, viverra commodo est felis vitae urna. Proin tincidunt neque vel turpis eleifend laoreet. Vestibulum sagittis, tortor sed iaculis consequat, urna ante sagittis est, ac ullamcorper lorem nibh dignissim odio. Nam arcu mi, cursus et blandit nec, aliquam ut nulla. Aenean quis iaculis tellus, eu egestas augue. Etiam pretium eget nisi quis iaculis. Aliquam sed convallis eros. Ut bibendum rhoncus lacus, in vestibulum dui ultricies id. Fusce vestibulum, mauris ut tempor consequat, dolor nisl pellentesque elit, in porta arcu elit vel ante. Vivamus at nisl est. Etiam nec blandit tortor, at pulvinar orci. Proin semper dapibus tincidunt. Phasellus lobortis enim ullamcorper dolor tempor cursus. + +Mauris a libero in enim gravida aliquet ac sit amet nibh. Vestibulum ac neque posuere, blandit libero ac, vehicula enim. Aliquam auctor iaculis eros sit amet molestie. Quisque faucibus turpis et massa tristique, nec dapibus mauris aliquet. Proin blandit aliquet mauris, non tincidunt odio blandit vel. Curabitur a nibh in eros commodo tincidunt eu et libero. Curabitur sit amet dapibus ex, in condimentum magna. Sed eu sem sem. Nunc tellus dolor, rutrum eu mauris nec, congue feugiat purus. Fusce tempor, neque vitae bibendum imperdiet, dolor ipsum condimentum urna, et egestas quam tortor in ex. Aliquam velit magna, commodo hendrerit sagittis sed, feugiat eget erat. Nunc quis ullamcorper velit, eu consequat augue. Nam arcu mauris, condimentum sit amet magna in, finibus scelerisque nunc. Mauris erat est, hendrerit ac accumsan eget, facilisis ut nisl. Quisque dignissim arcu quis diam tincidunt tristique. Sed rhoncus nisl non enim fermentum, a lobortis dolor consectetur. + +Sed eget condimentum ligula. Vestibulum vitae cursus eros. Donec elementum sapien magna, posuere iaculis sem ultrices lobortis. Morbi eu bibendum lectus. Suspendisse ante eros, ullamcorper ac viverra eget, pellentesque sed sapien. Duis sit amet tincidunt dui, vitae lobortis purus. Sed venenatis tincidunt volutpat. Vivamus a nisl ac elit consectetur semper ut eu libero. Proin id cursus ex. In hac habitasse platea dictumst. Aenean sed nisi vitae odio venenatis pulvinar vitae ac risus. Sed varius magna ut erat luctus vehicula. + +Nunc non ex eget purus blandit faucibus at quis velit. Donec quis mi vestibulum, facilisis nisi quis, tincidunt turpis. Sed bibendum metus sed consectetur mollis. Maecenas fermentum, erat finibus pulvinar lacinia, ex risus dictum sem, ut vestibulum augue diam vel diam. Donec ac massa non nibh pretium laoreet eget in orci. Nulla placerat eleifend mi, pretium vestibulum diam condimentum vitae. Nunc odio turpis, feugiat vitae turpis eget, pellentesque commodo turpis. Etiam sapien purus, consequat nec mi eget, consectetur efficitur neque. Phasellus porttitor sapien sit amet nunc semper, vel bibendum nibh finibus. Ut ac imperdiet ex, eu congue felis. In posuere nisi felis. Mauris tempus pretium mauris, ac viverra nunc hendrerit id. Sed fermentum nec sem ac pulvinar. Integer dictum velit eget congue venenatis. + +Cras eros dolor, venenatis ac dictum sed, dignissim nec sem. Curabitur tempor erat quis pretium interdum. Nunc vestibulum justo nisi, sit amet sagittis tellus consequat vel. Donec pharetra nunc vitae consequat eleifend. Quisque ut mauris quis nunc volutpat consectetur. Nam a suscipit ligula, at gravida libero. Vestibulum blandit, tellus sed bibendum volutpat, libero tortor convallis nisl, sit amet placerat lorem dolor nec libero. Integer blandit libero elit, ut congue ex euismod congue. Nulla sodales justo id eros condimentum faucibus. + +Proin quam ante, hendrerit at bibendum eu, pharetra at lectus. Proin finibus arcu id nisl aliquam dapibus. Fusce a suscipit nisl. Ut placerat ultrices nibh nec efficitur. Vestibulum vitae interdum magna. Donec lobortis finibus risus, et luctus ipsum efficitur euismod. Quisque dictum diam et venenatis euismod. Cras dictum molestie aliquet. Vestibulum imperdiet quam eget diam malesuada, quis pulvinar odio dignissim. Curabitur sapien elit, iaculis vitae justo eget, pretium malesuada elit. Donec gravida molestie tincidunt. Nullam at commodo ipsum. Sed nisi erat, tincidunt ultricies interdum consequat, pretium mollis ipsum. Vivamus in erat consectetur, sodales nibh at, porta nunc. Mauris semper, dui vitae condimentum tempus, mauris justo volutpat urna, ac ullamcorper tortor dolor in sem. + +Aenean leo ligula, egestas at vestibulum ac, porta vel mauris. Curabitur at lorem non mauris fringilla venenatis vel eu arcu. Donec posuere eleifend diam. Duis aliquet justo lacus, eu egestas erat eleifend sed. Sed non cursus urna. Sed pretium purus id blandit varius. Mauris turpis mi, lobortis eu tellus sit amet, maximus venenatis felis. Proin non varius enim, aliquet finibus velit. Praesent posuere pharetra ipsum eget varius. Phasellus non fermentum magna, ut iaculis augue. Praesent ut nisl nunc. + +Sed ligula tellus, interdum at sapien ut, dictum pellentesque nisl. Duis fringilla, sem nec elementum dapibus, nisl tellus maximus velit, eu varius sem nisl feugiat eros. Maecenas quis viverra lacus. Nulla nec commodo ex, nec placerat enim. Curabitur ultrices sagittis fringilla. Vestibulum sit amet enim sagittis, condimentum nisl sit amet, pulvinar orci. Ut at erat finibus erat bibendum convallis. Quisque euismod magna eget leo facilisis hendrerit. Cras venenatis, nisi quis sollicitudin volutpat, metus enim vestibulum nunc, at mollis leo leo nec est. Fusce fermentum tristique feugiat. Suspendisse sem est, condimentum ut ante at, egestas convallis ante. Vestibulum dictum, ex nec imperdiet laoreet, magna est ullamcorper augue, vel molestie quam odio vel ante. Donec dui dui, posuere a neque ac, condimentum vehicula magna. Curabitur suscipit, risus vitae bibendum posuere, mauris lorem viverra est, at tristique arcu quam quis leo. Donec sed posuere neque. Suspendisse iaculis aliquet condimentum. + +Morbi tempus condimentum diam eget bibendum. Aliquam varius magna quis lectus interdum, in elementum ligula tempor. Morbi vitae lorem sapien. Morbi luctus consectetur eros non aliquet. Pellentesque vestibulum sem sed ante accumsan faucibus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam ac suscipit justo. Nunc efficitur lectus eu arcu venenatis, vel accumsan ex suscipit. Vestibulum egestas ultricies tellus, et interdum enim pretium eu. Aenean rutrum est tincidunt rhoncus molestie. Phasellus hendrerit tellus et laoreet varius. Integer efficitur felis magna, nec sollicitudin arcu sollicitudin in. Curabitur non feugiat odio. Mauris nisi odio, luctus quis dolor sed, tristique luctus ex. Nullam libero enim, facilisis ut venenatis et, vulputate sed purus. + +Mauris a odio ut leo porttitor ultrices a et ex. Pellentesque vestibulum lacinia faucibus. In pellentesque eget augue at feugiat. Integer finibus augue dolor, in luctus lorem rutrum vitae. Donec pharetra lectus ac purus sollicitudin, vel tristique mauris pretium. Cras porttitor mi eu lectus consequat, a ultrices felis venenatis. In condimentum turpis in velit mattis laoreet. Aliquam sed mauris id nulla ultrices convallis vel vel velit. Suspendisse ut arcu finibus justo fermentum fringilla. Etiam in malesuada nisl. In hac habitasse platea dictumst. Duis a est lacinia, pretium dui eget, condimentum turpis. Integer ac placerat augue. Donec et eros felis. + +Integer cursus magna id quam sagittis consectetur. Aliquam erat volutpat. Quisque ullamcorper nisl nec massa dapibus facilisis vitae at nunc. Donec laoreet, libero in elementum tempus, enim odio porttitor felis, venenatis fermentum augue velit eu urna. Ut at ullamcorper enim. In molestie, velit et blandit maximus, erat nunc laoreet quam, vel finibus est mauris non sapien. Donec et dictum lorem. Nunc vestibulum, dolor eget tempus maximus, elit eros aliquam velit, vitae mattis mauris lectus ac tortor. Donec rutrum justo orci, id dictum metus vestibulum a. Etiam bibendum ipsum convallis, lacinia turpis vitae, dictum tortor. In velit dolor, scelerisque sed molestie nec, volutpat a turpis. Cras posuere commodo erat ut gravida. Quisque ante ipsum, volutpat a tellus non, dignissim ornare elit. Pellentesque sed porttitor dui, luctus rhoncus purus. In vitae ante pulvinar, consectetur orci quis, tempus velit. Curabitur tempus ligula id sapien ornare rhoncus. + +Maecenas eu nibh et elit accumsan blandit a at erat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam commodo feugiat condimentum. In non ante ut mauris eleifend lobortis. Phasellus eleifend vitae metus et semper. Nam sit amet rhoncus diam. Nunc molestie libero sed erat volutpat consequat. + +Aenean eget tristique odio. Vivamus quam tellus, dignissim sed faucibus sed, sagittis ut elit. Maecenas in ullamcorper sem. In at ipsum accumsan lectus vestibulum commodo nec non leo. Aliquam at suscipit felis. Nunc non egestas tortor. Donec sit amet eleifend eros. + +Sed eleifend nisi velit, in egestas erat condimentum eu. Pellentesque a tincidunt urna. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vestibulum dapibus mauris vitae elit auctor, id venenatis sem consectetur. Vivamus non leo pulvinar, blandit libero ut, vehicula arcu. Nullam elementum ex enim, at mattis massa pharetra eu. Nunc nulla magna, lobortis a magna sit amet, laoreet fermentum justo. Curabitur aliquam sollicitudin posuere. Aenean semper porta dictum. Mauris accumsan non nisi nec faucibus augue. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam in nibh eget ante viverra mattis. Etiam elit est, pharetra efficitur fermentum at, accumsan id eros. Aenean accumsan nisi facilisis tempor suscipit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nam magna erat, tincidunt eu placerat at, molestie at sem. In sit amet cursus mauris. Etiam ullamcorper lacus nisi, et porta metus semper id. Nulla mauris nunc, pharetra at facilisis a, consequat id metus. Sed convallis ligula non felis vulputate finibus. Curabitur nisl nulla, pharetra in justo a, dapibus ultricies dui. Morbi ultricies sollicitudin purus, quis pellentesque leo rhoncus sed. Curabitur sed eros quis lacus vulputate pretium. Vestibulum vitae urna nec arcu vulputate efficitur. Cras venenatis sodales dolor non ullamcorper. + +Donec eu placerat magna. Vestibulum justo lacus, luctus ac luctus sit amet, dapibus at orci. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec eu tortor ac justo pharetra hendrerit. Phasellus ornare lacinia vestibulum. Maecenas diam orci, molestie rhoncus fringilla vitae, aliquam eu purus. Maecenas vehicula felis dui, vel dapibus odio lobortis quis. Cras a velit non nisl efficitur tempor. + +Suspendisse magna enim, ultrices et elementum ut, venenatis ut purus. Curabitur convallis vel tortor id rhoncus. Pellentesque varius arcu non imperdiet eleifend. Vestibulum fringilla aliquet vehicula. Nullam et lorem ullamcorper, tincidunt ante eget, egestas ex. Donec laoreet, justo id tincidunt egestas, lectus diam faucibus tortor, nec venenatis felis leo a urna. In at tortor semper augue finibus ornare et vitae erat. Praesent vulputate, dui sed pulvinar porta, justo tortor feugiat tellus, eget accumsan velit turpis at orci. Nulla vitae velit facilisis augue sagittis fringilla nec sagittis nunc. + +Duis vel elementum odio, id eleifend mauris. Nam in ultrices nulla. Quisque pharetra blandit risus eget lacinia. Nulla mattis mi felis, a ultrices nisi egestas vulputate. Donec a augue ac ex laoreet semper at porta ante. Vestibulum arcu ipsum, vehicula vel mollis lobortis, ullamcorper sed augue. Ut viverra faucibus nunc, sit amet sodales diam gravida sollicitudin. Fusce finibus quam sed ornare consequat. Suspendisse viverra ultricies dui in volutpat. Maecenas blandit erat in rhoncus placerat. Phasellus eu nisi rutrum, bibendum nunc eu, viverra ex. Praesent sollicitudin mi sit amet dictum convallis. Vivamus purus ligula, interdum at tincidunt sit amet, pellentesque ut elit. Aenean vitae ultrices ex. In facilisis lectus est, nec vulputate diam tristique vel. In gravida tincidunt ex et viverra. + +Pellentesque mi justo, dignissim a nisl quis, tempor dictum lacus. Nam tristique varius dolor tincidunt pharetra. Nullam iaculis est sed quam dignissim cursus. Duis sit amet vehicula nisi, sed consectetur velit. Nullam non tellus nec dolor venenatis porta quis at quam. In pharetra id orci sed faucibus. Cras et malesuada erat. + +Quisque fringilla commodo metus nec feugiat. Donec et est urna. Maecenas ut magna dui. Vivamus eu sem venenatis, porta mi at, efficitur tortor. In lacinia hendrerit elit, in faucibus nulla ultrices et. Mauris accumsan nec orci et vestibulum. Aliquam eget justo velit. Mauris fringilla ullamcorper enim, in elementum enim eleifend a. Nulla id libero ac arcu tristique ultrices. Maecenas mattis orci vitae nisl laoreet auctor. + +Quisque id aliquam orci. Nunc molestie vitae quam eu consectetur. Vivamus molestie venenatis est, nec lacinia odio faucibus eget. Etiam ornare eu leo eget posuere. Quisque elementum ligula at euismod placerat. Maecenas lobortis leo diam, vel egestas odio iaculis ac. Integer dapibus mi metus. Sed at eleifend ligula, ullamcorper vestibulum eros. Suspendisse dignissim metus in vulputate iaculis. Nam rhoncus felis sit amet velit scelerisque semper. Ut sed augue eget nulla laoreet scelerisque. + +Aenean convallis ipsum id turpis gravida, et elementum ante facilisis. Donec vitae arcu id mauris mollis hendrerit. Mauris imperdiet cursus nibh at laoreet. Sed sit amet enim magna. Mauris blandit nisi quis arcu sodales, eget consequat orci euismod. Curabitur id bibendum diam. Ut pharetra tellus nec enim tristique, id efficitur eros accumsan. Suspendisse potenti. Praesent vel porttitor risus. Nulla vitae ex nec ex hendrerit euismod non mattis arcu. Aenean eget velit massa. Pellentesque venenatis arcu mauris, sit amet scelerisque sem lobortis ornare. Vivamus auctor porta eros, eget blandit lorem semper non. Nunc sed nibh commodo, hendrerit nunc at, malesuada nisl. Aliquam pretium leo sed iaculis vehicula. Vivamus interdum porta augue. + +Suspendisse potenti. Aenean sodales vehicula erat, vel sollicitudin eros eleifend id. Suspendisse hendrerit malesuada est, imperdiet tristique ex aliquet ac. Vivamus ultricies placerat lorem, ac placerat lectus sollicitudin ac. Etiam consequat odio vel bibendum pretium. Integer elementum nisl magna. Nam et vehicula risus, sed mollis tortor. Ut in molestie turpis. Nam luctus, elit molestie varius luctus, lacus ligula ullamcorper elit, non interdum ipsum neque non nibh. Curabitur vulputate facilisis suscipit. Sed vitae augue eu ex luctus lacinia ac vitae quam. Quisque non nibh ex. Praesent gravida efficitur dui, a vulputate nulla ultrices vitae. Duis libero dolor, facilisis in tempus vitae, pharetra non libero. Sed urna leo, placerat quis neque at, interdum placerat odio. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Vivamus in hendrerit elit, quis egestas sem. Sed tempus dolor finibus massa ultrices, sed tristique quam semper. Pellentesque euismod molestie facilisis. Vivamus sit amet ultrices elit. Ut eleifend, libero eu molestie maximus, ipsum elit pulvinar tortor, et aliquet nulla orci eu est. Nullam pretium, ligula at faucibus gravida, velit mauris pulvinar felis, sit amet tincidunt magna dui ut quam. Phasellus porttitor eu nunc id maximus. Suspendisse volutpat suscipit porta. + +Integer dictum augue purus, sed cursus eros blandit id. Vestibulum non nibh viverra, molestie lectus eu, vestibulum justo. Nullam a libero non nibh dignissim posuere id vel orci. Nulla viverra lorem eget condimentum scelerisque. Donec porta nunc sed sapien pretium dapibus. In hac habitasse platea dictumst. Suspendisse sit amet ligula elementum, accumsan ipsum vel, imperdiet massa. Fusce scelerisque non erat ac bibendum. Pellentesque consequat vehicula euismod. Morbi sapien nisl, ultrices ut scelerisque consectetur, ornare et orci. Maecenas efficitur eros a venenatis rutrum. Aenean bibendum dui in enim luctus posuere. Donec placerat porta eros eu dignissim. Fusce nulla velit, sodales eget nibh gravida, tincidunt venenatis urna. Aenean condimentum, massa in fringilla lobortis, turpis felis lacinia metus, sit amet facilisis nisl quam aliquet diam. Praesent fringilla vitae arcu nec lobortis. + +In tincidunt nisi ac dictum cursus. Sed dolor purus, posuere vel dolor vel, semper tempor elit. Integer nec scelerisque tellus, sit amet dictum magna. Donec hendrerit aliquet libero, a varius velit dapibus quis. Donec lectus turpis, egestas vel vestibulum vitae, iaculis quis eros. Maecenas id convallis metus. Duis quis tellus iaculis, lobortis massa vitae, condimentum eros. Pellentesque scelerisque nisl id hendrerit tempor. Donec quam augue, maximus eu porta ut, venenatis a ante. Curabitur dictum et nibh sed auctor. Nam et consequat odio. In vitae magna a dui porta pellentesque quis id magna. Sed eu nunc bibendum, imperdiet nunc sit amet, cursus diam. Vivamus in odio vel nibh sollicitudin molestie. Morbi sit amet leo et odio congue pharetra. Aliquam quis suscipit orci. + +Donec at ornare orci. Suspendisse nec tellus elit. Nam erat urna, laoreet at elit sed, tristique interdum ligula. Nullam dapibus orci sed lorem convallis fringilla. Cras urna ipsum, tristique maximus nisl quis, auctor mattis quam. Donec finibus consectetur lectus et interdum. Aenean posuere lectus vel turpis auctor finibus. Praesent molestie lectus ipsum, et tristique quam porttitor ac. Suspendisse aliquam pretium tortor, id egestas erat. Nulla mollis, orci at semper vulputate, nisl est pharetra diam, sit amet vulputate diam augue a sem. Donec in mauris felis. + +Nunc eleifend at elit a volutpat. Integer semper quis quam at faucibus. Donec pretium purus vitae erat pretium posuere. Sed ac aliquet nulla. Nam ut sagittis mauris. Sed quis sagittis risus, id pretium urna. Nam sagittis elementum ornare. Aenean ligula sapien, congue eget mi quis, viverra commodo nibh. Suspendisse non iaculis magna, nec volutpat neque. Donec eu dapibus eros. + +Nam sit amet iaculis massa. Nullam vel laoreet tellus, id cursus tortor. In hac habitasse platea dictumst. Curabitur in urna risus. Proin dui sem, interdum accumsan cursus ut, dignissim in purus. Nunc vitae consequat arcu. Proin volutpat elementum quam in posuere. Pellentesque luctus arcu in ornare tempor. Cras est turpis, viverra vel consectetur ac, dictum a quam. Donec sagittis tortor sed volutpat accumsan. Curabitur a tellus vitae arcu pretium viverra vel in ligula. Pellentesque turpis augue, porta vitae quam id, fermentum congue leo. Sed nec fermentum turpis. Nulla vehicula vulputate sem eu consequat. In tincidunt nisi et mi maximus, non facilisis justo porttitor. Proin eu sapien aliquam, accumsan nunc non, pulvinar leo. + +Nam vitae commodo sem. Ut laoreet in quam in suscipit. Nullam at faucibus diam. Fusce ut purus at ex lobortis elementum vitae quis sapien. Nam tempus consectetur ex, sed luctus felis. Donec porttitor mi dui, non luctus quam consectetur eu. Ut a egestas diam. Etiam sodales, nisl vitae gravida pulvinar, libero est condimentum tellus, vitae ullamcorper tortor justo a urna. Maecenas ac velit accumsan, laoreet leo eget, elementum libero. Maecenas dictum tincidunt blandit. Proin sed bibendum urna. Phasellus fermentum tincidunt tempor. Mauris iaculis, mi ac fermentum interdum, nibh odio pellentesque nunc, vel scelerisque sapien sem id purus. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Maecenas imperdiet sit amet lectus at hendrerit. Vivamus luctus, elit non lacinia hendrerit, risus velit finibus nulla, quis sagittis ipsum diam ut odio. Sed mauris sapien, vehicula efficitur gravida sed, gravida in lectus. Fusce eget consectetur turpis, in fermentum neque. Nullam turpis turpis, feugiat a accumsan in, euismod a augue. Vestibulum nec neque libero. Phasellus eget efficitur turpis, fermentum molestie nunc. Sed consequat elit urna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. + +Nunc egestas consequat blandit. Donec quis porta sapien. Sed augue nunc, efficitur vitae tellus sed, finibus bibendum sem. Fusce id sem quis quam pharetra ultrices. Phasellus non convallis velit. Quisque erat tellus, pretium eget porta in, ornare a arcu. Aenean nec lectus lorem. Suspendisse dolor lectus, ullamcorper ornare lorem a, consequat lobortis elit. Nunc dignissim est nec gravida facilisis. Proin faucibus erat vel eros volutpat, in vulputate neque sodales. + +Nunc vitae imperdiet ipsum, faucibus vehicula magna. Nulla nec nisl sapien. Curabitur et dui eget tortor efficitur accumsan. Aenean in pellentesque erat. Nam condimentum neque pulvinar, aliquam nisl eu, mollis lorem. Integer rutrum arcu eget felis semper euismod. Quisque vestibulum vel diam a tempor. Aenean mollis, tellus sit amet pretium pulvinar, nulla dolor ornare ligula, eget dignissim orci tortor ut sapien. Nam ut ipsum id lectus venenatis tempus vel non ex. Suspendisse blandit vitae nunc vitae porta. Suspendisse tincidunt est sit amet ultricies consectetur. Nulla fermentum hendrerit ex, vehicula rhoncus arcu lobortis ut. Vestibulum fermentum ornare diam at pellentesque. Praesent nunc lorem, porta et magna nec, sodales commodo justo. Duis aliquam sapien et rutrum tempus. Vestibulum malesuada felis eu ligula posuere luctus. + +Maecenas lacinia, lectus eget rhoncus aliquam, tortor est gravida sapien, vel aliquam arcu erat et magna. Praesent fringilla leo eget neque posuere imperdiet. In porttitor elit non enim gravida euismod. Aliquam tempus, orci at interdum dapibus, mauris lacus egestas sapien, ac ullamcorper ex nibh sed orci. Quisque iaculis enim et lectus egestas, malesuada posuere lectus interdum. Sed dignissim neque vel turpis dictum ornare. Vestibulum suscipit consequat maximus. + +Ut et ante sit amet leo rutrum volutpat. Sed malesuada quis sapien et ornare. Aliquam ac ex enim. Curabitur vel quam et orci posuere feugiat. Pellentesque nec metus eget sapien eleifend tincidunt non sit amet arcu. Cras posuere metus eget risus varius fermentum. Nullam orci eros, efficitur nec sapien nec, pretium laoreet erat. Nam gravida purus mauris, ac viverra orci hendrerit sed. Aenean ligula massa, posuere id faucibus vitae, malesuada quis augue. Morbi consectetur mattis mi, quis sodales diam bibendum eget. Aliquam sagittis neque at feugiat posuere. Donec gravida lectus a lectus fringilla tincidunt. Vivamus volutpat dui et turpis condimentum, ut tincidunt tortor lacinia. Ut laoreet, ante in pellentesque sodales, elit mauris scelerisque dui, quis tincidunt quam massa ac enim. Nulla porta porta sapien vel sollicitudin. + +Phasellus sed lectus posuere, mollis ante non, feugiat odio. Aenean a quam id mi ornare dapibus. Donec venenatis ipsum non velit accumsan, id elementum dolor imperdiet. Phasellus lacinia erat diam, sit amet convallis lectus ornare in. Curabitur lorem ligula, maximus eu varius quis, auctor quis odio. Etiam in orci porttitor, sodales ipsum at, rhoncus turpis. Nulla eget luctus nisi. Duis convallis est eget ligula fringilla viverra. Duis nec sapien quis dui luctus ornare sit amet quis erat. Quisque nec justo dui. + +Sed eleifend, tellus quis laoreet rhoncus, leo turpis imperdiet turpis, pretium varius odio tortor et leo. Morbi ut mi fringilla, porttitor sem ac, accumsan ante. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum cursus dolor accumsan dolor aliquet dignissim. Duis sed feugiat erat. Donec sed arcu accumsan, ornare nisl eget, lobortis nibh. Praesent pulvinar quis justo et hendrerit. + +Sed velit nibh, efficitur ut leo sed, eleifend iaculis nisl. Mauris ac diam euismod, luctus enim maximus, tincidunt sem. Ut tempus magna vitae blandit bibendum. Sed sapien tortor, ultrices et justo vel, pharetra faucibus dui. Vivamus et vestibulum arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Mauris tincidunt suscipit risus, vitae varius felis commodo in. Nullam semper tortor dolor, sit amet pharetra odio interdum hendrerit. Pellentesque sed justo eros. Cras quam purus, eleifend id tellus molestie, eleifend finibus lorem. Donec a volutpat libero, at vehicula tellus. Vivamus tellus orci, pharetra in nisi vitae, tincidunt dapibus nunc. + +Suspendisse ultricies sem laoreet quam molestie ullamcorper. Sed auctor sodales metus, eget convallis eros ultrices quis. Duis rutrum mi a tortor iaculis posuere. Suspendisse potenti. Pellentesque dictum faucibus dui a vestibulum. Donec elit nisl, aliquet at dolor non, viverra dignissim felis. Donec mi elit, condimentum sit amet magna id, efficitur sodales arcu. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque est elit, porttitor eget bibendum nec, auctor nec nulla. Pellentesque dignissim nisl vel orci commodo, ut ullamcorper justo viverra. + +Suspendisse pharetra gravida turpis sit amet efficitur. Nulla mattis tortor eget pharetra ultrices. Ut a turpis maximus, pharetra justo non, tempor quam. Nam et volutpat lectus. Vestibulum congue turpis augue, quis eleifend nulla euismod in. Vestibulum sed erat tempus, porttitor diam vel, elementum metus. Aenean facilisis molestie ante, sit amet ornare lacus mollis sed. Maecenas rutrum urna nec ex malesuada consequat. Nunc interdum pellentesque nisl sed viverra. Nam nec fermentum ipsum. Nulla facilisi. Maecenas congue dolor erat, a fermentum enim congue vitae. Integer metus libero, feugiat at eleifend eu, iaculis nec leo. Praesent eleifend convallis leo, eu posuere urna elementum eu. + +Aliquam dapibus dolor vel urna luctus venenatis. Suspendisse potenti. Donec vitae tellus nisi. Pellentesque vel neque dignissim, ornare tellus sed, sodales metus. Aliquam vitae maximus tortor. Nullam mattis, odio id porttitor euismod, est leo aliquet massa, bibendum pulvinar justo orci a magna. Morbi ut nisl congue, porttitor erat non, gravida turpis. In nulla risus, ullamcorper quis suscipit vel, tristique ut nulla. In malesuada odio augue, ac hendrerit nunc mattis a. Nam in quam ut elit ullamcorper mattis. Sed fringilla tempus felis, id pretium tortor varius non. Aenean quis sem quis risus mattis posuere vel quis nibh. + +Sed pulvinar commodo dui sit amet malesuada. Quisque porta tellus placerat congue efficitur. Cras id blandit enim. Praesent a urna felis. Aliquam ornare, nisl eget iaculis tempor, ligula mi vehicula dolor, ut eleifend massa massa vel est. Fusce venenatis laoreet lobortis. Proin tempus aliquet nunc ut porttitor. In est mauris, blandit eu mattis vitae, aliquam a orci. Cras vitae nulla nec ex cursus blandit. Aliquam fermentum, erat in aliquet dictum, metus urna fermentum quam, non varius magna lectus non urna. + +Donec faucibus nisl nunc. Integer rutrum dui enim, malesuada semper turpis pulvinar eget. Fusce consectetur ipsum tellus, sed maximus lorem auctor in. Morbi nec est in quam ultricies hendrerit. Sed aliquam sollicitudin elementum. Aenean aliquam ex sit amet dignissim venenatis. Duis malesuada leo nisi, id accumsan turpis pretium id. Sed ornare magna non pharetra pharetra. Ut auctor dolor neque, nec bibendum odio viverra in. Nulla convallis interdum condimentum. Proin vestibulum turpis in lorem ultrices, bibendum tristique ligula faucibus. In tincidunt auctor sem, vitae fringilla neque pulvinar eu. Etiam commodo pellentesque enim. Phasellus feugiat non sapien eget sollicitudin. Etiam consequat efficitur lacus vel maximus. Suspendisse vitae elementum diam. + +Mauris urna velit, efficitur at viverra at, interdum a velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean bibendum sagittis massa in interdum. Mauris facilisis dui eget ipsum euismod ultrices. Donec sit amet lorem iaculis, condimentum velit quis, lacinia justo. Integer faucibus metus sed eros semper, in congue tortor venenatis. Proin tincidunt maximus enim, accumsan facilisis tellus gravida eu. Phasellus interdum aliquam ante, sit amet rhoncus nisl ornare at. Suspendisse libero eros, cursus quis fermentum nec, tempor eget lectus. Aenean ullamcorper augue lacus, non iaculis dui rutrum id. Aliquam erat volutpat. + +Morbi hendrerit fermentum sodales. Proin rutrum congue auctor. Mauris pellentesque elit non velit condimentum tincidunt a sit amet velit. Etiam accumsan ante id neque commodo vulputate. Aenean nec mattis neque. Aliquam tempor urna quis nisl convallis congue. Praesent vitae porttitor ante. Mauris mattis vestibulum ante, nec auctor augue. Praesent sem leo, accumsan eu fermentum egestas, iaculis ac nulla. + +Etiam vel nisi congue, varius neque id, volutpat quam. Fusce placerat arcu hendrerit orci condimentum vehicula. Integer sem ex, facilisis et lacinia a, condimentum at ante. Morbi condimentum diam tellus, non lobortis arcu pellentesque sit amet. Phasellus imperdiet augue eget sollicitudin mollis. Vivamus sollicitudin arcu aliquam lectus tristique, in consequat diam egestas. Suspendisse aliquet non velit id feugiat. Sed eu est fringilla, gravida nulla ac, dignissim tortor. Phasellus pellentesque nisl non venenatis sagittis. Etiam facilisis nulla quis lorem scelerisque vehicula id at ex. Duis in risus enim. In eu vulputate massa, vel dignissim odio. Praesent ut mi tellus. In hac habitasse platea dictumst. + +Nunc malesuada turpis in fermentum lobortis. Donec blandit eu orci non laoreet. Suspendisse posuere blandit tortor, in accumsan velit cursus in. Integer suscipit justo nulla, in viverra orci suscipit et. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum a pulvinar lacus. Vestibulum mattis nisi vel nunc convallis maximus. Fusce aliquam, erat in volutpat gravida, elit odio feugiat ante, nec varius enim leo eget nisl. Donec sit amet ligula lobortis, sollicitudin dolor quis, blandit augue. Duis at interdum sem. Nullam eleifend ligula urna, vitae sollicitudin purus cursus nec. + +In commodo risus eu justo hendrerit, ut posuere mi eleifend. Suspendisse sollicitudin odio sem. Vestibulum at dapibus dui, vel dictum nisi. In hac habitasse platea dictumst. Sed ac pharetra ex. Ut ultrices augue ut vulputate condimentum. Phasellus convallis arcu tortor, ac tincidunt justo cursus vitae. Mauris dignissim dapibus imperdiet. Nullam id quam eget mauris cursus molestie finibus eu enim. Fusce laoreet orci eu nunc fermentum tincidunt. Pellentesque vitae ex ac nunc porta mattis sed ut ligula. Phasellus erat dui, consequat et lacinia vel, blandit nec dui. Donec ipsum magna, rhoncus ac viverra vitae, feugiat vel ligula. + +Cras ut nisl sed elit dictum semper a at magna. Aliquam laoreet viverra velit vel lobortis. Nam tempor lorem sit amet purus tincidunt accumsan. Vestibulum et vestibulum ligula. Donec sit amet neque faucibus leo rutrum semper. Maecenas scelerisque, lacus et lobortis congue, purus quam euismod risus, et mattis orci nisl eget est. In hac habitasse platea dictumst. Pellentesque eros velit, sollicitudin quis ante vel, blandit maximus mi. Suspendisse at eros id quam vulputate ornare. Duis placerat tellus vel odio ultrices, ac feugiat enim placerat. Vestibulum ut massa mattis, commodo orci euismod, cursus lacus. Suspendisse potenti. Sed venenatis cursus neque, at lacinia erat ornare et. Sed rutrum, dui at porttitor hendrerit, lacus magna fringilla quam, id mollis elit leo ut ante. Praesent vel diam sed urna mollis laoreet eget ut risus. + +Mauris varius odio lectus, sit amet consequat nulla sollicitudin sed. Suspendisse commodo rhoncus enim vitae pellentesque. Aliquam vulputate sollicitudin aliquet. Mauris interdum interdum orci, non varius sem ornare ut. Sed vel nibh nunc. Duis tincidunt quam quis lectus egestas, eu ultricies ante posuere. Aenean in mauris eros. Suspendisse potenti. Vivamus sit amet augue at velit accumsan fermentum. Phasellus vel dui sit amet felis convallis sodales. + +Nunc mattis vitae sapien ut dignissim. Nam fermentum sit amet massa eu accumsan. In ut ipsum sit amet ante pellentesque accumsan. Nulla egestas eros eget lacus rhoncus pharetra. Nam pellentesque laoreet ex in lobortis. Vivamus congue tincidunt molestie. Integer vel turpis augue. Cras vel molestie quam. Etiam vel mauris ut tellus finibus consequat. + +Curabitur velit erat, vestibulum blandit massa a, aliquam elementum tellus. Pellentesque id nisl tempor lorem condimentum dapibus. Quisque ut lorem at orci elementum dapibus quis ut enim. Praesent venenatis congue arcu eu sagittis. Nunc nunc massa, posuere ac gravida id, scelerisque nec velit. Suspendisse non lacus a orci dapibus congue. Sed leo velit, facilisis sed egestas ut, vehicula at turpis. Praesent a finibus felis. Quisque est mi, pellentesque sit amet varius eu, gravida id est. + +Donec auctor gravida urna ac suscipit. Etiam placerat ipsum vitae ante congue, at fringilla lectus ullamcorper. Donec viverra lacus ut erat posuere, dignissim porta purus tempor. Duis eget enim quis diam vehicula pulvinar. Donec tempus eros velit, sit amet pharetra ligula placerat in. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas sit amet pretium tellus, non dictum ligula. Nullam a ex purus. Vestibulum id ex rhoncus, pretium eros vel, consequat tellus. Vivamus lacinia dolor nec ipsum aliquam, euismod fermentum sem efficitur. Nulla facilisi. Pellentesque pharetra lacinia augue, et eleifend lorem aliquet eu. Ut ut porta ante. Duis ut auctor nisi, id porttitor erat. Proin sollicitudin sem quis justo sodales aliquam. Nulla pharetra gravida arcu vel tempus. + +Maecenas nec imperdiet nulla, vitae fringilla diam. Mauris maximus aliquet tellus at congue. Aliquam in dictum elit. Sed pretium mattis lectus, non pellentesque enim dignissim vel. Sed quis elementum diam, a porttitor enim. Cras cursus eros nisl. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ornare sapien vel diam vestibulum, at commodo metus lobortis. Sed euismod iaculis scelerisque. Pellentesque venenatis malesuada dolor, at tempus eros venenatis sit amet. Fusce a massa non libero blandit suscipit. + +Nulla facilisi. Sed nec leo nisl. Proin condimentum nunc at risus semper, in laoreet dui congue. Integer in dolor vitae ex maximus porttitor. In efficitur vulputate metus, ac egestas diam pharetra ac. Sed tristique tempus ligula dignissim convallis. Ut tincidunt laoreet fringilla. Praesent nunc est, lacinia tristique odio in, varius rhoncus ipsum. Vivamus bibendum justo at metus ullamcorper imperdiet. Quisque elit nibh, rhoncus a placerat eget, vehicula quis ante. + +Morbi fermentum turpis enim, eu interdum dui interdum sit amet. Sed vulputate lacus nec ligula gravida euismod. Duis in nisl fringilla, sollicitudin diam ac, laoreet tortor. Sed eget porttitor augue. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed eu enim convallis augue vulputate convallis. Praesent laoreet, leo at ornare luctus, nisi felis semper sapien, sit amet sodales augue mauris eu lorem. Fusce ornare volutpat malesuada. Curabitur vehicula, eros id fringilla placerat, tellus elit venenatis lorem, ac imperdiet purus ex blandit felis. Nunc suscipit elementum risus ac dictum. Aliquam bibendum dignissim ipsum, sit amet posuere sem viverra ut. Vestibulum egestas in ex ac volutpat. + +Donec ut faucibus velit, nec suscipit metus. Nullam dui ligula, commodo eget est venenatis, ullamcorper porttitor lectus. Suspendisse dictum, metus vitae commodo ultricies, enim quam pretium enim, a efficitur tortor purus sed ipsum. Nam sit amet lacus vel elit consectetur ultrices. Vestibulum ac nibh in metus porttitor vestibulum. In blandit et est non efficitur. Aenean vestibulum tortor sit amet mattis semper. Praesent sit amet turpis ac neque malesuada tincidunt nec nec urna. Mauris posuere elit nisl, ac ullamcorper nisi pulvinar in. Nulla ac elit in neque ullamcorper facilisis sed et dui. + +Quisque molestie euismod dui, non scelerisque arcu dictum a. Donec eu nulla nisl. Aliquam neque orci, ultrices in nunc vitae, sollicitudin eleifend augue. Etiam at mattis velit, a efficitur dui. Nam bibendum enim non elit tristique aliquet. Vestibulum eget nibh lacus. Pellentesque id sapien dui. Nullam urna leo, faucibus et efficitur vel, ullamcorper iaculis mi. Donec sit amet bibendum justo, id dapibus sem. Pellentesque dignissim varius tellus nec egestas. Praesent eu orci vel ipsum pharetra maximus. Cras nisl ligula, sollicitudin in ligula vel, posuere sagittis eros. Phasellus porta tristique mauris vel eleifend. Mauris sit amet volutpat ipsum, sed vestibulum orci. + +Ut rutrum augue quis rhoncus tincidunt. Aenean sollicitudin lacus at erat varius ullamcorper. Integer vitae orci vel nisi vulputate cursus eget nec ex. Mauris elementum, augue ac accumsan convallis, libero leo porta ante, sit amet elementum felis ligula non enim. Ut venenatis semper posuere. Vestibulum nec nisl nisi. Ut a sapien ac orci finibus dictum sed at ex. Phasellus ac lorem nisl. Quisque vitae tempor lacus. Vestibulum in mauris diam. Proin mattis ligula vitae ipsum bibendum, at dapibus nunc placerat. Nam iaculis justo in accumsan tristique. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris pretium velit et tristique pellentesque. Nunc in sapien a purus congue rutrum. Nam placerat risus in ante rutrum rutrum. In in ligula magna. Duis orci ante, vehicula elementum consectetur vitae, lobortis vitae arcu. Ut feugiat tempus metus quis sollicitudin. Etiam cursus venenatis augue at fermentum. + +Vestibulum id vehicula massa. Proin ut ligula et sapien placerat tristique non nec nibh. Etiam non lacus placerat, molestie ex eu, venenatis elit. Sed posuere, ante et ullamcorper luctus, elit lectus blandit tortor, nec consequat massa elit a tortor. Fusce urna felis, porttitor at ultrices vel, eleifend porta ipsum. Pellentesque nisl nibh, molestie sit amet sem eu, dapibus pharetra nulla. Phasellus viverra augue eu augue volutpat dignissim. Integer bibendum faucibus varius. Curabitur non turpis purus. + +Sed pretium eros nisl, sed ullamcorper magna faucibus quis. Etiam ornare euismod tellus, vitae accumsan eros bibendum ut. Morbi a ex sed risus faucibus rutrum et ut urna. Nullam non tortor commodo, facilisis massa non, tristique metus. Curabitur placerat, arcu in egestas ullamcorper, nisl nisl luctus felis, ac dapibus erat velit sed erat. Proin sagittis felis vitae sem posuere semper. Vivamus fermentum eu nisi ac facilisis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque pellentesque lacinia ex tempus lacinia. Aliquam erat volutpat. Nam aliquet mattis risus non pretium. Suspendisse potenti. + +Suspendisse et sapien ornare, elementum erat vel, ultrices nisi. Curabitur interdum dignissim tincidunt. Cras eget est a velit consectetur finibus et sed nisl. Curabitur vestibulum semper posuere. Aliquam porta diam commodo nulla tempus imperdiet. Sed id dictum neque. Ut sit amet risus aliquet, dignissim velit sed, tristique ipsum. Nam a sapien id magna commodo tincidunt quis ac tellus. Nunc nec pellentesque orci. In justo purus, vulputate quis urna a, fringilla blandit sem. Cras porttitor quam vitae metus vehicula faucibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut eleifend orci lectus, vitae semper eros hendrerit id. Fusce nec tellus condimentum, vehicula elit quis, gravida erat. Maecenas volutpat interdum velit id vestibulum. + +Pellentesque id metus metus. Nullam ac metus non nisi facilisis aliquet quis a sem. Morbi cursus consectetur aliquam. Morbi id sapien nibh. Etiam tempus tempor tempor. Nam scelerisque condimentum purus. Proin nec cursus eros, in condimentum dolor. Nam in sagittis urna. Ut eget ornare erat. Vivamus nec elit ut sapien tristique aliquet a nec urna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Maecenas varius pellentesque diam. + +Ut eget libero iaculis urna porta iaculis vel ac eros. Sed sed mi et libero porta consequat a in libero. Sed in imperdiet ipsum, sit amet ultricies dui. Curabitur sit amet consectetur eros. Praesent nunc tellus, feugiat ac laoreet consectetur, accumsan nec magna. Praesent at elementum orci. Donec dapibus venenatis libero, id tincidunt libero euismod ac. + +Aenean sit amet ex placerat, molestie sapien a, volutpat turpis. Vivamus elementum lacinia nisi a convallis. Donec a sagittis turpis. Curabitur pulvinar laoreet dolor id consequat. Aliquam aliquam feugiat magna, vitae sagittis lorem mattis ut. Donec sed auctor nulla. Ut convallis, neque vitae faucibus efficitur, nisi justo pharetra odio, vitae tristique purus lorem et nisi. Quisque eleifend aliquam arcu nec maximus. Nunc mauris elit, finibus nec gravida non, maximus nec nibh. Sed eu ipsum in arcu gravida maximus. Maecenas massa dolor, ullamcorper eget odio eu, congue ornare leo. Suspendisse venenatis ultricies imperdiet. Nam finibus orci vitae sagittis finibus. Pellentesque sit amet dapibus diam. Nam eu nunc elit. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam euismod turpis nec urna lobortis, ut malesuada ex suscipit. Fusce sed viverra risus. Pellentesque tristique nulla pulvinar tincidunt accumsan. Nullam vitae nibh imperdiet erat rutrum pellentesque et sit amet quam. Aenean laoreet ultricies hendrerit. Duis elementum tellus a feugiat vulputate. Aliquam aliquam enim placerat dolor viverra, non suscipit lorem ultrices. + +Proin interdum vitae lorem quis viverra. Praesent nec tempor dolor, vitae sagittis turpis. Etiam sit amet imperdiet sapien, ac laoreet arcu. Proin aliquam, risus nec maximus dapibus, dui diam elementum dolor, vitae tempor augue lorem vel justo. Aenean ac egestas dolor. Ut aliquet, felis at fringilla venenatis, leo nisl ullamcorper ex, vitae pellentesque turpis orci quis lectus. Nullam vitae suscipit metus. Integer congue, ante in lacinia rhoncus, erat lorem interdum elit, egestas suscipit lectus nunc non orci. Nunc vel viverra quam. Mauris sit amet sodales tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam quis sollicitudin libero. In nec venenatis orci. + +Integer non quam fringilla, tristique nulla id, gravida arcu. Aenean scelerisque lacinia magna. Praesent nunc sem, lobortis non convallis rhoncus, rutrum vitae ante. Sed et orci ut erat viverra bibendum a et lectus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce sit amet eros eget dolor pharetra vestibulum. Nullam vestibulum, massa dictum finibus egestas, orci nulla pulvinar sem, nec tempor libero lacus eu ante. Nam lacus erat, gravida eu placerat sit amet, facilisis sed urna. Suspendisse efficitur felis non ornare dictum. Nam sit amet nulla enim. Nulla eget scelerisque est. Suspendisse lacinia velit arcu, id lobortis felis facilisis in. + +Suspendisse potenti. Nulla porttitor metus ut nunc dictum tristique. Cras sit amet tortor eget ligula tristique efficitur. Ut at nisi id purus imperdiet laoreet. Sed sit amet malesuada urna. In nunc dolor, luctus ac condimentum ut, dapibus vel metus. Suspendisse pretium urna eget libero convallis vestibulum. Integer ut mauris hendrerit ex posuere euismod sed sed odio. Nulla egestas libero sit amet magna venenatis faucibus. Pellentesque semper vestibulum elit, et pretium felis scelerisque non. Suspendisse aliquet leo arcu, sed dapibus ex semper non. Donec lacinia dictum dignissim. Maecenas ipsum nunc, aliquet eget consectetur sit amet, aliquet vitae odio. + +Proin consectetur blandit feugiat. Nulla ac dictum quam. Vivamus suscipit scelerisque ipsum, vitae consequat neque sagittis id. Donec eu augue hendrerit, varius ipsum at, cursus massa. Morbi id augue id ipsum porttitor mollis. Phasellus nec libero eu arcu finibus dapibus. Nullam nec pharetra ante. Aenean sit amet urna eget justo tempus pharetra ut nec mi. Pellentesque viverra, ligula nec elementum ornare, ante elit eleifend enim, nec dignissim ligula elit in nibh. Vestibulum facilisis, felis eu condimentum dapibus, ante risus tincidunt urna, ut posuere dui augue ut ante. + +Nam arcu lacus, accumsan ac dolor in, egestas euismod est. Sed consectetur mauris et enim tincidunt semper. Donec sit amet pellentesque diam. Fusce viverra arcu a placerat fermentum. Nullam euismod dui eget egestas ultrices. Duis euismod viverra tortor eget eleifend. Pellentesque vitae neque dapibus, bibendum mi eu, auctor velit. + +Pellentesque congue consectetur turpis, eget auctor dui suscipit vel. Aliquam a sollicitudin turpis. Praesent nec blandit tortor. Suspendisse non nunc at urna tincidunt elementum. Integer eu elit nec urna maximus blandit quis at tortor. Nulla laoreet elit a purus tempus, et tincidunt lorem sagittis. Aenean semper erat eu neque hendrerit ornare. Cras posuere lorem nec orci vulputate finibus. Fusce tempor ex ac lacus gravida venenatis. + +Phasellus laoreet, libero vel fermentum euismod, sem diam accumsan quam, vitae gravida arcu est at sem. In bibendum, felis at viverra congue, felis nunc fringilla libero, ut scelerisque erat massa ut neque. Suspendisse potenti. Cras faucibus dolor vel tortor blandit, quis imperdiet magna semper. Nullam ut urna dapibus, vulputate est a, hendrerit purus. Vivamus vestibulum nisi a tempus placerat. Morbi vitae ultricies lacus. + +Nunc vel efficitur urna. Fusce bibendum suscipit mauris, quis imperdiet nisl bibendum non. Nam sed nisi imperdiet, pellentesque sem sed, pellentesque diam. Praesent luctus feugiat odio, eget fermentum lectus ullamcorper non. Phasellus pulvinar lectus sed ligula semper lobortis. Pellentesque fermentum ultricies fermentum. Quisque id turpis vel orci rutrum rhoncus id vel metus. Vivamus ex leo, accumsan eu luctus sit amet, tincidunt vitae erat. Sed eu mollis odio, ut ultricies lacus. Duis enim odio, pellentesque non velit vel, aliquam blandit erat. Mauris feugiat felis fermentum purus blandit, id rhoncus lorem tempus. Integer cursus, nunc vitae laoreet commodo, nunc lacus venenatis orci, quis eleifend risus est nec quam. Nulla tincidunt nunc ac purus tincidunt, eu molestie ipsum sollicitudin. + +Vestibulum ac varius velit, non suscipit nulla. Vestibulum a sagittis tortor. Suspendisse vestibulum felis quam, eget blandit nulla dictum vel. Praesent eu tellus ut lacus facilisis ultrices. Etiam fringilla nulla nec leo viverra faucibus. Sed nec sollicitudin nisl. Aenean libero massa, ornare sed elit ut, interdum fermentum arcu. + +Aliquam maximus diam et elit dapibus, eget condimentum sapien finibus. Etiam gravida nunc dapibus facilisis feugiat. Sed quis massa ligula. Nunc eleifend dolor lacus, at dapibus nisi vulputate eget. Etiam vel euismod urna, quis molestie nibh. Vestibulum bibendum erat non dictum sollicitudin. Suspendisse sed placerat lacus. Cras sollicitudin quam justo, a condimentum urna feugiat a. Quisque mauris libero, ultricies at ligula a, facilisis euismod ante. + +Morbi hendrerit maximus sapien ut auctor. Nullam nisi erat, aliquam ac metus eu, fermentum laoreet augue. Nam ultricies mi at vulputate laoreet. In ipsum est, blandit sit amet lorem id, egestas aliquam odio. Praesent venenatis lobortis mi. Aenean eu lacus consequat, mattis enim et, pellentesque leo. Duis convallis facilisis placerat. Donec porta, enim eget placerat congue, nisi augue faucibus odio, et fringilla purus elit vitae felis. + +Nulla et tellus a libero pellentesque eleifend vitae mattis nisi. Nunc placerat neque et sapien lobortis, aliquet pharetra nunc vulputate. Suspendisse potenti. Etiam ullamcorper lacinia varius. Suspendisse sit amet malesuada magna. Nam molestie mi nec diam tempus laoreet. Suspendisse urna tellus, scelerisque vitae purus et, dapibus fermentum felis. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec scelerisque eget neque sed hendrerit. Donec at ligula augue. Donec vulputate massa nunc, a feugiat nunc elementum ut. Donec pulvinar libero sit amet ante faucibus sagittis. Mauris quis diam ultrices, tristique sapien nec, tempus urna. Morbi hendrerit odio sit amet leo lacinia, non egestas diam condimentum. Suspendisse efficitur sapien eget elit euismod tristique. Duis posuere vestibulum risus id sodales. Ut eget mi in urna rutrum molestie non nec dolor. Curabitur sollicitudin pharetra lacus, in sodales tortor tempor vitae. Nullam rhoncus arcu vel euismod varius. + +Aenean malesuada, purus quis volutpat sollicitudin, lectus risus lacinia mi, a facilisis ante lacus a dolor. Aenean vel aliquam tortor. Vivamus ornare, tellus at malesuada suscipit, quam dolor egestas erat, id convallis enim augue a enim. Aenean ultricies sodales placerat. Vivamus vel erat ac mi vulputate commodo nec eget urna. Suspendisse potenti. Mauris quis dapibus est, id tristique libero. + +Suspendisse ex diam, imperdiet quis dui id, interdum sodales elit. Maecenas at nunc ligula. Etiam imperdiet convallis magna sit amet tristique. Proin hendrerit laoreet magna, eget malesuada elit rhoncus et. Curabitur eget odio imperdiet, molestie sapien pretium, efficitur turpis. Aliquam sed congue arcu. Pellentesque vitae mauris id neque pulvinar tempor. Donec lobortis tellus id gravida dictum. Nulla et varius ex. Vestibulum pharetra eu quam dapibus finibus. Nullam accumsan sagittis turpis. Mauris fermentum orci et tortor tempus, ac tristique odio sollicitudin. Duis nec elit et magna imperdiet molestie eget vel ante. + +Suspendisse tempus augue nec massa molestie aliquam. Pellentesque vestibulum, erat sit amet fringilla fermentum, sapien lorem tempor felis, at efficitur augue erat quis nisi. Proin nec felis sagittis, sollicitudin turpis eu, fringilla leo. Vestibulum at augue nec dui vestibulum aliquam a at metus. Duis ullamcorper eleifend bibendum. Maecenas viverra mauris lacus, et consequat nisl pharetra eu. Praesent rutrum diam eu mi aliquet, non pellentesque est suscipit. Vestibulum massa leo, blandit eget mi at, cursus faucibus nunc. Praesent nec bibendum libero, vitae cursus libero. Sed consequat vitae eros nec maximus. + +Pellentesque et tortor eget lectus feugiat venenatis. Integer ornare nisl quam, nec imperdiet justo finibus at. Morbi malesuada, ante sit amet rutrum tempor, risus lorem tristique urna, egestas varius purus ex ut sem. Etiam sit amet feugiat sapien. Etiam quis risus commodo, eleifend velit quis, tincidunt enim. Mauris fermentum lacinia velit quis efficitur. Nunc sit amet lacus sit amet urna viverra feugiat. Nullam sagittis rhoncus suscipit. Aliquam eu sem ligula. Sed sodales risus et tortor lacinia tristique. Nulla massa augue, malesuada sit amet euismod ac, euismod placerat nulla. Sed lobortis ex nec lacus facilisis, nec semper mauris ultrices. Quisque ornare non felis vitae pellentesque. Donec mattis ut magna sed imperdiet. Integer viverra tempus feugiat. + +Donec laoreet aliquam imperdiet. Fusce justo neque, dictum vitae fringilla vitae, euismod sed augue. Fusce sodales, lectus a congue bibendum, ante eros pharetra tortor, eu sollicitudin libero ipsum sit amet felis. Curabitur sed condimentum quam, in iaculis ante. Ut feugiat dictum odio, vitae hendrerit lacus volutpat sed. Sed scelerisque et metus nec gravida. Mauris mattis porttitor magna, ut maximus justo sagittis vitae. Donec at metus ut leo gravida vehicula in sed diam. Vestibulum dignissim suscipit sagittis. Nam varius et arcu sit amet lacinia. Sed eget elit rhoncus, elementum dolor a, vehicula orci. Nam vitae tellus sapien. Phasellus massa lorem, commodo quis placerat in, semper faucibus tortor. + +Vivamus id dolor mi. Curabitur sagittis posuere quam in mollis. Phasellus finibus ipsum vel elit tincidunt, a bibendum ligula vulputate. Suspendisse quis purus nisl. Integer mi sem, posuere quis nisi a, consequat consequat magna. Vestibulum bibendum mauris in risus auctor fringilla vel at lacus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus venenatis mauris at mattis eleifend. Donec tempus ipsum ac nisl convallis ultrices. + +Nullam eu nibh ut erat vehicula accumsan. Vivamus vitae faucibus libero. In in luctus odio. Nulla nec enim pharetra, fermentum erat in, tempor turpis. Sed ac magna suscipit, dignissim elit id, faucibus libero. Maecenas placerat in dolor et faucibus. Sed maximus purus dolor, non egestas massa rutrum non. Nunc ut rhoncus lacus. Sed sit amet dolor eu sapien sagittis molestie. Aenean ipsum erat, rutrum a diam et, varius porttitor ligula. Phasellus fermentum fermentum felis. + +Phasellus odio massa, lacinia ac hendrerit vestibulum, placerat sit amet erat. Praesent eget justo scelerisque, congue est in, pharetra mi. Etiam blandit turpis dui, a faucibus ipsum viverra vitae. Praesent sed scelerisque arcu. Cras nec venenatis ipsum. In et tempus metus. Pellentesque cursus quis nibh quis porttitor. Duis leo lacus, pretium eget rutrum eu, tincidunt lobortis tellus. Cras in erat tristique arcu faucibus luctus sit amet tempus erat. Nullam placerat, est a interdum iaculis, purus justo convallis arcu, at mattis erat sem tempor velit. Curabitur sapien sapien, tempor eget sodales vitae, blandit ac libero. Proin eget sem et arcu malesuada convallis non a est. + +Ut id sagittis urna. Morbi est mauris, molestie quis magna ut, convallis porta justo. Etiam in vulputate sem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nullam consectetur enim nisl, sit amet pulvinar turpis elementum at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum convallis cursus libero vel feugiat. + +Curabitur vel scelerisque metus. Etiam aliquet faucibus quam, vel aliquet est mattis quis. Pellentesque pulvinar sodales augue, a dignissim sem tristique vitae. Pellentesque dolor quam, pharetra vitae rhoncus ut, blandit at elit. Sed dignissim diam turpis, ac condimentum justo iaculis vel. Donec facilisis eros sit amet est dapibus, vel pellentesque eros consequat. Integer euismod mollis metus, vel rutrum risus imperdiet ac. Fusce semper dolor sit amet mi congue accumsan. Maecenas sagittis magna justo, vel varius ante scelerisque id. Fusce at urna sapien. Aliquam dui sapien, facilisis eu magna laoreet, feugiat tincidunt elit. Fusce iaculis sit amet erat id dignissim. Suspendisse sollicitudin laoreet consequat. Pellentesque eu mollis quam, vel dapibus libero. Duis mattis tortor vitae orci vehicula, et viverra ipsum lobortis. Quisque iaculis sapien est, id sagittis tortor rutrum at. + +Duis ut condimentum risus, et venenatis nulla. Praesent urna ex, faucibus eu mollis nec, lobortis vitae neque. Vivamus a malesuada tellus. Quisque bibendum dolor nec massa porta, nec malesuada magna auctor. Phasellus non auctor turpis, et bibendum risus. Ut pellentesque justo non neque convallis, ut imperdiet diam sagittis. Nunc arcu nisi, rutrum sagittis neque elementum, finibus consequat felis. Proin euismod elit eu urna tristique ultrices. + +Curabitur id hendrerit ipsum. Aliquam tortor nisi, dignissim vel sodales a, ultricies a ipsum. Etiam commodo arcu sed volutpat varius. Proin vehicula lacinia tempor. Vestibulum feugiat nibh eu ante tempor efficitur. Etiam eleifend a orci eu euismod. Cras a tortor risus. Cras nec urna non urna malesuada maximus vel et ipsum. In ullamcorper porttitor dolor, at fringilla tortor tristique ac. Nulla gravida tortor nec dolor convallis, accumsan sollicitudin dui luctus. Vestibulum euismod vestibulum semper. Nam semper lacus dolor, vitae rhoncus nisi blandit nec. Phasellus turpis dolor, posuere sed sodales sit amet, interdum ut arcu. + +Sed blandit nulla et sem vulputate, et semper lacus posuere. Proin velit lorem, sagittis tincidunt aliquet vitae, fermentum sed orci. Ut interdum ultrices dolor eu tempor. Quisque pretium vulputate dui at blandit. Aenean pretium urna sed purus dapibus aliquam. Mauris tristique augue pretium magna scelerisque, vel cursus sapien porttitor. Nullam neque justo, aliquam non neque id, lobortis facilisis nibh. Duis molestie dui eu augue tristique porttitor. Sed posuere justo massa, efficitur vulputate erat posuere non. Cras quis condimentum tellus. + +Etiam eleifend ipsum ut justo viverra porttitor. Nam bibendum enim nec ligula vestibulum, vel fringilla velit posuere. Praesent leo enim, varius sit amet nulla ut, sodales sollicitudin nunc. Phasellus hendrerit varius ex quis tempus. Suspendisse maximus laoreet enim, ut bibendum tortor. Ut non magna vehicula augue condimentum scelerisque. Aenean efficitur elit vel rhoncus euismod. Vestibulum sit amet sollicitudin dui. Quisque ac diam nibh. Nullam quam enim, volutpat eu turpis et, posuere consectetur neque. + +Ut tincidunt semper dictum. Etiam varius enim sit amet metus consequat malesuada in at ligula. Cras nisl dui, tincidunt a urna et, porttitor rhoncus velit. Mauris interdum imperdiet lectus ac mollis. Aliquam sollicitudin ornare mi, a varius nulla faucibus aliquet. Nunc fermentum tempor ullamcorper. Pellentesque laoreet libero condimentum pellentesque pharetra. Nullam sed eros vel erat vestibulum dictum. Nulla nec interdum dui, quis finibus nunc. + +Sed ac turpis in mi ultricies finibus sit amet et diam. Pellentesque sagittis non ligula et convallis. Suspendisse interdum est aliquet erat rhoncus semper. Donec pretium turpis ante, vel posuere mauris facilisis vel. Vivamus nibh ligula, pharetra sit amet hendrerit nec, vulputate ut sapien. Nulla ullamcorper condimentum metus vitae imperdiet. Ut eget ex purus. Nulla dapibus malesuada pharetra. Praesent sit amet ornare turpis. Nulla pulvinar felis eget arcu mollis vulputate. Vestibulum eget semper felis. Donec viverra justo id mi tempor, a mollis ipsum porttitor. Maecenas aliquet volutpat ante eget tempor. Duis ipsum nisi, scelerisque quis metus vitae, blandit faucibus nisl. Mauris rutrum nisi sed lorem dictum ultricies. + +Nulla dictum arcu augue, aliquet ornare ligula suscipit ut. Integer libero erat, bibendum quis arcu at, consequat luctus sem. Ut ut erat tincidunt, porta erat efficitur, bibendum neque. Maecenas eget convallis sapien. Nullam facilisis ex et lorem fringilla, ut convallis leo dictum. Duis porttitor, ex eu venenatis faucibus, erat augue dapibus leo, sit amet scelerisque neque dui sed arcu. Praesent at diam nec sem sodales condimentum. Vivamus vehicula urna tellus, a semper ipsum cursus id. Duis auctor enim erat, laoreet volutpat dui rhoncus maximus. Suspendisse pellentesque euismod lacus, at auctor purus. Suspendisse volutpat imperdiet diam, id laoreet est egestas at. + +Fusce lobortis ex vel condimentum convallis. Vivamus fermentum convallis dolor quis rutrum. Donec tempus ornare maximus. Mauris quam neque, dignissim rutrum maximus sed, volutpat sed lectus. Donec id augue gravida, pretium purus eu, sagittis tellus. Maecenas tincidunt gravida felis nec pulvinar. Fusce turpis urna, sodales non rhoncus nec, sodales ac ipsum. Suspendisse risus felis, imperdiet id malesuada fermentum, ultricies et erat. Suspendisse potenti. Pellentesque tellus ipsum, dapibus eget pellentesque eleifend, venenatis sed risus. Ut sed mollis nisl. Aliquam lobortis aliquam ipsum, et ornare magna bibendum ut. Proin quis justo vitae neque tincidunt maximus ultricies et purus. Nullam non semper turpis. Quisque non mauris odio. + +Donec commodo tempus egestas. Vestibulum at diam vel mi tincidunt finibus. Donec aliquam magna id eros tristique finibus. Cras ut enim sapien. Proin gravida risus a nisi dapibus, ac vulputate nunc laoreet. Donec at leo vel nulla iaculis feugiat sed et ante. Nulla a arcu at ligula sollicitudin semper sed eget est. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Ut dolor magna, luctus id elit ut, interdum viverra lacus. Aenean sed tempor metus. Aenean arcu dui, eleifend quis est sed, luctus lacinia nulla. Morbi id luctus libero. Maecenas sodales leo eu sapien maximus porta. Praesent gravida augue eu ligula pretium cursus. + +Duis aliquam luctus tortor, eu facilisis quam sodales sed. Phasellus feugiat venenatis lorem, in scelerisque est efficitur quis. Cras quis nisl nisl. Quisque magna felis, tempus nec pharetra et, tempor id ligula. Pellentesque sed dignissim velit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris convallis iaculis posuere. Nullam orci velit, venenatis eget enim quis, molestie consequat dolor. Nulla egestas risus vestibulum sagittis blandit. Nullam dui lacus, tempus euismod sapien a, eleifend sodales justo. In id tortor neque. Vivamus faucibus ante ac odio vestibulum, vitae condimentum nibh consectetur. In rutrum hendrerit est, sit amet mollis ex aliquet tempor. + +Donec non tincidunt sem. Nullam dignissim felis nibh, et accumsan felis tristique sed. Maecenas id massa erat. Ut justo ligula, aliquet eu condimentum a, aliquet eget ex. Phasellus et neque eu nisl consectetur ullamcorper. Sed gravida efficitur nisi, vitae posuere nisi tincidunt sed. Duis vitae molestie metus, vitae finibus urna. Integer at lacus vitae turpis convallis feugiat a dignissim libero. In dolor nibh, aliquam eu malesuada in, tincidunt vitae nisi. Nullam at lectus risus. Integer vitae ligula interdum, congue nibh id, tincidunt dolor. + +Mauris risus quam, mollis eu consequat vitae, molestie sit amet risus. Vestibulum congue vulputate nibh sit amet ullamcorper. Nullam elit justo, hendrerit euismod elit nec, gravida tincidunt ipsum. Aenean tellus tortor, commodo vitae cursus vitae, finibus quis mi. Nam in tellus scelerisque, cursus magna a, elementum tellus. Praesent ut lacinia justo. Donec consectetur, mi nec faucibus viverra, nisl justo suscipit est, in consequat ex urna vitae orci. Fusce molestie feugiat ex sed dictum. Phasellus interdum eros dapibus sodales volutpat. Morbi elementum sapien ante, quis sagittis justo fermentum at. Maecenas vestibulum massa quis eleifend rhoncus. Praesent auctor ex at urna pellentesque facilisis. + +Duis tincidunt porta quam, in commodo orci dapibus interdum. Donec mattis erat ante, nec faucibus magna pharetra id. Aliquam dignissim ultricies auctor. Duis quis ex mi. Phasellus ipsum mi, volutpat quis augue nec, dapibus aliquet lectus. Aenean id eros mi. Fusce rhoncus iaculis magna, commodo commodo nibh pellentesque ut. Donec consectetur lacinia erat ut luctus. Mauris efficitur erat vitae sapien fringilla sollicitudin. Maecenas a egestas ipsum. Aenean placerat congue augue, ut condimentum lorem accumsan in. Morbi ac quam nunc. + +Nunc posuere faucibus semper. Integer at convallis ex. Duis a purus molestie, dignissim mi quis, consequat nulla. Proin tempus congue ligula, sit amet ultricies enim consequat ut. Nunc ac est at nisl euismod cursus. Pellentesque vulputate molestie ipsum. Praesent varius, lacus non lobortis blandit, nibh lacus scelerisque nisi, sit amet interdum ligula tellus ac purus. Vestibulum sed quam tempor, pharetra tellus ullamcorper, tincidunt est. Nulla tempus tortor nec erat tempus eleifend pellentesque eget purus. Duis gravida dui justo, ac commodo ipsum mollis et. Nullam vitae nibh enim. Ut sodales mi eu diam sagittis, quis luctus tortor euismod. + +Maecenas a augue ac augue varius rhoncus. Fusce nunc turpis, mattis eu iaculis a, dapibus nec purus. Pellentesque imperdiet sit amet purus a blandit. Morbi augue tellus, venenatis nec tortor in, consectetur tincidunt nulla. Aenean purus libero, volutpat quis neque vitae, mattis efficitur eros. Donec sodales venenatis augue, sed faucibus magna accumsan convallis. Pellentesque efficitur elementum imperdiet. Cras at condimentum leo. Curabitur feugiat ut nisi facilisis commodo. + +Sed commodo sapien quam, quis vulputate orci lobortis nec. Aenean luctus dictum ipsum, non consequat sapien molestie vel. Proin blandit libero tortor, vitae efficitur tortor hendrerit at. Sed eleifend eu velit eu tempor. Nunc auctor tincidunt mollis. Ut molestie, erat ut lobortis convallis, odio felis sollicitudin elit, vitae ultricies lorem neque et dui. Sed venenatis tempus ullamcorper. Integer eget elementum nulla. Maecenas a placerat ipsum. Etiam sagittis sagittis rhoncus. Aliquam faucibus magna id lacus pharetra, nec interdum lacus sodales. + +Mauris ipsum sem, venenatis non elit in, feugiat pretium lacus. Fusce eget tincidunt dolor. Nam pharetra lacus vitae diam bibendum, ac placerat tortor aliquet. Sed eget gravida orci. Ut in orci lorem. Morbi condimentum ligula dolor, in dictum mi vestibulum sed. Suspendisse eu velit finibus, tincidunt dolor malesuada, mattis est. Morbi iaculis sapien vitae metus facilisis fringilla. Donec sed volutpat neque. Mauris ipsum metus, venenatis cursus mattis quis, convallis ultricies purus. Aliquam quam magna, sagittis at mi quis, lacinia iaculis turpis. Praesent viverra, ex nec euismod lacinia, urna risus pretium odio, sit amet mattis metus eros non lectus. + +Nam sed vehicula est, ac aliquet mi. Aenean iaculis placerat orci, ut lobortis dui vestibulum convallis. Vestibulum eleifend ante sed lorem interdum lobortis ut vel tortor. Sed auctor id nisl sed bibendum. Fusce maximus vulputate mauris, tempus sollicitudin nunc efficitur vitae. Nulla pellentesque molestie leo, quis hendrerit enim. In vel dapibus nisi. Nam at dui quis eros porttitor volutpat in id magna. Maecenas quis quam a justo cursus aliquet viverra sit amet mauris. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut hendrerit est at augue pretium condimentum at ut velit. Suspendisse non gravida metus. + +Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Praesent lacinia commodo elit, sed fringilla ipsum imperdiet ut. Proin a dictum ante. Suspendisse potenti. Praesent ac nulla volutpat, ultricies diam vel, auctor risus. Nullam aliquam elit vel quam suscipit molestie vel ac dolor. Ut pharetra finibus elit, id vulputate ex dignissim in. Ut ac finibus urna, a luctus magna. + +Fusce suscipit convallis eros nec blandit. Cras quis lectus nibh. Donec pulvinar rhoncus pulvinar. Vivamus nulla nisi, vestibulum vel neque et, dictum gravida ex. Vestibulum in arcu vitae nibh auctor rhoncus ac in massa. Sed semper enim ac dui sollicitudin porttitor. Etiam ante erat, laoreet sed dolor eget, cursus commodo lorem. Curabitur maximus tincidunt nulla at molestie. Phasellus ultrices felis a cursus facilisis. Nullam a semper tortor. Nulla ac vulputate justo. Mauris maximus nisi quam, elementum eleifend nulla condimentum nec. Aenean congue tincidunt mi, id mollis orci blandit vitae. Integer placerat orci at nisl vestibulum lacinia. + +Sed egestas posuere egestas. Quisque pulvinar velit commodo felis accumsan, a consequat purus laoreet. Ut at arcu at augue sodales rhoncus. Ut non nisl dui. Sed sed nulla quis orci eleifend auctor ut et sem. Aliquam erat volutpat. Donec egestas tincidunt leo in interdum. Donec varius odio nulla, id porttitor erat venenatis ut. Nulla ac nisl ut enim placerat congue. Fusce consequat purus eget risus luctus finibus. Donec in blandit odio. Sed vestibulum nisl ut diam vestibulum volutpat. Donec magna quam, tempor eget velit quis, blandit tristique lacus. Pellentesque et orci dui. + +Integer tempor mollis purus ut volutpat. Pellentesque efficitur cursus neque in ullamcorper. Integer ac tincidunt lacus, at venenatis tellus. Curabitur convallis commodo enim a convallis. Aenean sagittis sodales nibh, sed eleifend diam ultricies at. Vestibulum erat mauris, lobortis ac tempor a, viverra non sem. Nulla non ipsum mollis, dignissim elit a, laoreet enim. + +In laoreet purus eget orci rhoncus viverra. Quisque vel metus quis nulla hendrerit viverra. In consectetur velit vitae purus vestibulum, in hendrerit odio sollicitudin. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ut est sed ligula tincidunt fermentum nec ac nulla. Nam in sagittis velit. Vivamus vel dapibus erat, ullamcorper sollicitudin metus. + +Quisque pulvinar nulla eget mi mattis, ut convallis nulla ullamcorper. Vivamus placerat mauris vel elementum aliquet. Sed ac accumsan eros. Vivamus vitae rhoncus urna. Fusce gravida cursus varius. In posuere finibus leo cursus molestie. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris vulputate mi ut nunc finibus convallis. Praesent luctus erat eu urna facilisis convallis. + +Nam efficitur tempus augue varius porttitor. Pellentesque scelerisque dolor scelerisque, dignissim massa eget, iaculis nibh. Praesent viverra molestie dui a pulvinar. Mauris malesuada mattis felis, vitae sagittis metus pellentesque viverra. Phasellus faucibus congue blandit. Pellentesque mattis, justo sed imperdiet rutrum, metus metus porta nisi, vel malesuada lorem massa at dolor. Nullam nisi eros, tristique quis mollis ac, hendrerit nec leo. Praesent viverra molestie vestibulum. Nullam eu aliquam risus, at dignissim diam. Quisque blandit fermentum erat, sed ullamcorper augue pellentesque in. Integer eleifend libero non lacus sagittis auctor. Aliquam volutpat interdum accumsan. + +Etiam in eros porta, bibendum lacus eget, feugiat orci. Integer massa dui, commodo ac luctus nec, ornare id velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis dignissim scelerisque ex. Aliquam tempus cursus nibh, sed scelerisque libero sagittis ut. Morbi finibus vestibulum nulla, nec aliquam urna venenatis vulputate. Pellentesque ultricies, lorem a dignissim bibendum, augue nisi eleifend libero, cursus ultrices nulla purus a nunc. Quisque dictum pulvinar turpis vitae finibus. Etiam eget ultrices diam. Sed commodo enim ante, fermentum rhoncus tortor tincidunt ac. Suspendisse fermentum aliquet erat non posuere. Aenean vel dapibus lorem. + +Aenean lorem libero, rutrum vel egestas vel, pellentesque ut ante. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis diam, pellentesque sit amet ultricies sed, vestibulum in leo. Morbi aliquet, quam ut fringilla sollicitudin, justo risus suscipit mi, in vulputate neque erat ut turpis. Duis auctor dui non urna sagittis dictum. Vestibulum nec felis vel sapien congue volutpat a a orci. Phasellus tincidunt libero ac lorem feugiat, ac elementum lectus eleifend. Praesent sit amet est id massa convallis congue. Integer ac nunc eget orci pharetra vehicula. Mauris et ultricies diam. Nunc et pellentesque lacus, eget bibendum sapien. Morbi condimentum mi ac metus euismod convallis. Proin consequat rhoncus varius. Maecenas feugiat elementum vulputate. + +Vestibulum tempor feugiat dapibus. Ut ornare in augue non euismod. Nulla faucibus gravida condimentum. Curabitur pharetra dui non lorem sagittis iaculis. Quisque vitae nibh magna. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce quis pharetra sem, sed aliquet lectus. + +Fusce volutpat tempor tincidunt. Pellentesque ultricies imperdiet tincidunt. Integer porta dictum lorem, non luctus tellus bibendum sit amet. Quisque posuere felis et est dapibus, commodo rutrum leo molestie. Fusce sodales felis sed sem pharetra pretium. Praesent nec sollicitudin nisl. Donec volutpat convallis elementum. Duis id libero augue. Nulla facilisi. + +Nulla viverra, urna nec efficitur vulputate, metus odio lacinia purus, in sagittis elit erat vel massa. Fusce ligula leo, finibus non felis dignissim, dictum ullamcorper odio. Phasellus vel eros eu sem venenatis sagittis a nec nulla. Pellentesque sapien elit, lobortis quis dictum et, maximus vitae metus. Curabitur quis aliquam eros. Quisque cursus condimentum urna vel efficitur. Etiam aliquet lorem ut varius suscipit. In viverra molestie felis vitae vehicula. Curabitur cursus, nunc sodales faucibus ullamcorper, urna magna dapibus velit, accumsan blandit mi quam nec justo. Donec ac dui lorem. Sed eu sagittis nulla. Suspendisse ut ante lacus. Fusce non tristique felis. Nulla convallis consectetur arcu, semper egestas urna efficitur quis. Nulla ac turpis at leo pretium maximus. Morbi cursus diam ac purus imperdiet, non commodo tellus cursus. + +Mauris ut eros suscipit, suscipit nisi vel, porttitor sapien. Morbi in nulla sapien. Cras maximus pretium justo, vel eleifend urna vulputate sed. Aenean egestas nisl ex. Curabitur sed pharetra ligula. Nulla blandit lorem id mauris tincidunt, at elementum risus aliquet. Quisque id interdum dui. + +Aenean nec elit condimentum ex viverra hendrerit. Pellentesque blandit nibh elit, a ultrices orci vestibulum vitae. Donec ultricies malesuada justo ac luctus. Pellentesque laoreet nunc a sem varius, ac dapibus ligula volutpat. Aenean sem felis, aliquam nec ullamcorper quis, ullamcorper non ante. Pellentesque libero leo, sagittis nec tempus a, tincidunt eget risus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec elementum massa vel diam suscipit suscipit. Suspendisse eget neque porta, cursus orci eget, imperdiet libero. + +Vivamus faucibus vulputate sapien, non pulvinar ex sodales vitae. Maecenas consequat est urna, id faucibus eros dictum at. Vestibulum tortor mi, porta vitae sagittis sed, convallis quis enim. Vestibulum gravida justo pulvinar sem ornare, efficitur dictum neque varius. Nullam egestas congue tellus vitae interdum. Nam lectus erat, porta vitae lorem non, mollis semper velit. Nulla vestibulum tincidunt elit. Phasellus sed vulputate leo. + +Sed efficitur quam ac iaculis volutpat. Praesent nec feugiat ex. Aenean at ligula iaculis, imperdiet lacus et, condimentum magna. Integer euismod leo id mauris condimentum, quis semper lacus blandit. Sed volutpat neque urna, sit amet ullamcorper est dignissim non. Vestibulum tristique sollicitudin risus, sed hendrerit massa finibus id. Vestibulum sit amet risus non eros vehicula malesuada. Nam facilisis lacus sed quam pulvinar, at fermentum lectus tincidunt. + +Vivamus laoreet sem nec odio blandit, ut ornare sem egestas. Suspendisse potenti. Nulla aliquam pretium volutpat. Maecenas a orci suscipit, aliquam eros a, maximus urna. Aliquam eu gravida justo, et hendrerit justo. Suspendisse a commodo lorem, quis viverra nisl. Nulla vel pellentesque nisl. Nulla ligula tellus, vehicula sit amet turpis in, sodales tincidunt tellus. Proin ut nibh sed ipsum porta dignissim vel sed mauris. Interdum et malesuada fames ac ante ipsum primis in faucibus. In hac habitasse platea dictumst. Vestibulum efficitur nulla rutrum, accumsan lacus at, dignissim tortor. Morbi egestas metus ut nibh tincidunt posuere at sed elit. Integer eget hendrerit nulla. + +Donec sagittis sagittis tempus. Proin iaculis neque vehicula commodo faucibus. Pellentesque erat ante, vehicula sed efficitur vitae, varius non sem. Mauris pellentesque, felis nec egestas scelerisque, nulla nunc fringilla arcu, feugiat fringilla quam neque in elit. Nullam ut ex odio. Mauris enim risus, consequat at suscipit at, pulvinar ut arcu. Fusce mollis sem sed tellus tempus pellentesque. In pharetra, libero sed tristique vestibulum, massa velit egestas risus, at mollis augue lorem sit amet turpis. Mauris risus dui, sagittis vitae congue ut, condimentum ut augue. Quisque non sollicitudin purus, sit amet consectetur sapien. + +Sed scelerisque ipsum sed augue varius, sit amet ultricies purus posuere. Etiam vehicula at nunc in posuere. Cras eget risus a sapien mollis facilisis. Morbi vel purus auctor, faucibus mauris non, malesuada leo. Donec venenatis consectetur libero in pretium. Ut efficitur molestie metus id scelerisque. Nunc dolor justo, pharetra vel sagittis vitae, placerat ut justo. Donec tempor fermentum est semper auctor. Nullam tincidunt risus non risus elementum varius. Suspendisse euismod augue metus, nec pellentesque enim sagittis ac. Morbi et lectus quis justo commodo varius commodo vel risus. In bibendum purus in erat ullamcorper, sit amet dignissim sapien scelerisque. Quisque tempus elementum rutrum. + +In viverra sollicitudin pretium. Sed finibus scelerisque sollicitudin. Nulla lobortis purus in lectus iaculis, a ornare ex accumsan. Morbi malesuada mollis placerat. In hac habitasse platea dictumst. Pellentesque sed dui quis odio scelerisque molestie eu nec libero. Proin viverra magna ligula, at luctus lacus posuere sed. Nullam non congue mi. Praesent non mattis neque, sit amet tempus diam. Mauris eget cursus lacus, id dictum mi. + +Sed semper velit in vehicula tristique. In lobortis est augue, et maximus tellus iaculis ut. Proin quis ex maximus erat iaculis imperdiet. Curabitur ultrices turpis ac nibh congue ornare. Fusce a aliquet felis, nec sodales tellus. Nunc mauris ante, feugiat et facilisis at, pharetra ultricies dui. Integer felis metus, porttitor ac ipsum vitae, placerat varius ex. Morbi in dui a nunc aliquam posuere. Nulla tempus tortor ac lorem consectetur sodales. Praesent sit amet placerat diam. Curabitur sodales mauris ante, et tincidunt lacus ultricies quis. Nulla eu finibus nisl, sit amet volutpat leo. Donec ligula enim, feugiat non aliquet nec, congue interdum lorem. Pellentesque a nisi blandit, semper massa sit amet, auctor eros. + +Aenean ligula libero, commodo a erat at, tristique facilisis quam. Sed congue fringilla lacus, vel iaculis quam suscipit ornare. Curabitur non pretium mi. Donec condimentum odio dui, id malesuada ipsum porta ac. Aliquam imperdiet cursus ullamcorper. Duis cursus tincidunt nibh sit amet cursus. Aliquam urna orci, sodales ac ipsum at, placerat lobortis neque. Sed sit amet convallis felis, vestibulum finibus arcu. Phasellus venenatis egestas felis, id aliquam turpis iaculis non. Proin a lacus ut felis malesuada sodales. Duis elementum, eros ac placerat bibendum, quam nisl varius mauris, vel venenatis neque augue et justo. Nunc varius sem velit, vel tempus mi aliquet id. Fusce purus massa, mollis id nulla non, dignissim cursus massa. Sed sollicitudin interdum felis, nec eleifend nisl feugiat eget. Nulla rutrum id odio ut consectetur. Maecenas fermentum turpis vitae libero ullamcorper suscipit. + +Vestibulum auctor lectus ligula, non sollicitudin odio maximus nec. Cras faucibus, felis sed suscipit tempor, risus lorem consectetur est, eget pulvinar nibh dui eu dolor. Vestibulum pellentesque, ex aliquam vulputate laoreet, libero lorem rhoncus risus, vitae congue velit justo vitae nisi. Nullam placerat venenatis tortor, sit amet lacinia augue placerat nec. Quisque vulputate lectus vel pulvinar condimentum. Nullam at libero iaculis, mattis purus vel, tincidunt diam. Suspendisse eu ullamcorper eros. Nulla id eros odio. Ut enim leo, ultricies quis mauris sed, interdum commodo felis. Etiam enim lacus, scelerisque a interdum eget, mollis vel tellus. Phasellus vel vulputate orci. Nulla ornare leo sed arcu rhoncus convallis. Duis libero arcu, pulvinar ut tellus vitae, aliquam euismod magna. Donec semper nisi eget nulla tempus, sed condimentum massa sollicitudin. + +Suspendisse bibendum ante vitae ullamcorper semper. Praesent dui est, elementum nec lacus in, pellentesque accumsan sapien. Cras quis urna at lacus posuere commodo eget nec arcu. Nunc varius ante quis ligula rhoncus, ut dignissim metus commodo. Integer volutpat gravida nunc eget faucibus. Quisque imperdiet, mauris vitae cursus malesuada, lacus mauris tempus sem, ut accumsan purus lectus quis nisi. Vestibulum aliquam dui sed nisl laoreet, id posuere quam imperdiet. Aliquam ultricies non enim vel tempor. Donec sed feugiat justo, vel rutrum enim. Phasellus lorem quam, dapibus a tincidunt vulputate, pellentesque non lorem. + +Nunc quam diam, condimentum sit amet enim semper, hendrerit laoreet magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam et pulvinar leo, ac fermentum lectus. Sed erat ante, mattis ac tempor vitae, euismod at tortor. Ut sagittis fringilla scelerisque. Ut pulvinar molestie leo eu faucibus. Sed quis efficitur purus. Pellentesque nibh nisi, porta id orci id, sagittis sodales tellus. Ut venenatis velit a lectus lacinia, at ultrices nisi commodo. Vivamus eros orci, ornare vel ullamcorper elementum, posuere id ligula. Aliquam non massa pellentesque, semper ipsum scelerisque, dapibus leo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nulla pretium leo diam, sit amet bibendum lacus tempus quis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer quis pulvinar nibh. + +Mauris sed eros nisi. Cras malesuada, turpis vitae laoreet porta, sapien odio maximus nulla, ut efficitur mauris odio tincidunt elit. Nam ut urna eros. Sed at vestibulum risus. Nulla luctus pulvinar rhoncus. Pellentesque maximus ligula a est ullamcorper, sed tempus tortor ultrices. Curabitur ligula odio, mollis sit amet risus quis, tempor auctor magna. Suspendisse vel quam quis lorem commodo facilisis. Donec eu ipsum suscipit, molestie dui sed, fringilla dui. Proin placerat ex a lorem sodales convallis. Sed molestie quam mi. + +Fusce laoreet nec dolor id bibendum. Nunc condimentum vulputate massa lacinia fermentum. Donec maximus cursus eros sed porta. Nunc eu porta turpis. Nulla cursus et nisl eu elementum. Ut rutrum scelerisque aliquet. In tellus erat, rhoncus id tempus vitae, vestibulum non quam. + +Vivamus luctus elit a efficitur dapibus. Praesent magna mi, pellentesque sed accumsan dapibus, blandit at lectus. Praesent sodales erat diam. Pellentesque placerat lobortis enim, a dapibus ligula molestie ut. Phasellus dui augue, suscipit ac urna non, iaculis eleifend augue. Maecenas sed elit iaculis, pretium ligula lacinia, aliquam libero. Nulla sem mi, pellentesque viverra lorem vel, luctus vulputate augue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque fermentum nunc ex, at lobortis turpis congue vitae. Suspendisse iaculis elementum enim commodo facilisis. Vestibulum non neque tellus. Proin vitae sodales urna. Mauris interdum purus et neque tempus, vitae mattis libero finibus. Suspendisse iaculis condimentum nibh, eu mattis enim vehicula a. + +Fusce dictum urna non lectus ullamcorper vestibulum id in elit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc fermentum odio non ante sollicitudin posuere. Praesent vulputate quam nec magna euismod pellentesque. Etiam tempor nunc eget velit volutpat eleifend. Mauris sodales nunc ullamcorper, gravida purus eget, rutrum sem. Nam a sapien rhoncus, scelerisque lacus ac, condimentum ipsum. Pellentesque suscipit, ligula ut rhoncus ullamcorper, ligula augue rutrum lorem, eget aliquam risus tortor eget metus. Curabitur scelerisque bibendum purus vel vulputate. Morbi et odio at neque sodales suscipit. Nam at pretium nulla. In scelerisque vulputate suscipit. Nulla facilisi. Pellentesque eu sem eu ligula efficitur viverra. Pellentesque gravida consequat urna id bibendum. + +Nulla consectetur facilisis est nec aliquam. Proin hendrerit magna suscipit ante accumsan hendrerit. Nulla ut sem id neque tempor vehicula. Sed eget massa eget sem placerat tincidunt. Ut eleifend, justo sit amet egestas lacinia, ex velit pharetra nulla, nec aliquet elit neque ut turpis. Quisque in gravida velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed euismod malesuada convallis. Ut ultricies, magna et blandit fringilla, dolor dolor maximus sapien, in aliquet augue est vel odio. Nulla commodo elit massa, euismod tempor turpis aliquet eget. Morbi non odio et tortor egestas ultrices. Etiam semper, ipsum et dictum pharetra, eros purus bibendum lacus, vel laoreet lacus sem vehicula nibh. Nunc enim nulla, pulvinar sit amet lobortis sed, porttitor molestie risus. Morbi tincidunt diam mi, nec auctor sem rutrum at. + +Donec pellentesque odio odio, eu condimentum risus consectetur quis. Vivamus ullamcorper, mauris non semper rutrum, dui risus suscipit tellus, ut tempor velit risus vitae nibh. Duis tempor nibh at tristique rutrum. Cras congue nisl at sem sollicitudin efficitur. Aenean auctor purus vel libero fermentum elementum. Mauris convallis orci id interdum accumsan. Aliquam at metus risus. Sed accumsan, quam id dignissim congue, velit risus eleifend ligula, ut fringilla elit erat at neque. Aliquam tempor, quam ut ultrices volutpat, orci lectus vulputate turpis, eget pharetra purus nisi non lorem. Ut condimentum convallis justo, a volutpat eros. Sed tempus turpis leo, in convallis est fringilla sed. Vivamus eu laoreet tellus. Quisque ullamcorper ullamcorper leo. Nam fermentum egestas facilisis. Nulla egestas ligula feugiat urna molestie, faucibus convallis erat accumsan. Cras pellentesque ipsum lectus, vitae luctus dolor suscipit nec. + +Vivamus vel nibh sed mi tincidunt cursus quis facilisis tellus. Donec eget est eu velit pretium molestie. Maecenas lacinia risus turpis. Sed tristique id risus sit amet venenatis. Duis maximus, metus ac molestie convallis, quam ex dapibus sem, at rutrum metus nisi quis lectus. Suspendisse sodales in nisi at hendrerit. Maecenas suscipit lobortis vulputate. Nunc convallis sit amet elit eget porta. Mauris tincidunt massa in augue finibus iaculis. Vestibulum imperdiet, orci vel bibendum laoreet, ligula quam mollis lacus, a eleifend nisi tellus vitae ipsum. Cras non ante sollicitudin, auctor dolor id, condimentum tellus. Morbi malesuada leo nec lectus scelerisque, nec interdum lacus ornare. Integer ultrices ligula nunc, sed blandit urna aliquam eget. Proin consequat viverra ex non rhoncus. Phasellus id nibh at tortor sodales blandit. Donec dignissim ipsum vel ligula malesuada rhoncus. + +Nullam mauris sem, dapibus ac nisl at, hendrerit faucibus nisi. Mauris dictum fermentum pellentesque. Praesent in gravida odio. Vestibulum pharetra iaculis est quis tincidunt. Sed venenatis rhoncus lacus, non porta ante vulputate at. Duis fringilla ipsum at urna euismod molestie. Duis a porttitor ante. Mauris pharetra elit et metus auctor, a eleifend sapien porta. Vivamus ut tincidunt purus, lobortis euismod tellus. Nullam non nulla gravida, laoreet tellus ac, placerat urna. + +Sed justo sapien, scelerisque nec nisl vel, efficitur aliquet purus. Phasellus eget posuere nisl. Integer porta vel purus nec ornare. Pellentesque rutrum in nulla at hendrerit. Nullam elementum sodales volutpat. Duis tempus, purus sagittis auctor ullamcorper, dui erat hendrerit nulla, id finibus eros dui quis risus. Quisque posuere nunc quis augue placerat lacinia. Nunc quis lorem vulputate, tincidunt enim nec, rhoncus odio. Ut porttitor porta velit ut vulputate. Duis convallis sagittis magna nec gravida. Ut eu luctus sem, non placerat erat. Vivamus viverra ut ligula ac finibus. Cras ligula urna, tincidunt id risus eu, dapibus viverra lorem. Aliquam erat volutpat. Sed dolor nisi, faucibus non ligula faucibus, laoreet bibendum diam. Vivamus sagittis lacus ut condimentum tincidunt. + +Proin tristique mi ullamcorper dolor accumsan mollis. Nulla facilisi. Pellentesque iaculis mattis augue ac rutrum. Mauris in nulla at ligula fringilla pulvinar. Nam commodo ornare nibh ac viverra. Maecenas eget augue a risus mattis ullamcorper non at nibh. Aenean nec erat diam. Pellentesque augue nisl, venenatis mollis dolor non, bibendum malesuada leo. Pellentesque elementum purus ornare mauris iaculis porttitor. Sed mollis tempor dui eu elementum. Donec porttitor tellus non mattis gravida. Mauris venenatis suscipit convallis. Sed dolor sapien, pellentesque et tellus a, tempus cursus magna. Nunc lobortis leo magna, at maximus orci ultrices eget. Nullam vulputate dictum nisi, vel egestas orci. + +Nam nibh mi, ornare sit amet nibh vel, lobortis lacinia leo. Ut egestas mi vel nunc luctus vestibulum. Nullam id tempus felis, eget convallis erat. Aliquam venenatis ut tellus id fringilla. Aliquam erat volutpat. Sed vehicula, odio nec mollis dapibus, ligula sapien consequat risus, ut volutpat diam neque ut neque. Mauris venenatis libero vitae sem elementum, sit amet maximus massa varius. Maecenas malesuada mi id leo euismod, eu maximus sapien vehicula. Nulla facilisi. Duis nec venenatis ante, non pulvinar lacus. In sollicitudin sit amet velit suscipit egestas. Maecenas a lectus euismod, dictum nulla at, malesuada lectus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec lorem massa, laoreet non elit nec, sollicitudin blandit felis. + +Cras dignissim fermentum tellus ut fringilla. Maecenas maximus eros pretium sagittis venenatis. Maecenas id enim ac est semper efficitur ac hendrerit leo. Cras eu arcu tincidunt risus pellentesque aliquam. Pellentesque vel sodales libero, non rhoncus enim. Integer vulputate vulputate libero, eu consectetur eros euismod vitae. Fusce magna nibh, vehicula sed turpis eget, malesuada ultricies sem. Sed convallis sem nisl, rhoncus mollis mauris bibendum ut. Pellentesque vitae massa a justo dapibus laoreet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Sed mollis egestas nisl vel ultricies. Phasellus sit amet varius leo, sed vehicula sapien. Etiam in urna eget neque aliquet ultricies in nec quam. Aliquam at feugiat elit. + +Maecenas placerat nisi ultrices neque pulvinar, et ultrices ante tempus. Vestibulum laoreet quam lacus, eget commodo magna dictum consectetur. Aliquam erat volutpat. Aenean tincidunt ipsum sit amet justo aliquet tempus. Quisque blandit sit amet est facilisis dictum. Sed non consequat dui. Integer purus diam, gravida quis tortor lobortis, iaculis vehicula sem. Morbi pellentesque est elit, vitae interdum elit laoreet et. Vestibulum in blandit ante, eu blandit velit. Morbi sagittis eu est eu vulputate. Nullam ac mi feugiat nibh feugiat suscipit. Sed interdum tincidunt efficitur. Integer dictum sem erat, ut pharetra libero lacinia in. Mauris at hendrerit odio, a facilisis lacus. Donec blandit massa ac nisi ultrices faucibus. + +Aenean tempus id ligula at mollis. Cras id dui magna. Integer id neque tincidunt, rhoncus nunc et, laoreet nibh. Pellentesque consectetur odio ligula, et consectetur tortor scelerisque non. Cras quis ex pharetra mi ornare lobortis. Phasellus fringilla interdum felis, vel aliquam ligula. Mauris cursus varius turpis in iaculis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent eget mi id nulla semper dapibus. + +Vivamus lacus augue, eleifend dignissim ipsum eu, interdum accumsan massa. Nam id quam vel ligula consectetur volutpat id eget eros. Aliquam sed felis velit. Duis egestas velit ac dolor blandit, ut elementum mi tincidunt. Integer varius nisl a sapien pellentesque, et pellentesque ante elementum. Fusce ac orci interdum, faucibus nisl ut, sagittis nisi. Vestibulum sed diam eu magna congue ornare. Integer dignissim ligula sit amet mauris pretium vulputate. Duis ac tincidunt magna. Sed vehicula, ante nec vulputate venenatis, mauris odio blandit dolor, vitae lacinia nibh lorem et metus. + +Sed non sapien vel lorem tempor semper ac ac tellus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas auctor ante sit amet suscipit vulputate. In ac cursus turpis. Donec eu sem bibendum, blandit est nec, blandit tortor. Pellentesque scelerisque justo vitae magna cursus, vitae egestas libero interdum. Pellentesque vitae ligula vel mauris vehicula convallis. Nunc ornare lectus sit amet lorem aliquet dignissim. Cras nec ligula pretium, semper nulla a, pulvinar lacus. Sed ac augue bibendum, posuere ipsum eu, venenatis quam. Sed elementum nunc quis odio pellentesque, a vestibulum ex congue. Cras id malesuada arcu. Mauris ut egestas nulla, sed tempus ligula. Donec ac elit ut nisi pulvinar elementum. Suspendisse id auctor lectus, vitae ultricies lacus. + +Vestibulum pulvinar ornare cursus. Curabitur accumsan sollicitudin mi in vestibulum. Vestibulum vulputate tincidunt luctus. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus a mattis neque, id malesuada odio. Aliquam id auctor magna. Vestibulum quis nibh lacinia, tincidunt felis sed, vestibulum ex. + +Donec placerat ipsum diam, vel imperdiet velit eleifend ac. Quisque dapibus erat non dui convallis eleifend. Praesent pellentesque felis id suscipit rutrum. Donec quis lacinia turpis. Nulla justo eros, fringilla vel fringilla in, euismod sollicitudin arcu. In ut lobortis arcu, at rhoncus elit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. + +Pellentesque iaculis quam nec interdum eleifend. Fusce mauris mauris, imperdiet sollicitudin volutpat eu, sollicitudin blandit leo. Vestibulum hendrerit diam gravida erat imperdiet, blandit dignissim dui tempor. Phasellus viverra ante quis aliquam finibus. Duis sed condimentum augue, nec sollicitudin dui. Ut ullamcorper metus ac ligula accumsan, ac fringilla metus gravida. Morbi rhoncus est nunc, at lacinia ex fringilla sit amet. Integer lobortis ultricies nisi vitae accumsan. Nulla nec sagittis ante. Mauris bibendum nisi ut magna vulputate maximus. Praesent in elit eu metus dignissim cursus. Ut a tellus felis. Mauris eget ornare diam. + +Suspendisse potenti. Nam nec tortor ex. Duis eu elementum ligula. Pellentesque efficitur sodales orci, vitae sollicitudin odio tempor ac. Nam lacus ante, lobortis vitae lobortis eu, eleifend et velit. Nulla et lectus eu nibh tristique malesuada a sit amet felis. Proin molestie, lectus vitae sagittis malesuada, massa velit finibus est, id vestibulum dolor felis eget lectus. Phasellus varius pellentesque neque, a malesuada ex mattis eget. Nulla facilisi. Phasellus interdum placerat lobortis. Sed et odio tristique, viverra libero et, sagittis diam. Proin tristique lectus id bibendum maximus. Integer ornare euismod ligula nec malesuada. Nulla facilisi. Nullam bibendum hendrerit pharetra. + +Duis pharetra auctor felis, eget aliquet justo commodo at. Donec quis nibh non arcu sodales efficitur. Aenean lorem sapien, tincidunt eu sapien nec, convallis tempor diam. Curabitur volutpat, felis ut congue euismod, lacus nisl euismod ipsum, vel consectetur orci nibh sed ligula. Curabitur consectetur vitae mauris sed tempor. Fusce vel pretium nibh, et tristique dolor. Aliquam semper a ante non finibus. Praesent euismod urna augue, at feugiat orci commodo ut. Duis imperdiet purus non augue cursus gravida. Maecenas sodales purus et sollicitudin venenatis. Nam ultrices lorem lectus, ut pretium turpis hendrerit eget. Vestibulum vel lacinia lectus, at dignissim diam. Vivamus ut tortor ac tortor blandit finibus. Etiam porttitor tortor sit amet elit lacinia gravida. Pellentesque pretium, orci vitae tempor vehicula, nisi tellus tincidunt sapien, id egestas felis quam a nunc. Pellentesque sed vestibulum nisl. + +Mauris congue, justo vel dapibus pretium, ipsum augue consectetur leo, at aliquam ipsum neque non mauris. Nam sollicitudin in urna eu blandit. Aliquam erat volutpat. Morbi eget interdum ante. Pellentesque congue gravida arcu, eget ornare ante elementum nec. Integer a auctor dolor, in varius sem. Etiam dictum nibh magna, at volutpat nunc blandit eu. Curabitur at sem ipsum. + +Nulla porttitor erat eget volutpat iaculis. Pellentesque egestas, nisl vel ultrices efficitur, ex magna condimentum urna, in tincidunt massa turpis eget metus. Etiam vitae magna elementum, fermentum justo ut, pretium velit. Aliquam aliquet venenatis malesuada. Quisque consequat enim metus, pellentesque blandit leo rhoncus id. Vivamus vestibulum, est in condimentum mollis, ligula eros blandit lorem, vitae dignissim lectus mi eget nulla. Cras posuere leo at neque convallis, sed pretium massa ultrices. Morbi tempus porttitor turpis. Nam vitae mauris et massa venenatis tincidunt. Quisque vestibulum lacus ligula, in sodales felis elementum vel. Sed a tellus condimentum, sodales nisi id, aliquet elit. + +Vestibulum ac ex eu turpis lacinia condimentum nec eget ipsum. Nulla non imperdiet erat, at malesuada lorem. Praesent efficitur imperdiet augue vel laoreet. Sed dignissim, ante non porta feugiat, enim nunc rhoncus lorem, eu luctus turpis risus sit amet orci. Vivamus bibendum pharetra venenatis. In at gravida nisi. Vestibulum pulvinar sapien ac augue scelerisque, vitae blandit nibh malesuada. Nunc vitae est faucibus, accumsan risus a, laoreet urna. Cras imperdiet lacus in augue facilisis dignissim. Duis sed nisi quis diam mollis rutrum. + +Vestibulum eget egestas neque. Praesent viverra, velit quis porttitor euismod, sem libero venenatis mauris, id congue urna nulla sed lorem. Nulla mollis metus nec diam malesuada aliquam. Duis ac risus nunc. Cras sollicitudin urna nunc, id sodales quam gravida sit amet. Fusce in vulputate orci, in venenatis lorem. Donec a sagittis ipsum. Quisque consequat sapien tellus, sed efficitur lacus aliquam eu. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean in neque at augue elementum commodo pellentesque eget ligula. Nullam condimentum efficitur tincidunt. Phasellus posuere tincidunt odio sed facilisis. Aenean eu risus at est euismod facilisis. Curabitur elit purus, malesuada quis blandit id, rutrum vitae dui. Praesent porta rutrum erat, ullamcorper viverra nunc. Cras ut velit dui. + +Etiam posuere pulvinar mi at ullamcorper. Pellentesque finibus, tellus non convallis commodo, orci nibh dapibus nisl, at aliquam purus nulla eget dui. Praesent fringilla urna nec nulla pellentesque, nec rhoncus turpis ultricies. Sed laoreet velit pellentesque libero varius, ac interdum urna viverra. Phasellus sed consectetur massa. Morbi quis velit nec ipsum varius tempor. Proin id sodales felis. Aliquam lacinia risus quis ligula condimentum sodales. Nulla vel arcu aliquet neque iaculis aliquet. Cras sed lorem eu turpis tincidunt sodales. Sed pulvinar elementum ligula, nec faucibus nisl. Fusce nec tellus eget dui tempor sagittis. In vitae enim in ex viverra commodo. Duis est erat, fringilla ac semper a, dapibus in tortor. + +Maecenas commodo vulputate iaculis. Aliquam non facilisis est. Donec pellentesque vitae nibh nec volutpat. In commodo metus placerat lorem commodo, non lacinia nibh bibendum. In viverra rhoncus erat. Mauris nec nisl blandit, elementum justo nec, accumsan libero. Aliquam elementum, velit et ullamcorper convallis, turpis lorem elementum lorem, quis consequat tortor eros ut erat. Curabitur et enim quis felis vulputate congue et vel purus. Sed elementum interdum ipsum, sed tincidunt arcu scelerisque et. + +Aenean interdum elementum mauris ut porta. Mauris vel purus ac odio vulputate pulvinar at quis odio. In turpis turpis, convallis in augue id, elementum vulputate lorem. Sed tincidunt fermentum vulputate. Nunc ipsum ipsum, molestie vel convallis id, pharetra vel arcu. Maecenas vel dui elit. Sed blandit dolor sit amet risus commodo faucibus. Duis rhoncus felis arcu, vel aliquam nisi faucibus sed. Suspendisse cursus eget nunc ut bibendum. Fusce non ligula risus. Curabitur vitae cursus metus, quis fringilla diam. Phasellus turpis ante, pulvinar ac turpis tristique, sollicitudin congue lectus. Proin quis ipsum at ipsum euismod euismod. + +Nunc ut fermentum nunc. Donec id commodo lacus, at hendrerit justo. Donec cursus purus sodales nunc commodo, quis bibendum elit hendrerit. Quisque quis pellentesque nibh, ac vulputate neque. Aenean non placerat felis, eget feugiat ligula. Vivamus a eros accumsan, cursus neque at, ultricies magna. Fusce tincidunt tellus vitae mi rutrum laoreet sed quis ligula. Nullam ullamcorper ligula ligula, vel fringilla metus aliquet a. Morbi aliquet, mauris vel interdum venenatis, ex arcu venenatis tortor, at tincidunt dui ipsum et arcu. Nulla blandit gravida nulla ac iaculis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed ullamcorper lectus in sapien lobortis, eget posuere massa varius. + +Cras lacinia nunc faucibus mauris placerat pretium eget non sapien. Morbi viverra bibendum posuere. Aliquam accumsan sagittis dolor non iaculis. Sed nunc odio, lobortis in dolor ac, rutrum fermentum velit. In venenatis, velit in molestie semper, est magna condimentum dui, aliquam auctor lorem nulla vel ligula. Morbi vehicula turpis turpis, quis mollis justo tempus vitae. Proin luctus lacus in mauris porttitor aliquet. Duis vitae nunc ex. Nullam eget erat vitae nulla iaculis rhoncus. Sed lacus dui, suscipit eu leo vitae, tincidunt dignissim risus. Praesent ut massa ut arcu sagittis consequat. Sed sit amet tincidunt turpis. Nulla bibendum, felis eget posuere dictum, libero mi tristique elit, at venenatis neque elit ac quam. Curabitur nisi sem, scelerisque nec nunc ac, pulvinar pharetra odio. Curabitur egestas pellentesque arcu sed suscipit. In mattis dolor vel dui mollis feugiat. + +Sed commodo, dui ac vestibulum dictum, tellus libero tincidunt lacus, viverra commodo est felis vitae urna. Proin tincidunt neque vel turpis eleifend laoreet. Vestibulum sagittis, tortor sed iaculis consequat, urna ante sagittis est, ac ullamcorper lorem nibh dignissim odio. Nam arcu mi, cursus et blandit nec, aliquam ut nulla. Aenean quis iaculis tellus, eu egestas augue. Etiam pretium eget nisi quis iaculis. Aliquam sed convallis eros. Ut bibendum rhoncus lacus, in vestibulum dui ultricies id. Fusce vestibulum, mauris ut tempor consequat, dolor nisl pellentesque elit, in porta arcu elit vel ante. Vivamus at nisl est. Etiam nec blandit tortor, at pulvinar orci. Proin semper dapibus tincidunt. Phasellus lobortis enim ullamcorper dolor tempor cursus. + +Mauris a libero in enim gravida aliquet ac sit amet nibh. Vestibulum ac neque posuere, blandit libero ac, vehicula enim. Aliquam auctor iaculis eros sit amet molestie. Quisque faucibus turpis et massa tristique, nec dapibus mauris aliquet. Proin blandit aliquet mauris, non tincidunt odio blandit vel. Curabitur a nibh in eros commodo tincidunt eu et libero. Curabitur sit amet dapibus ex, in condimentum magna. Sed eu sem sem. Nunc tellus dolor, rutrum eu mauris nec, congue feugiat purus. Fusce tempor, neque vitae bibendum imperdiet, dolor ipsum condimentum urna, et egestas quam tortor in ex. Aliquam velit magna, commodo hendrerit sagittis sed, feugiat eget erat. Nunc quis ullamcorper velit, eu consequat augue. Nam arcu mauris, condimentum sit amet magna in, finibus scelerisque nunc. Mauris erat est, hendrerit ac accumsan eget, facilisis ut nisl. Quisque dignissim arcu quis diam tincidunt tristique. Sed rhoncus nisl non enim fermentum, a lobortis dolor consectetur. + +Sed eget condimentum ligula. Vestibulum vitae cursus eros. Donec elementum sapien magna, posuere iaculis sem ultrices lobortis. Morbi eu bibendum lectus. Suspendisse ante eros, ullamcorper ac viverra eget, pellentesque sed sapien. Duis sit amet tincidunt dui, vitae lobortis purus. Sed venenatis tincidunt volutpat. Vivamus a nisl ac elit consectetur semper ut eu libero. Proin id cursus ex. In hac habitasse platea dictumst. Aenean sed nisi vitae odio venenatis pulvinar vitae ac risus. Sed varius magna ut erat luctus vehicula. + +Nunc non ex eget purus blandit faucibus at quis velit. Donec quis mi vestibulum, facilisis nisi quis, tincidunt turpis. Sed bibendum metus sed consectetur mollis. Maecenas fermentum, erat finibus pulvinar lacinia, ex risus dictum sem, ut vestibulum augue diam vel diam. Donec ac massa non nibh pretium laoreet eget in orci. Nulla placerat eleifend mi, pretium vestibulum diam condimentum vitae. Nunc odio turpis, feugiat vitae turpis eget, pellentesque commodo turpis. Etiam sapien purus, consequat nec mi eget, consectetur efficitur neque. Phasellus porttitor sapien sit amet nunc semper, vel bibendum nibh finibus. Ut ac imperdiet ex, eu congue felis. In posuere nisi felis. Mauris tempus pretium mauris, ac viverra nunc hendrerit id. Sed fermentum nec sem ac pulvinar. Integer dictum velit eget congue venenatis. + +Cras eros dolor, venenatis ac dictum sed, dignissim nec sem. Curabitur tempor erat quis pretium interdum. Nunc vestibulum justo nisi, sit amet sagittis tellus consequat vel. Donec pharetra nunc vitae consequat eleifend. Quisque ut mauris quis nunc volutpat consectetur. Nam a suscipit ligula, at gravida libero. Vestibulum blandit, tellus sed bibendum volutpat, libero tortor convallis nisl, sit amet placerat lorem dolor nec libero. Integer blandit libero elit, ut congue ex euismod congue. Nulla sodales justo id eros condimentum faucibus. + +Proin quam ante, hendrerit at bibendum eu, pharetra at lectus. Proin finibus arcu id nisl aliquam dapibus. Fusce a suscipit nisl. Ut placerat ultrices nibh nec efficitur. Vestibulum vitae interdum magna. Donec lobortis finibus risus, et luctus ipsum efficitur euismod. Quisque dictum diam et venenatis euismod. Cras dictum molestie aliquet. Vestibulum imperdiet quam eget diam malesuada, quis pulvinar odio dignissim. Curabitur sapien elit, iaculis vitae justo eget, pretium malesuada elit. Donec gravida molestie tincidunt. Nullam at commodo ipsum. Sed nisi erat, tincidunt ultricies interdum consequat, pretium mollis ipsum. Vivamus in erat consectetur, sodales nibh at, porta nunc. Mauris semper, dui vitae condimentum tempus, mauris justo volutpat urna, ac ullamcorper tortor dolor in sem. + +Aenean leo ligula, egestas at vestibulum ac, porta vel mauris. Curabitur at lorem non mauris fringilla venenatis vel eu arcu. Donec posuere eleifend diam. Duis aliquet justo lacus, eu egestas erat eleifend sed. Sed non cursus urna. Sed pretium purus id blandit varius. Mauris turpis mi, lobortis eu tellus sit amet, maximus venenatis felis. Proin non varius enim, aliquet finibus velit. Praesent posuere pharetra ipsum eget varius. Phasellus non fermentum magna, ut iaculis augue. Praesent ut nisl nunc. + +Sed ligula tellus, interdum at sapien ut, dictum pellentesque nisl. Duis fringilla, sem nec elementum dapibus, nisl tellus maximus velit, eu varius sem nisl feugiat eros. Maecenas quis viverra lacus. Nulla nec commodo ex, nec placerat enim. Curabitur ultrices sagittis fringilla. Vestibulum sit amet enim sagittis, condimentum nisl sit amet, pulvinar orci. Ut at erat finibus erat bibendum convallis. Quisque euismod magna eget leo facilisis hendrerit. Cras venenatis, nisi quis sollicitudin volutpat, metus enim vestibulum nunc, at mollis leo leo nec est. Fusce fermentum tristique feugiat. Suspendisse sem est, condimentum ut ante at, egestas convallis ante. Vestibulum dictum, ex nec imperdiet laoreet, magna est ullamcorper augue, vel molestie quam odio vel ante. Donec dui dui, posuere a neque ac, condimentum vehicula magna. Curabitur suscipit, risus vitae bibendum posuere, mauris lorem viverra est, at tristique arcu quam quis leo. Donec sed posuere neque. Suspendisse iaculis aliquet condimentum. + +Morbi tempus condimentum diam eget bibendum. Aliquam varius magna quis lectus interdum, in elementum ligula tempor. Morbi vitae lorem sapien. Morbi luctus consectetur eros non aliquet. Pellentesque vestibulum sem sed ante accumsan faucibus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam ac suscipit justo. Nunc efficitur lectus eu arcu venenatis, vel accumsan ex suscipit. Vestibulum egestas ultricies tellus, et interdum enim pretium eu. Aenean rutrum est tincidunt rhoncus molestie. Phasellus hendrerit tellus et laoreet varius. Integer efficitur felis magna, nec sollicitudin arcu sollicitudin in. Curabitur non feugiat odio. Mauris nisi odio, luctus quis dolor sed, tristique luctus ex. Nullam libero enim, facilisis ut venenatis et, vulputate sed purus. + +Mauris a odio ut leo porttitor ultrices a et ex. Pellentesque vestibulum lacinia faucibus. In pellentesque eget augue at feugiat. Integer finibus augue dolor, in luctus lorem rutrum vitae. Donec pharetra lectus ac purus sollicitudin, vel tristique mauris pretium. Cras porttitor mi eu lectus consequat, a ultrices felis venenatis. In condimentum turpis in velit mattis laoreet. Aliquam sed mauris id nulla ultrices convallis vel vel velit. Suspendisse ut arcu finibus justo fermentum fringilla. Etiam in malesuada nisl. In hac habitasse platea dictumst. Duis a est lacinia, pretium dui eget, condimentum turpis. Integer ac placerat augue. Donec et eros felis. + +Integer cursus magna id quam sagittis consectetur. Aliquam erat volutpat. Quisque ullamcorper nisl nec massa dapibus facilisis vitae at nunc. Donec laoreet, libero in elementum tempus, enim odio porttitor felis, venenatis fermentum augue velit eu urna. Ut at ullamcorper enim. In molestie, velit et blandit maximus, erat nunc laoreet quam, vel finibus est mauris non sapien. Donec et dictum lorem. Nunc vestibulum, dolor eget tempus maximus, elit eros aliquam velit, vitae mattis mauris lectus ac tortor. Donec rutrum justo orci, id dictum metus vestibulum a. Etiam bibendum ipsum convallis, lacinia turpis vitae, dictum tortor. In velit dolor, scelerisque sed molestie nec, volutpat a turpis. Cras posuere commodo erat ut gravida. Quisque ante ipsum, volutpat a tellus non, dignissim ornare elit. Pellentesque sed porttitor dui, luctus rhoncus purus. In vitae ante pulvinar, consectetur orci quis, tempus velit. Curabitur tempus ligula id sapien ornare rhoncus. + +Maecenas eu nibh et elit accumsan blandit a at erat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam commodo feugiat condimentum. In non ante ut mauris eleifend lobortis. Phasellus eleifend vitae metus et semper. Nam sit amet rhoncus diam. Nunc molestie libero sed erat volutpat consequat. + +Aenean eget tristique odio. Vivamus quam tellus, dignissim sed faucibus sed, sagittis ut elit. Maecenas in ullamcorper sem. In at ipsum accumsan lectus vestibulum commodo nec non leo. Aliquam at suscipit felis. Nunc non egestas tortor. Donec sit amet eleifend eros. + +Sed eleifend nisi velit, in egestas erat condimentum eu. Pellentesque a tincidunt urna. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vestibulum dapibus mauris vitae elit auctor, id venenatis sem consectetur. Vivamus non leo pulvinar, blandit libero ut, vehicula arcu. Nullam elementum ex enim, at mattis massa pharetra eu. Nunc nulla magna, lobortis a magna sit amet, laoreet fermentum justo. Curabitur aliquam sollicitudin posuere. Aenean semper porta dictum. Mauris accumsan non nisi nec faucibus augue. diff --git a/powertools-e2e-tests/src/test/resources/log4j2.xml b/powertools-e2e-tests/src/test/resources/log4j2.xml new file mode 100644 index 000000000..12bd0b59d --- /dev/null +++ b/powertools-e2e-tests/src/test/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration status="INFO"> + <Appenders> + <Console name="Console" target="SYSTEM_OUT"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" /> + </Console> + </Appenders> + <Loggers> + <Logger name="software.amazon.lambda.powertools" level="debug" additivity="false"> + <AppenderRef ref="Console"/> + </Logger> + <Root level="info"> + <AppenderRef ref="Console" /> + </Root> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-e2e-tests/src/test/resources/validation/invalid_alb_in_event.json b/powertools-e2e-tests/src/test/resources/validation/invalid_alb_in_event.json new file mode 100644 index 000000000..ebad834d8 --- /dev/null +++ b/powertools-e2e-tests/src/test/resources/validation/invalid_alb_in_event.json @@ -0,0 +1,28 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a" + } + }, + "httpMethod": "POST", + "path": "/path/to/resource", + "queryStringParameters": { + "query": "1234ABCD" + }, + "headers": { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "accept-encoding": "gzip", + "accept-language": "en-US,en;q=0.9", + "connection": "keep-alive", + "host": "lambda-alb-123578498.us-east-2.elb.amazonaws.com", + "upgrade-insecure-requests": "1", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", + "x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476", + "x-forwarded-for": "72.12.164.125", + "x-forwarded-port": "80", + "x-forwarded-proto": "http", + "x-imforwards": "20" + }, + "body": "{\"message\": \"Lambda rocks\"}", + "isBase64Encoded": true +} \ No newline at end of file diff --git a/powertools-e2e-tests/src/test/resources/validation/invalid_alb_out_event.json b/powertools-e2e-tests/src/test/resources/validation/invalid_alb_out_event.json new file mode 100644 index 000000000..1b1961063 --- /dev/null +++ b/powertools-e2e-tests/src/test/resources/validation/invalid_alb_out_event.json @@ -0,0 +1,28 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a" + } + }, + "httpMethod": "POST", + "path": "/path/to/resource", + "queryStringParameters": { + "query": "1234ABCD" + }, + "headers": { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "accept-encoding": "gzip", + "accept-language": "en-US,en;q=0.9", + "connection": "keep-alive", + "host": "lambda-alb-123578498.us-east-2.elb.amazonaws.com", + "upgrade-insecure-requests": "1", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", + "x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476", + "x-forwarded-for": "72.12.164.125", + "x-forwarded-port": "80", + "x-forwarded-proto": "http", + "x-imforwards": "20" + }, + "body": "{\"price\": 50000}", + "isBase64Encoded": true +} \ No newline at end of file diff --git a/powertools-e2e-tests/src/test/resources/validation/invalid_api_gw_in_event.json b/powertools-e2e-tests/src/test/resources/validation/invalid_api_gw_in_event.json new file mode 100644 index 000000000..014ef9f05 --- /dev/null +++ b/powertools-e2e-tests/src/test/resources/validation/invalid_api_gw_in_event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"Lambda rocks\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} \ No newline at end of file diff --git a/powertools-e2e-tests/src/test/resources/validation/invalid_api_gw_out_event.json b/powertools-e2e-tests/src/test/resources/validation/invalid_api_gw_out_event.json new file mode 100644 index 000000000..b7ef1780c --- /dev/null +++ b/powertools-e2e-tests/src/test/resources/validation/invalid_api_gw_out_event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"price\": 50000}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} \ No newline at end of file diff --git a/powertools-e2e-tests/src/test/resources/validation/valid_alb_in_out_event.json b/powertools-e2e-tests/src/test/resources/validation/valid_alb_in_out_event.json new file mode 100644 index 000000000..35560f109 --- /dev/null +++ b/powertools-e2e-tests/src/test/resources/validation/valid_alb_in_out_event.json @@ -0,0 +1,28 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a" + } + }, + "httpMethod": "POST", + "path": "/path/to/resource", + "queryStringParameters": { + "query": "1234ABCD" + }, + "headers": { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "accept-encoding": "gzip", + "accept-language": "en-US,en;q=0.9", + "connection": "keep-alive", + "host": "lambda-alb-123578498.us-east-2.elb.amazonaws.com", + "upgrade-insecure-requests": "1", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", + "x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476", + "x-forwarded-for": "72.12.164.125", + "x-forwarded-port": "80", + "x-forwarded-proto": "http", + "x-imforwards": "20" + }, + "body": "{\"price\": 150}", + "isBase64Encoded": true +} \ No newline at end of file diff --git a/powertools-e2e-tests/src/test/resources/validation/valid_api_gw_in_out_event.json b/powertools-e2e-tests/src/test/resources/validation/valid_api_gw_in_out_event.json new file mode 100644 index 000000000..8cb8ea27a --- /dev/null +++ b/powertools-e2e-tests/src/test/resources/validation/valid_api_gw_in_out_event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"price\": 150}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} \ No newline at end of file diff --git a/powertools-idempotency/README.md b/powertools-idempotency/README.md new file mode 100644 index 000000000..141021e1d --- /dev/null +++ b/powertools-idempotency/README.md @@ -0,0 +1,14 @@ +## Idempotency +Refer to the [documentation](https://docs.powertools.aws.dev/lambda-java/utilities/idempotency/) for details on how to use this module in your Lambda function. + +### Contributing +This module provides a persistence layer with a built-in store using DynamoDB. +To unit test it, we use [DynamoDB Local](https://docs.aws.amazon.com/fr_fr/amazondynamodb/latest/developerguide/DynamoDBLocal.html) which depends on sqlite. +You may encounter the following issue on Apple M1 chips: +``` +com.almworks.sqlite4java.SQLiteException: [-91] cannot load library: java.lang.UnsatisfiedLinkError: native-libs/libsqlite4java-osx-1.0.392.dylib: dlopen(native-libs/libsqlite4java-osx-1.0.392.dylib, 1): no suitable image found. Did find: +native-libs/libsqlite4java-osx-1.0.392.dylib: no matching architecture in universal wrapper +``` + +In such case, try with another JDK. See [stackoverflow](https://stackoverflow.com/questions/66635424/dynamodb-local-setup-on-m1-apple-silicon-mac) and this [issue](https://github.com/aws-samples/aws-dynamodb-examples/issues/22) for more info. +We'll update the dependencies as soon as it will be solved. \ No newline at end of file diff --git a/powertools-idempotency/pom.xml b/powertools-idempotency/pom.xml new file mode 100644 index 000000000..cbe2384ba --- /dev/null +++ b/powertools-idempotency/pom.xml @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>powertools-idempotency</artifactId> + <packaging>pom</packaging> + + <name>Powertools for AWS Lambda (Java) library Idempotency</name> + <description>Utility to implement idempotency of Lambda functions</description> + + + <modules> + <module>powertools-idempotency-core</module> + <module>powertools-idempotency-dynamodb</module> + </modules> + + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + </dependency> + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit-pioneer</groupId> + <artifactId>junit-pioneer</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-tests</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <systemPropertyVariables> + <sqlite4java.library.path>${project.build.directory}/native-libs</sqlite4java.library.path> + </systemPropertyVariables> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>copy</id> + <phase>test-compile</phase> + <goals> + <goal>copy-dependencies</goal> + </goals> + <configuration> + <includeScope>test</includeScope> + <includeTypes>so,dll,dylib</includeTypes> + <outputDirectory>${project.build.directory}/native-libs</outputDirectory> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-idempotency/powertools-idempotency-core/pom.xml b/powertools-idempotency/powertools-idempotency-core/pom.xml new file mode 100644 index 000000000..4cba1956f --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/pom.xml @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>powertools-idempotency-core</artifactId> + <packaging>jar</packaging> + + <name>Powertools for AWS Lambda (Java) library Idempotency - Core</name> + <description> + Idempotency module common implementation + </description> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-serialization</artifactId> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>generate-graalvm-files</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-idempotency-core,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-idempotency-core</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/Constants.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/Constants.java new file mode 100644 index 000000000..0d19fa7a9 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/Constants.java @@ -0,0 +1,19 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency; + +public class Constants { + public static final String IDEMPOTENCY_DISABLED_ENV = "POWERTOOLS_IDEMPOTENCY_DISABLED"; +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/Idempotency.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/Idempotency.java new file mode 100644 index 000000000..4f73edea0 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/Idempotency.java @@ -0,0 +1,328 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency; + +import java.util.function.Function; +import java.util.function.Supplier; + +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; + +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyConfigurationException; +import software.amazon.lambda.powertools.idempotency.internal.IdempotencyHandler; +import software.amazon.lambda.powertools.idempotency.persistence.BasePersistenceStore; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +/** + * Idempotency provides both a configuration and a functional API for implementing idempotent workloads. + * + * <p>This class is thread-safe. All operations delegate to the underlying persistence store + * which handles concurrent access safely.</p> + * + * <h2>Configuration</h2> + * <p>Configure the persistence layer and idempotency settings before your handler executes (e.g. in constructor):</p> + * <pre>{@code + * Idempotency.config() + * .withPersistenceStore(persistenceStore) + * .withConfig(idempotencyConfig) + * .configure(); + * }</pre> + * + * <h2>Functional API</h2> + * <p>Make methods idempotent without AspectJ annotations. Generic return types (e.g., {@code Map<String, Object>}, + * {@code List<Product>}) are supported via Jackson {@link TypeReference}.</p> + * + * <p><strong>Important:</strong> Always call {@link #registerLambdaContext(Context)} + * at the start of your handler to enable proper timeout handling.</p> + * + * <p>Example usage with Function (single parameter):</p> + * <pre>{@code + * public Basket handleRequest(Product input, Context context) { + * Idempotency.registerLambdaContext(context); + * return Idempotency.makeIdempotent(this::processProduct, input, Basket.class); + * } + * + * private Basket processProduct(Product product) { + * // business logic + * } + * }</pre> + * + * <p>Example usage with Supplier (multi-parameter methods):</p> + * <pre>{@code + * public String handleRequest(SQSEvent event, Context context) { + * Idempotency.registerLambdaContext(context); + * return Idempotency.makeIdempotent( + * event.getRecords().get(0).getBody(), + * () -> processPayment(orderId, amount, currency), + * String.class + * ); + * } + * }</pre> + * + * <p>When different methods use the same payload as idempotency key, use explicit function names + * to differentiate between them:</p> + * <pre>{@code + * // Different methods, same payload + * Idempotency.makeIdempotent("processPayment", orderId, + * () -> processPayment(orderId), String.class); + * + * Idempotency.makeIdempotent("refundPayment", orderId, + * () -> refundPayment(orderId), String.class); + * }</pre> + * + * @see #config() + * @see #registerLambdaContext(Context) + * @see #makeIdempotent(Object, Supplier, Class) + * @see #makeIdempotent(String, Object, Supplier, Class) + * @see #makeIdempotent(Function, Object, Class) + * @see #makeIdempotent(Object, Supplier, TypeReference) + * @see #makeIdempotent(String, Object, Supplier, TypeReference) + * @see #makeIdempotent(Function, Object, TypeReference) + */ +public final class Idempotency { + private static final String DEFAULT_FUNCTION_NAME = "function"; + + private IdempotencyConfig config; + private BasePersistenceStore persistenceStore; + + private Idempotency() { + } + + public static Idempotency getInstance() { + return Holder.instance; + } + + /** + * Can be used in a method which is not the handler to capture the Lambda context, + * to calculate the remaining time before the invocation times out. + * + * @param lambdaContext + */ + public static void registerLambdaContext(Context lambdaContext) { + getInstance().getConfig().setLambdaContext(lambdaContext); + } + + /** + * Acts like a builder that can be used to configure {@link Idempotency} + * + * @return a new instance of {@link Config} + */ + public static Config config() { + return new Config(); + } + + public IdempotencyConfig getConfig() { + return config; + } + + private void setConfig(IdempotencyConfig config) { + this.config = config; + } + + public BasePersistenceStore getPersistenceStore() { + if (persistenceStore == null) { + throw new IllegalStateException("Persistence Store is null, did you call 'configure()'?"); + } + return persistenceStore; + } + + private void setPersistenceStore(BasePersistenceStore persistenceStore) { + this.persistenceStore = persistenceStore; + } + + private static final class Holder { + private static final Idempotency instance = new Idempotency(); + } + + public static class Config { + + private IdempotencyConfig config; + private BasePersistenceStore store; + + /** + * Use this method after configuring persistence layer (mandatory) and idem potency configuration (optional) + */ + public void configure() { + if (store == null) { + throw new IllegalStateException( + "Persistence Layer is null, configure one with 'withPersistenceStore()'"); + } + if (config == null) { + config = IdempotencyConfig.builder().build(); + } + Idempotency.getInstance().setConfig(config); + Idempotency.getInstance().setPersistenceStore(store); + } + + public Config withPersistenceStore(BasePersistenceStore persistenceStore) { + this.store = persistenceStore; + return this; + } + + public Config withConfig(IdempotencyConfig config) { + this.config = config; + return this; + } + } + + // Functional API methods + + /** + * Makes a function idempotent using the provided idempotency key. + * Uses a default function name for namespacing the idempotency key. + * + * <p>This method is thread-safe and can be used in parallel processing scenarios + * such as batch processors.</p> + * + * <p>This method is suitable for making methods idempotent that have more than one parameter. + * For simple single-parameter methods, {@link #makeIdempotent(Function, Object, Class)} is more intuitive.</p> + * + * <p><strong>Note:</strong> If you need to call different functions with the same payload, + * use {@link #makeIdempotent(String, Object, Supplier, Class)} to specify distinct function names. + * This ensures each function has its own idempotency scope.</p> + * + * @param idempotencyKey the key used for idempotency (will be converted to JSON) + * @param function the function to make idempotent + * @param returnType the class of the return type for deserialization + * @param <T> the return type of the function + * @return the result of the function execution (either fresh or cached) + */ + public static <T> T makeIdempotent(Object idempotencyKey, Supplier<T> function, Class<T> returnType) { + return makeIdempotent(DEFAULT_FUNCTION_NAME, idempotencyKey, function, returnType); + } + + /** + * Makes a function idempotent using the provided function name and idempotency key. + * + * <p>This method is thread-safe and can be used in parallel processing scenarios + * such as batch processors.</p> + * + * @param functionName the name of the function (used for persistence store configuration) + * @param idempotencyKey the key used for idempotency (will be converted to JSON) + * @param function the function to make idempotent + * @param returnType the class of the return type for deserialization + * @param <T> the return type of the function + * @return the result of the function execution (either fresh or cached) + */ + public static <T> T makeIdempotent(String functionName, Object idempotencyKey, Supplier<T> function, + Class<T> returnType) { + return makeIdempotent(functionName, idempotencyKey, function, JsonConfig.toTypeReference(returnType)); + } + + /** + * Makes a function with one parameter idempotent. + * The parameter is used as the idempotency key. + * + * <p>For functions with more than one parameter, use {@link #makeIdempotent(Object, Supplier, Class)} instead.</p> + * + * <p><strong>Note:</strong> If you need to call different functions with the same payload, + * use {@link #makeIdempotent(String, Object, Supplier, Class)} to specify distinct function names. + * This ensures each function has its own idempotency scope.</p> + * + * @param function the function to make idempotent (method reference) + * @param arg the argument to pass to the function (also used as idempotency key) + * @param returnType the class of the return type for deserialization + * @param <T> the argument type + * @param <R> the return type + * @return the result of the function execution (either fresh or cached) + */ + public static <T, R> R makeIdempotent(Function<T, R> function, T arg, Class<R> returnType) { + return makeIdempotent(DEFAULT_FUNCTION_NAME, arg, () -> function.apply(arg), returnType); + } + + /** + * Makes a function idempotent using the provided idempotency key with support for generic return types. + * Uses a default function name for namespacing the idempotency key. + * + * <p>Use this method when the return type contains generics (e.g., {@code Map<String, Basket>}).</p> + * + * <p>Example usage:</p> + * <pre>{@code + * Map<String, Basket> result = Idempotency.makeIdempotent( + * payload, + * () -> processBaskets(), + * new TypeReference<Map<String, Basket>>() {} + * ); + * }</pre> + * + * @param idempotencyKey the key used for idempotency (will be converted to JSON) + * @param function the function to make idempotent + * @param typeRef the TypeReference for deserialization of generic types + * @param <T> the return type of the function + * @return the result of the function execution (either fresh or cached) + */ + public static <T> T makeIdempotent(Object idempotencyKey, Supplier<T> function, TypeReference<T> typeRef) { + return makeIdempotent(DEFAULT_FUNCTION_NAME, idempotencyKey, function, typeRef); + } + + /** + * Makes a function idempotent using the provided function name and idempotency key with support for generic return types. + * + * @param functionName the name of the function (used for persistence store configuration) + * @param idempotencyKey the key used for idempotency (will be converted to JSON) + * @param function the function to make idempotent + * @param typeRef the TypeReference for deserialization of generic types + * @param <T> the return type of the function + * @return the result of the function execution (either fresh or cached) + */ + @SuppressWarnings("unchecked") + public static <T> T makeIdempotent(String functionName, Object idempotencyKey, Supplier<T> function, + TypeReference<T> typeRef) { + try { + JsonNode payload = JsonConfig.get().getObjectMapper().valueToTree(idempotencyKey); + Context lambdaContext = Idempotency.getInstance().getConfig().getLambdaContext(); + + IdempotencyHandler handler = new IdempotencyHandler( + function::get, + typeRef, + functionName, + payload, + lambdaContext); + + Object result = handler.handle(); + return (T) result; + } catch (RuntimeException e) { + throw e; + } catch (Throwable e) { + throw new IdempotencyConfigurationException("Idempotency operation failed: " + e.getMessage()); + } + } + + /** + * Makes a function with one parameter idempotent with support for generic return types. + * The parameter is used as the idempotency key. + * + * <p>Example usage:</p> + * <pre>{@code + * Map<String, Basket> result = Idempotency.makeIdempotent( + * this::processProduct, + * product, + * new TypeReference<Map<String, Basket>>() {} + * ); + * }</pre> + * + * @param function the function to make idempotent (method reference) + * @param arg the argument to pass to the function (also used as idempotency key) + * @param typeRef the TypeReference for deserialization of generic types + * @param <T> the argument type + * @param <R> the return type + * @return the result of the function execution (either fresh or cached) + */ + public static <T, R> R makeIdempotent(Function<T, R> function, T arg, TypeReference<R> typeRef) { + return makeIdempotent(DEFAULT_FUNCTION_NAME, arg, () -> function.apply(arg), typeRef); + } + +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/IdempotencyConfig.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/IdempotencyConfig.java new file mode 100644 index 000000000..0d9504483 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/IdempotencyConfig.java @@ -0,0 +1,278 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency; + +import java.time.Duration; +import java.util.function.BiFunction; + +import com.amazonaws.services.lambda.runtime.Context; + +import software.amazon.lambda.powertools.idempotency.internal.cache.LRUCache; +import software.amazon.lambda.powertools.idempotency.persistence.DataRecord; + +/** + * Configuration of the idempotency feature. Use the {@link Builder} to create an instance. + */ +public final class IdempotencyConfig { + private final int localCacheMaxItems; + private final boolean useLocalCache; + private final long expirationInSeconds; + private final String eventKeyJMESPath; + private final String payloadValidationJMESPath; + private final boolean throwOnNoIdempotencyKey; + private final String hashFunction; + private final BiFunction<Object, DataRecord, Object> responseHook; + private final InheritableThreadLocal<Context> lambdaContext = new InheritableThreadLocal<>(); + + private IdempotencyConfig(String eventKeyJMESPath, String payloadValidationJMESPath, + boolean throwOnNoIdempotencyKey, boolean useLocalCache, int localCacheMaxItems, + long expirationInSeconds, String hashFunction, BiFunction<Object, DataRecord, Object> responseHook) { + this.localCacheMaxItems = localCacheMaxItems; + this.useLocalCache = useLocalCache; + this.expirationInSeconds = expirationInSeconds; + this.eventKeyJMESPath = eventKeyJMESPath; + this.payloadValidationJMESPath = payloadValidationJMESPath; + this.throwOnNoIdempotencyKey = throwOnNoIdempotencyKey; + this.hashFunction = hashFunction; + this.responseHook = responseHook; + } + + /** + * Create a builder that can be used to configure and create a {@link IdempotencyConfig}. + * + * @return a new instance of {@link Builder} + */ + public static Builder builder() { + return new Builder(); + } + + public int getLocalCacheMaxItems() { + return localCacheMaxItems; + } + + public boolean useLocalCache() { + return useLocalCache; + } + + public long getExpirationInSeconds() { + return expirationInSeconds; + } + + public String getEventKeyJMESPath() { + return eventKeyJMESPath; + } + + public String getPayloadValidationJMESPath() { + return payloadValidationJMESPath; + } + + public boolean throwOnNoIdempotencyKey() { + return throwOnNoIdempotencyKey; + } + + public String getHashFunction() { + return hashFunction; + } + + public Context getLambdaContext() { + return lambdaContext.get(); + } + + public void setLambdaContext(Context lambdaContext) { + this.lambdaContext.set(lambdaContext); + } + + public BiFunction<Object, DataRecord, Object> getResponseHook() { + return responseHook; + } + + public static class Builder { + + private int localCacheMaxItems = 256; + private boolean useLocalCache = false; + private long expirationInSeconds = 60 * 60L; // 1 hour + private String eventKeyJMESPath; + private String payloadValidationJMESPath; + private boolean throwOnNoIdempotencyKey = false; + private String hashFunction = "MD5"; + private BiFunction<Object, DataRecord, Object> responseHook; + + /** + * Initialize and return an instance of {@link IdempotencyConfig}.<br> + * Example:<br> + * + * <pre> + * IdempotencyConfig.builder().withUseLocalCache().build(); + * </pre> + * + * This instance must then be passed to the {@link Idempotency.Config}: + * + * <pre> + * Idempotency.config().withConfig(config).configure(); + * </pre> + * + * @return an instance of {@link IdempotencyConfig}. + */ + public IdempotencyConfig build() { + return new IdempotencyConfig( + eventKeyJMESPath, + payloadValidationJMESPath, + throwOnNoIdempotencyKey, + useLocalCache, + localCacheMaxItems, + expirationInSeconds, + hashFunction, + responseHook); + } + + /** + * A JMESPath expression to extract the idempotency key from the event record. <br> + * See <a href="https://jmespath.org/">https://jmespath.org/</a> for more details.<br> + * Common paths are: + * <ul> + * <li><code>powertools_json(body)</code> for APIGatewayProxyRequestEvent and APIGatewayV2HTTPEvent</li> + * <li><code>Records[*].powertools_json(body)</code> for SQSEvent</li> + * <li><code>Records[0].Sns.Message | powertools_json(@)</code> for SNSEvent</li> + * <li><code>detail</code> for ScheduledEvent (EventBridge / CloudWatch events)</li> + * <li><code>Records[*].kinesis.powertools_json(powertools_base64(data))</code> for KinesisEvent</li> + * <li><code>Records[*].powertools_json(powertools_base64(data))</code> for KinesisFirehoseEvent</li> + * <li>...</li> + * </ul> + * + * @param eventKeyJMESPath + * path of the key in the Lambda event + * @return the instance of the builder (to chain operations) + */ + public Builder withEventKeyJMESPath(String eventKeyJMESPath) { + this.eventKeyJMESPath = eventKeyJMESPath; + return this; + } + + /** + * Set the maximum number of items to store in local cache, by default 256 + * + * @param localCacheMaxItems + * maximum number of items to store in local cache + * @return the instance of the builder (to chain operations) + */ + public Builder withLocalCacheMaxItems(int localCacheMaxItems) { + this.localCacheMaxItems = localCacheMaxItems; + return this; + } + + /** + * Whether to locally cache idempotency results, by default false + * + * @param useLocalCache + * boolean that indicate if a local cache must be used in addition to the persistence store. + * If set to true, will use the {@link LRUCache} + * @return the instance of the builder (to chain operations) + */ + public Builder withUseLocalCache(boolean useLocalCache) { + this.useLocalCache = useLocalCache; + return this; + } + + /** + * The number of seconds to wait before a record is expired + * + * @param expiration + * expiration of the record in the store + * @return the instance of the builder (to chain operations) + */ + public Builder withExpiration(Duration expiration) { + this.expirationInSeconds = expiration.getSeconds(); + return this; + } + + /** + * A JMESPath expression to extract the payload to be validated from the event record. <br/> + * See <a href="https://jmespath.org/">https://jmespath.org/</a> for more details. + * + * @param payloadValidationJMESPath + * JMES Path of a part of the payload to be used for validation + * @return the instance of the builder (to chain operations) + */ + public Builder withPayloadValidationJMESPath(String payloadValidationJMESPath) { + this.payloadValidationJMESPath = payloadValidationJMESPath; + return this; + } + + /** + * Whether to throw an exception if no idempotency key was found in the request, by default false + * + * @param throwOnNoIdempotencyKey + * boolean to indicate if we must throw an Exception when idempotency key could not be found in the + * payload. + * @return the instance of the builder (to chain operations) + */ + public Builder withThrowOnNoIdempotencyKey(boolean throwOnNoIdempotencyKey) { + this.throwOnNoIdempotencyKey = throwOnNoIdempotencyKey; + return this; + } + + /** + * Throw an exception if no idempotency key was found in the request. + * Shortcut for {@link #withThrowOnNoIdempotencyKey(boolean)}, forced as true + * + * @return the instance of the builder (to chain operations) + */ + public Builder withThrowOnNoIdempotencyKey() { + return withThrowOnNoIdempotencyKey(true); + } + + /** + * Function to use for calculating hashes, by default MD5. + * + * @param hashFunction + * Can be any algorithm supported by {@link java.security.MessageDigest}, most commons are + * <ul> + * <li>MD5</li> + * <li>SHA-1</li> + * <li>SHA-256</li> + * </ul> + * @return the instance of the builder (to chain operations) + */ + public Builder withHashFunction(String hashFunction) { + this.hashFunction = hashFunction; + return this; + } + + /** + * Response hook that will be called for each idempotent response. This hook will receive the de-serialized + * response data from the persistence store as first argument and the original DataRecord from the persistence + * store as second argument. + * + * Usage: + * + * <pre> + * IdempotencyConfig.builder().withResponseHook((responseData, dataRecord) -> { + * // do something with the response data, for example: + * if(responseData instanceof APIGatewayProxyRequestEvent) { + * ((APIGatewayProxyRequestEvent) responseData).setHeaders(Map.of("x-idempotency-response", "true") + * } + * return responseData; + * }) + * </pre> + * + * @param responseHook + * @return + */ + public Builder withResponseHook(BiFunction<Object, DataRecord, Object> responseHook) { + this.responseHook = responseHook; + return this; + } + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/IdempotencyKey.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/IdempotencyKey.java new file mode 100644 index 000000000..4f63b10f5 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/IdempotencyKey.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @IdempotencyKey is used to signal that a method parameter is used as a key for idempotency.<br/> + * Must be used in conjunction with the @Idempotency annotation.<br/> + * Example:<br/> + * <pre> + * @Idempotent + * private MyObject subMethod(String param1, @IdempotencyKey String param2) { + * // ... + * return something; + * } + * </pre> + * Note: This annotation is not needed when the method only has one parameter. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface IdempotencyKey { +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/Idempotent.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/Idempotent.java new file mode 100644 index 000000000..d08874492 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/Idempotent.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency; + +import com.amazonaws.services.lambda.runtime.Context; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @Idempotent is used to signal that the annotated method is idempotent:<br/> + * Calling this method one or multiple times with the same parameter will always return the same result.<br/> + * This annotation can be placed on the + * {@link com.amazonaws.services.lambda.runtime.RequestHandler#handleRequest(Object, Context)} + * method of a Lambda function:<br/> + * <pre> + * @Idempotent + * public String handleRequest(String event, Context ctx) { + * // ... + * return something; + * } + * </pre> + * <br/> + * It can also be placed on another method. In that case you may need to use the @{@link IdempotencyKey} + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Idempotent { + +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyAlreadyInProgressException.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyAlreadyInProgressException.java new file mode 100644 index 000000000..dc87f422b --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyAlreadyInProgressException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.exceptions; + +/** + * This exception is thrown when the same payload is sent + * while the previous one was not yet fully stored in the persistence layer (marked as COMPLETED). + */ +public class IdempotencyAlreadyInProgressException extends RuntimeException { + private static final long serialVersionUID = 7229475093418832265L; + + public IdempotencyAlreadyInProgressException(String msg) { + super(msg); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyConfigurationException.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyConfigurationException.java new file mode 100644 index 000000000..9e85f4b5f --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyConfigurationException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.exceptions; + +/** + * Exception thrown when Idempotency is not well configured: + * <ul> + * <li>An annotated method does not return anything</li> + * <li>An annotated method does not have parameters or more than one without + * the {@link software.amazon.lambda.powertools.idempotency.IdempotencyKey} annotation</li> + * </ul> + */ +public class IdempotencyConfigurationException extends RuntimeException { + private static final long serialVersionUID = 560587720373305487L; + + public IdempotencyConfigurationException(String message) { + super(message); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyInconsistentStateException.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyInconsistentStateException.java new file mode 100644 index 000000000..e41e30e84 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyInconsistentStateException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.exceptions; + +/** + * IdempotencyInconsistentStateException can happen under rare but expected cases + * when persistent state changes in the small-time between put & get requests. + */ +public class IdempotencyInconsistentStateException extends RuntimeException { + private static final long serialVersionUID = -4293951999802300672L; + + public IdempotencyInconsistentStateException(String msg, Exception e) { + super(msg, e); + } + + public IdempotencyInconsistentStateException(String msg) { + super(msg); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyItemAlreadyExistsException.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyItemAlreadyExistsException.java new file mode 100644 index 000000000..aed2e5ae0 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyItemAlreadyExistsException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.exceptions; + +import java.util.Optional; + +import software.amazon.lambda.powertools.idempotency.persistence.DataRecord; + +/** + * Exception thrown when trying to store an item which already exists. + */ +public class IdempotencyItemAlreadyExistsException extends RuntimeException { + private static final long serialVersionUID = 9027152772149436500L; + // transient because we don't want to accidentally dump any payloads in logs / stack traces + private transient Optional<DataRecord> dr = Optional.empty(); + + public IdempotencyItemAlreadyExistsException() { + super(); + } + + public IdempotencyItemAlreadyExistsException(String msg, Throwable e) { + super(msg, e); + } + + public IdempotencyItemAlreadyExistsException(String msg, Throwable e, DataRecord dr) { + super(msg, e); + this.dr = Optional.ofNullable(dr); + } + + public Optional<DataRecord> getDataRecord() { + return dr; + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyItemNotFoundException.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyItemNotFoundException.java new file mode 100644 index 000000000..420829363 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyItemNotFoundException.java @@ -0,0 +1,26 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.exceptions; + +/** + * Exception thrown when the item was not found in the persistence store. + */ +public class IdempotencyItemNotFoundException extends RuntimeException { + private static final long serialVersionUID = 4818288566747993032L; + + public IdempotencyItemNotFoundException(String idempotencyKey) { + super("Item with idempotency key " + idempotencyKey + " not found"); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyKeyException.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyKeyException.java new file mode 100644 index 000000000..29b8bd2ec --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyKeyException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.exceptions; + +/** + * Exception thrown only when using {@link software.amazon.lambda.powertools.idempotency.IdempotencyConfig#throwOnNoIdempotencyKey()}, + * and if a key could not be found in the event (for example when having a bad JMESPath configured) + */ +public class IdempotencyKeyException extends RuntimeException { + private static final long serialVersionUID = -8514965705001281773L; + + public IdempotencyKeyException(String message) { + super(message); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyPersistenceLayerException.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyPersistenceLayerException.java new file mode 100644 index 000000000..bfdd2d792 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyPersistenceLayerException.java @@ -0,0 +1,26 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.exceptions; + +/** + * Exception thrown when a technical error occurred with the persistence layer (eg. insertion, deletion, ... in database) + */ +public class IdempotencyPersistenceLayerException extends RuntimeException { + private static final long serialVersionUID = 6781832947434168547L; + + public IdempotencyPersistenceLayerException(String msg, Exception e) { + super(msg, e); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyValidationException.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyValidationException.java new file mode 100644 index 000000000..cdb2bb6a7 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyValidationException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.exceptions; + +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; + +/** + * Exception thrown only when using {@link IdempotencyConfig#getPayloadValidationJMESPath()} is configured + * and the payload changed between two calls (but with the same idempotency key). + */ +public class IdempotencyValidationException extends RuntimeException { + private static final long serialVersionUID = -4218652810664634761L; + + public IdempotencyValidationException() { + super(); + } + + public IdempotencyValidationException(String message) { + super(message); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/internal/IdempotencyHandler.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/internal/IdempotencyHandler.java new file mode 100644 index 000000000..d4e0d2222 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/internal/IdempotencyHandler.java @@ -0,0 +1,240 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.internal; + +import static software.amazon.lambda.powertools.idempotency.persistence.DataRecord.Status.EXPIRED; +import static software.amazon.lambda.powertools.idempotency.persistence.DataRecord.Status.INPROGRESS; + +import java.time.Instant; +import java.util.OptionalInt; +import java.util.function.BiFunction; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; + +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyAlreadyInProgressException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyInconsistentStateException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemAlreadyExistsException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemNotFoundException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyKeyException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyPersistenceLayerException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyValidationException; +import software.amazon.lambda.powertools.idempotency.persistence.BasePersistenceStore; +import software.amazon.lambda.powertools.idempotency.persistence.DataRecord; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +/** + * Internal class that will handle the Idempotency, and use the + * {@link software.amazon.lambda.powertools.idempotency.persistence.PersistenceStore} + * to store the result of previous calls. + */ +public class IdempotencyHandler { + private static final Logger LOG = LoggerFactory.getLogger(IdempotencyHandler.class); + private static final int MAX_RETRIES = 2; + + private final IdempotentFunction<?> function; + private final TypeReference<?> returnTypeRef; + private final JsonNode data; + private final BasePersistenceStore persistenceStore; + private final Context lambdaContext; + + public IdempotencyHandler(IdempotentFunction<?> function, Class<?> returnType, String functionName, + JsonNode payload, Context lambdaContext) { + this(function, JsonConfig.toTypeReference(returnType), functionName, payload, lambdaContext); + } + + public IdempotencyHandler(IdempotentFunction<?> function, TypeReference<?> returnTypeRef, String functionName, + JsonNode payload, Context lambdaContext) { + this.function = function; + this.returnTypeRef = returnTypeRef; + this.data = payload; + this.lambdaContext = lambdaContext; + persistenceStore = Idempotency.getInstance().getPersistenceStore(); + persistenceStore.configure(Idempotency.getInstance().getConfig(), functionName); + } + + /** + * Main entry point for handling idempotent execution of a function. + * + * @return function response + */ + public Object handle() throws Throwable { + // IdempotencyInconsistentStateException can happen under rare but expected cases + // when persistent state changes in the small time between put & get requests. + // In most cases we can retry successfully on this exception. + for (int i = 0; true; i++) { + try { + return processIdempotency(); + } catch (IdempotencyInconsistentStateException e) { + if (i == MAX_RETRIES) { + throw e; + } + } + } + } + + /** + * Process the function with idempotency + * + * @return function response + */ + private Object processIdempotency() throws Throwable { + try { + // We call saveInProgress first as an optimization for the most common case where no idempotent record + // already exists. If it succeeds, there's no need to call getRecord. + persistenceStore.saveInProgress(data, Instant.now(), getRemainingTimeInMillis()); + } catch (IdempotencyItemAlreadyExistsException iaee) { + // If a DataRecord is already present on the Exception we can immediately take that one instead of trying + // to fetch it first. + DataRecord dr = iaee.getDataRecord().orElseGet(this::getIdempotencyRecord); + if (dr != null) { + return handleForStatus(dr); + } + } catch (IdempotencyKeyException ike) { + throw ike; + } catch (IdempotencyValidationException ive) { + throw ive; + } catch (Exception e) { + throw new IdempotencyPersistenceLayerException( + "Failed to save in progress record to idempotency store. If you believe this is a Powertools for AWS Lambda (Java) bug, please open an issue.", + e); + } + return getFunctionResponse(); + } + + /** + * Tries to determine the remaining time available for the current lambda invocation. + * Currently, it only works if the idempotent handler decorator is used or using + * {@link Idempotency#registerLambdaContext(Context)} + * + * @return the remaining time in milliseconds or empty if the context was not provided/found + */ + private OptionalInt getRemainingTimeInMillis() { + if (lambdaContext != null) { + return OptionalInt.of(lambdaContext.getRemainingTimeInMillis()); + } else { + LOG.warn("Couldn't determine the remaining time left. Did you call registerLambdaContext on Idempotency?"); + } + return OptionalInt.empty(); + } + + /** + * Retrieve the idempotency record from the persistence layer. + * + * @return the record if available, potentially null + */ + private DataRecord getIdempotencyRecord() { + try { + return persistenceStore.getRecord(data, Instant.now()); + } catch (IdempotencyItemNotFoundException e) { + // This code path will only be triggered if the record is removed between saveInProgress and getRecord + LOG.debug("An existing idempotency record was deleted before we could fetch it"); + throw new IdempotencyInconsistentStateException("saveInProgress and getRecord return inconsistent results", + e); + } catch (IdempotencyValidationException | IdempotencyKeyException vke) { + throw vke; + } catch (Exception e) { + throw new IdempotencyPersistenceLayerException( + "Failed to get record from idempotency store. If you believe this is a Powertools for AWS Lambda (Java) bug, please open an issue.", + e); + } + } + + /** + * Take appropriate action based on data_record's status + * + * @param record + * DataRecord + * @return Function's response previously used for this idempotency key, if it has successfully executed already. + */ + private Object handleForStatus(DataRecord record) { + // This code path will only be triggered if the record becomes expired between the saveInProgress call and here + if (EXPIRED.equals(record.getStatus())) { + throw new IdempotencyInconsistentStateException("saveInProgress and getRecord return inconsistent results"); + } + + if (INPROGRESS.equals(record.getStatus())) { + if (record.getInProgressExpiryTimestamp().isPresent() + && record.getInProgressExpiryTimestamp().getAsLong() < Instant.now().toEpochMilli()) { + throw new IdempotencyInconsistentStateException( + "Item should have been expired in-progress because it already time-outed."); + } + throw new IdempotencyAlreadyInProgressException( + "Execution already in progress with idempotency key: " + record.getIdempotencyKey()); + } + + try { + LOG.debug("Response for key '{}' retrieved from idempotency store, skipping the function", + record.getIdempotencyKey()); + + final BiFunction<Object, DataRecord, Object> responseHook = Idempotency.getInstance().getConfig() + .getResponseHook(); + final Object responseData; + + if (String.class.equals(returnTypeRef.getType())) { + // Primitive String data will be returned raw and not de-serialized from JSON. + responseData = record.getResponseData(); + } else { + responseData = JsonConfig.get().getObjectMapper().readValue(record.getResponseData(), + returnTypeRef); + } + + if (responseHook != null) { + LOG.debug("Applying user-defined response hook to idempotency data before returning."); + return responseHook.apply(responseData, record); + } + + return responseData; + } catch (Exception e) { + throw new IdempotencyPersistenceLayerException( + "Unable to get function response as " + returnTypeRef.getType().getTypeName(), e); + } + } + + private Object getFunctionResponse() throws Throwable { + Object response; + try { + response = function.execute(); + } catch (Throwable handlerException) { + // We need these nested blocks to preserve function's exception in case the persistence store operation + // also raises an exception + try { + persistenceStore.deleteRecord(data, handlerException); + } catch (IdempotencyKeyException ke) { + throw ke; + } catch (Exception e) { + throw new IdempotencyPersistenceLayerException( + "Failed to delete record from idempotency store. If you believe this is a Powertools for AWS Lambda (Java) bug, please open an issue.", + e); + } + throw handlerException; + } + + try { + persistenceStore.saveSuccess(data, response, Instant.now()); + } catch (Exception e) { + throw new IdempotencyPersistenceLayerException( + "Failed to update record state to success in idempotency store. If you believe this is a Powertools for AWS Lambda (Java) bug, please open an issue.", + e); + } + return response; + } + +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/internal/IdempotentAspect.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/internal/IdempotentAspect.java new file mode 100644 index 000000000..35c6dee40 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/internal/IdempotentAspect.java @@ -0,0 +1,117 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.internal; + +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.placedOnRequestHandler; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.DeclarePrecedence; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; + +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.databind.JsonNode; + +import software.amazon.lambda.powertools.idempotency.Constants; +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.IdempotencyKey; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyConfigurationException; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +/** + * Aspect that handles the {@link Idempotent} annotation. + * It uses the {@link IdempotencyHandler} to actually do the job. + */ +@Aspect +// Idempotency annotation should come first before large message +@DeclarePrecedence("software.amazon.lambda.powertools.idempotency.internal.IdempotentAspect, *") +public class IdempotentAspect { + @SuppressWarnings({ "EmptyMethod" }) + @Pointcut("@annotation(idempotent)") + public void callAt(Idempotent idempotent) { + // Pointcut method - body intentionally empty + } + + @Around(value = "callAt(idempotent) && execution(@Idempotent * *.*(..))", argNames = "pjp,idempotent") + public Object around(ProceedingJoinPoint pjp, + Idempotent idempotent) throws Throwable { + + String idempotencyDisabledEnv = System.getenv().get(Constants.IDEMPOTENCY_DISABLED_ENV); + if (idempotencyDisabledEnv != null && !"false".equalsIgnoreCase(idempotencyDisabledEnv)) { + return pjp.proceed(pjp.getArgs()); + } + + Method method = ((MethodSignature) pjp.getSignature()).getMethod(); + if (method.getReturnType().equals(void.class)) { + throw new IdempotencyConfigurationException( + "The annotated method doesn't return anything. Unable to perform idempotency on void return type"); + } + + boolean isHandler = placedOnRequestHandler(pjp); + JsonNode payload = getPayload(pjp, method, isHandler); + if (payload == null) { + throw new IdempotencyConfigurationException( + "Unable to get payload from the method. Ensure there is at least one parameter or that you use @IdempotencyKey"); + } + + Context lambdaContext; + if (isHandler) { + lambdaContext = (Context) pjp.getArgs()[1]; + } else { + lambdaContext = Idempotency.getInstance().getConfig().getLambdaContext(); + } + + IdempotencyHandler idempotencyHandler = new IdempotencyHandler( + () -> pjp.proceed(pjp.getArgs()), + method.getReturnType(), + method.getName(), + payload, + lambdaContext); + return idempotencyHandler.handle(); + } + + /** + * Retrieve the payload from the annotated method parameters + * + * @param pjp joinPoint + * @param method the annotated method + * @return the payload used for idempotency + */ + private JsonNode getPayload(ProceedingJoinPoint pjp, Method method, boolean isHandler) { + JsonNode payload = null; + // handleRequest or method with one parameter: get the first one + if (isHandler || pjp.getArgs().length == 1) { + payload = JsonConfig.get().getObjectMapper().valueToTree(pjp.getArgs()[0]); + } else { + // Look for a parameter annotated with @IdempotencyKey + Annotation[][] annotations = method.getParameterAnnotations(); + for (int i = 0; i < annotations.length && payload == null; i++) { + Annotation[] annotationsRow = annotations[i]; + for (int j = 0; j < annotationsRow.length && payload == null; j++) { + if (annotationsRow[j].annotationType().equals(IdempotencyKey.class)) { + payload = JsonConfig.get().getObjectMapper().valueToTree(pjp.getArgs()[i]); + } + } + } + } + return payload; + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/internal/IdempotentFunction.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/internal/IdempotentFunction.java new file mode 100644 index 000000000..2ff2071b0 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/internal/IdempotentFunction.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.internal; + +/** + * Functional interface for idempotent function execution. + * <p> + * This interface is similar to {@link java.util.concurrent.Callable} but throws {@link Throwable} + * instead of {@link Exception}. This is necessary to support AspectJ's {@code ProceedingJoinPoint.proceed()} + * which throws {@code Throwable}, allowing exceptions to bubble up naturally without wrapping. + * + * @param <T> the return type of the function + */ +@FunctionalInterface +public interface IdempotentFunction<T> { + @SuppressWarnings("java:S112") // Throwable is required for AspectJ compatibility + T execute() throws Throwable; +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/internal/cache/LRUCache.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/internal/cache/LRUCache.java new file mode 100644 index 000000000..a3bf1c5ff --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/internal/cache/LRUCache.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.internal.cache; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Implementation of a simple LRU Cache based on a {@link LinkedHashMap} + * See <a href="https://stackoverflow.com/a/6400874/270653">here</a>. + * + * @param <K> Type of the keys + * @param <V> Types of the values + */ +public class LRUCache<K, V> extends LinkedHashMap<K, V> { + + private static final long serialVersionUID = 3108262622672699228L; + private final int capacity; + + public LRUCache(int capacity) { + super(capacity * 4 / 3, 0.75f, true); + this.capacity = capacity; + } + + @Override + protected boolean removeEldestEntry(Map.Entry<K, V> entry) { + return size() > this.capacity; + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/BasePersistenceStore.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/BasePersistenceStore.java new file mode 100644 index 000000000..00ddc4336 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/BasePersistenceStore.java @@ -0,0 +1,427 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.persistence; + +import static software.amazon.lambda.powertools.common.internal.LambdaConstants.LAMBDA_FUNCTION_NAME_ENV; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectWriter; + +import io.burt.jmespath.Expression; +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemAlreadyExistsException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemNotFoundException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyKeyException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyValidationException; +import software.amazon.lambda.powertools.idempotency.internal.cache.LRUCache; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +/** + * Persistence layer that will store the idempotency result. + * Base implementation. See {@link DynamoDBPersistenceStore} for an implementation (default one) + * Extends this class to use your own implementation (DocumentDB, Elasticache, ...) + */ +public abstract class BasePersistenceStore implements PersistenceStore { + + private static final Logger LOG = LoggerFactory.getLogger(BasePersistenceStore.class); + protected boolean payloadValidationEnabled = false; + private String functionName = ""; + private boolean configured = false; + private long expirationInSeconds = 60 * 60L; // 1 hour default + private boolean useLocalCache = false; + private LRUCache<String, DataRecord> cache; + private String eventKeyJMESPath; + private Expression<JsonNode> eventKeyCompiledJMESPath; + private Expression<JsonNode> validationKeyJMESPath; + private boolean throwOnNoIdempotencyKey = false; + private String hashFunctionName; + + /** + * Initialize the base persistence layer from the configuration settings + * + * @param config Idempotency configuration settings + * @param functionName The name of the function being decorated + */ + public void configure(IdempotencyConfig config, String functionName) { + String funcEnv = System.getenv(LAMBDA_FUNCTION_NAME_ENV); + this.functionName = funcEnv != null ? funcEnv : "testFunction"; + if (functionName != null && !functionName.isEmpty()) { + this.functionName += "." + functionName; + } + + if (configured) { + // prevent being reconfigured multiple times + return; + } + + eventKeyJMESPath = config.getEventKeyJMESPath(); + if (eventKeyJMESPath != null) { + eventKeyCompiledJMESPath = JsonConfig.get().getJmesPath().compile(eventKeyJMESPath); + } + if (config.getPayloadValidationJMESPath() != null) { + validationKeyJMESPath = JsonConfig.get().getJmesPath().compile(config.getPayloadValidationJMESPath()); + payloadValidationEnabled = true; + } + throwOnNoIdempotencyKey = config.throwOnNoIdempotencyKey(); + + useLocalCache = config.useLocalCache(); + if (useLocalCache) { + cache = new LRUCache<>(config.getLocalCacheMaxItems()); + } + expirationInSeconds = config.getExpirationInSeconds(); + hashFunctionName = config.getHashFunction(); + configured = true; + } + + /** + * Save record of function's execution completing successfully + * + * @param data Payload + * @param result the response from the function + */ + public void saveSuccess(JsonNode data, Object result, Instant now) { + ObjectWriter writer = JsonConfig.get().getObjectMapper().writer(); + try { + String responseJson; + if (result instanceof String) { + responseJson = (String) result; + } else { + responseJson = writer.writeValueAsString(result); + } + Optional<String> hashedIdempotencyKey = getHashedIdempotencyKey(data); + if (!hashedIdempotencyKey.isPresent()) { + // missing idempotency key => non-idempotent transaction, we do not store the data, simply return + return; + } + DataRecord dataRecord = new DataRecord( + hashedIdempotencyKey.get(), + DataRecord.Status.COMPLETED, + getExpiryEpochSecond(now), + responseJson, + getHashedPayload(data)); + LOG.debug("Function successfully executed. Saving record to persistence store with idempotency key: {}", + dataRecord.getIdempotencyKey()); + updateRecord(dataRecord); + saveToCache(dataRecord); + } catch (JsonProcessingException e) { + throw new RuntimeException("Error while serializing the response", e); + } + } + + /** + * Save record of function's execution being in progress + * + * @param data Payload + * @param now + */ + public void saveInProgress(JsonNode data, Instant now, OptionalInt remainingTimeInMs) + throws IdempotencyItemAlreadyExistsException { + Optional<String> hashedIdempotencyKey = getHashedIdempotencyKey(data); + if (!hashedIdempotencyKey.isPresent()) { + // missing idempotency key => non-idempotent transaction, we do not store the data, simply return + return; + } + + String idempotencyKey = hashedIdempotencyKey.get(); + if (retrieveFromCache(idempotencyKey, now) != null) { + throw new IdempotencyItemAlreadyExistsException(); + } + + OptionalLong inProgressExpirationMsTimestamp = OptionalLong.empty(); + if (remainingTimeInMs.isPresent()) { + inProgressExpirationMsTimestamp = OptionalLong + .of(now.plus(remainingTimeInMs.getAsInt(), ChronoUnit.MILLIS).toEpochMilli()); + } + + DataRecord dataRecord = new DataRecord( + idempotencyKey, + DataRecord.Status.INPROGRESS, + getExpiryEpochSecond(now), + null, + getHashedPayload(data), + inProgressExpirationMsTimestamp); + LOG.debug("saving in progress record for idempotency key: {}", dataRecord.getIdempotencyKey()); + + try { + putRecord(dataRecord, now); + } catch (IdempotencyItemAlreadyExistsException iaee) { + // Similar to getRecord, we need to call validatePayload before returning a data record. + // PR https://github.com/aws-powertools/powertools-lambda-java/pull/1821 introduced returning a data record + // through IdempotencyItemAlreadyExistsException to save DynamoDB calls when using DDB as store. + Optional<DataRecord> dr = iaee.getDataRecord(); + if (dr.isPresent()) { + // throws IdempotencyValidationException if payload validation is enabled and failing + validatePayload(data, dr.get()); + } + + throw iaee; + } + } + + /** + * Delete record from the persistence store + * + * @param data Payload + * @param throwable The throwable thrown by the function + */ + public void deleteRecord(JsonNode data, Throwable throwable) { + Optional<String> hashedIdempotencyKey = getHashedIdempotencyKey(data); + if (!hashedIdempotencyKey.isPresent()) { + // missing idempotency key => non-idempotent transaction, we do not delete the data, simply return + return; + } + + String idemPotencyKey = hashedIdempotencyKey.get(); + LOG.debug("Function raised an exception {}. " + + "Clearing in progress record in persistence store for idempotency key: {}", + throwable.getClass(), + idemPotencyKey); + + deleteRecord(idemPotencyKey); + deleteFromCache(idemPotencyKey); + } + + /** + * Retrieve idempotency key for data provided, fetch from persistence store, and convert to DataRecord. + * + * @param data Payload + * @return DataRecord representation of existing record found in persistence store + * @throws IdempotencyValidationException Payload doesn't match the stored record for the given idempotency key + * @throws IdempotencyItemNotFoundException Exception thrown if no record exists in persistence store with the idempotency key + */ + public DataRecord getRecord(JsonNode data, Instant now) + throws IdempotencyValidationException, IdempotencyItemNotFoundException { + Optional<String> hashedIdempotencyKey = getHashedIdempotencyKey(data); + if (!hashedIdempotencyKey.isPresent()) { + // missing idempotency key => non-idempotent transaction, we do not get the data, simply return nothing + return null; + } + + String idemPotencyKey = hashedIdempotencyKey.get(); + DataRecord cachedRecord = retrieveFromCache(idemPotencyKey, now); + if (cachedRecord != null) { + LOG.debug("Idempotency record found in cache with idempotency key: {}", idemPotencyKey); + validatePayload(data, cachedRecord); + return cachedRecord; + } + + DataRecord dataRecord = getRecord(idemPotencyKey); + saveToCache(dataRecord); + validatePayload(data, dataRecord); + return dataRecord; + } + + /** + * Extract idempotency key and return a hashed representation + * + * @param data incoming data + * @return Hashed representation of the data extracted by the jmespath expression + */ + private Optional<String> getHashedIdempotencyKey(JsonNode data) { + JsonNode node = data; + + if (eventKeyJMESPath != null) { + node = eventKeyCompiledJMESPath.search(data); + } + + if (isMissingIdemPotencyKey(node)) { + if (throwOnNoIdempotencyKey) { + throw new IdempotencyKeyException("No data found to create a hashed idempotency key"); + } else { + LOG.warn("No data found to create a hashed idempotency key. JMESPath: {}", eventKeyJMESPath); + return Optional.empty(); + } + } + + String hash = generateHash(node); + hash = functionName + "#" + hash; + return Optional.of(hash); + } + + private boolean isMissingIdemPotencyKey(JsonNode data) { + if (data.isContainerNode()) { + Stream<JsonNode> stream = StreamSupport.stream( + Spliterators.spliteratorUnknownSize(data.elements(), Spliterator.ORDERED), + false); + return stream.allMatch(JsonNode::isNull); + } + return data.isNull(); + } + + /** + * Extract payload using validation key jmespath and return a hashed representation + * + * @param data Payload + * @return Hashed representation of the data extracted by the jmespath expression + */ + private String getHashedPayload(JsonNode data) { + if (!payloadValidationEnabled) { + return ""; + } + JsonNode object = validationKeyJMESPath.search(data); + return generateHash(object); + } + + /** + * Generate a hash value from the provided data + * + * @param data data to hash + * @return Hashed representation of the provided data + */ + String generateHash(JsonNode data) { + Object node; + // if array or object, use the json string representation, otherwise get the real value + if (data.isContainerNode()) { + node = data.toString(); + } else if (data.isTextual()) { + node = data.asText(); + } else if (data.isInt()) { + node = data.asInt(); + } else if (data.isLong()) { + node = data.asLong(); + } else if (data.isDouble()) { + node = data.asDouble(); + } else if (data.isFloat()) { + node = data.floatValue(); + } else if (data.isBigInteger()) { + node = data.bigIntegerValue(); + } else if (data.isBigDecimal()) { + node = data.decimalValue(); + } else if (data.isBoolean()) { + node = data.asBoolean(); + } else { + node = data; // anything else + } + + MessageDigest hashAlgorithm = getHashAlgorithm(); + byte[] digest = hashAlgorithm.digest(node.toString().getBytes(StandardCharsets.UTF_8)); + return String.format("%032x", new BigInteger(1, digest)); + } + + @SuppressWarnings("java:S4790") // Usage of MessageDigest is OK + private MessageDigest getHashAlgorithm() { + MessageDigest hashAlgorithm; + try { + hashAlgorithm = MessageDigest.getInstance(hashFunctionName); + } catch (NoSuchAlgorithmException e) { + LOG.warn("Error instantiating {} hash function, trying with MD5", hashFunctionName); + try { + hashAlgorithm = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException("Unable to instantiate MD5 digest", ex); + } + } + return hashAlgorithm; + } + + /** + * Validate that the hashed payload matches data provided and stored data record + * + * @param data Payload + * @param dataRecord DataRecord instance + */ + private void validatePayload(JsonNode data, DataRecord dataRecord) throws IdempotencyValidationException { + if (payloadValidationEnabled) { + String dataHash = getHashedPayload(data); + if (!isEqual(dataRecord.getPayloadHash(), dataHash)) { + throw new IdempotencyValidationException("Payload does not match stored record for this event key"); + } + } + } + + /** + * @param now + * @return unix timestamp of expiry date for idempotency record + */ + private long getExpiryEpochSecond(Instant now) { + return now.plus(expirationInSeconds, ChronoUnit.SECONDS).getEpochSecond(); + } + + /** + * Save data_record to local cache except when status is "INPROGRESS" + * <br/> + * NOTE: We can't cache "INPROGRESS" records as we have no way to reflect updates that can happen outside of the + * execution environment + * + * @param dataRecord DataRecord to save in cache + */ + private void saveToCache(DataRecord dataRecord) { + if (!useLocalCache) { + return; + } + if (dataRecord.getStatus().equals(DataRecord.Status.INPROGRESS)) { + return; + } + + cache.put(dataRecord.getIdempotencyKey(), dataRecord); + } + + private DataRecord retrieveFromCache(String idempotencyKey, Instant now) { + if (!useLocalCache) { + return null; + } + + DataRecord dataRecord = cache.get(idempotencyKey); + if (dataRecord != null) { + if (!dataRecord.isExpired(now)) { + return dataRecord; + } + LOG.debug("Removing expired local cache record for idempotency key: {}", idempotencyKey); + deleteFromCache(idempotencyKey); + } + return null; + } + + private void deleteFromCache(String idempotencyKey) { + if (!useLocalCache) { + return; + } + cache.remove(idempotencyKey); + } + + /** + * For test purpose only (adding a cache to mock) + */ + void configure(IdempotencyConfig config, String functionName, LRUCache<String, DataRecord> cache) { + this.configure(config, functionName); + this.cache = cache; + } + + private static boolean isEqual(String dataRecordPayload, String dataHash) { + if (dataHash != null && dataRecordPayload != null) { + return dataHash.length() == dataRecordPayload.length() && dataHash.equals(dataRecordPayload); + } else { + return false; + } + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/DataRecord.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/DataRecord.java new file mode 100644 index 000000000..0621c372b --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/DataRecord.java @@ -0,0 +1,165 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.persistence; + +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; + +import java.time.Instant; +import java.util.Objects; +import java.util.OptionalLong; + +/** + * Data Class for idempotency records. This is actually the item that will be stored in the persistence layer. + */ +public class DataRecord { + private final String idempotencyKey; + private final String status; + + /** + * This field is controlling how long the result of the idempotent + * event is cached. It is stored in _seconds since epoch_. + * <p> + * DynamoDB's TTL mechanism is used to remove the record once the + * expiry has been reached, and subsequent execution of the request + * will be permitted. The user must configure this on their table. + */ + private final long expiryTimestamp; + private final String responseData; + private final String payloadHash; + + /** + * The in-progress field is set to the remaining lambda execution time + * when the record is created. + * This field is stored in _milliseconds since epoch_. + * <p> + * This ensures that: + * <p> + * 1/ other concurrently executing requests are blocked from starting + * 2/ if a lambda times out, subsequent requests will be allowed again, despite + * the fact that the idempotency record is already in the table + */ + private final OptionalLong inProgressExpiryTimestamp; + + public DataRecord(String idempotencyKey, Status status, long expiryTimestamp, String responseData, + String payloadHash) { + this.idempotencyKey = idempotencyKey; + this.status = status.toString(); + this.expiryTimestamp = expiryTimestamp; + this.responseData = responseData; + this.payloadHash = payloadHash; + this.inProgressExpiryTimestamp = OptionalLong.empty(); + } + + public DataRecord(String idempotencyKey, Status status, long expiryTimestamp, String responseData, + String payloadHash, OptionalLong inProgressExpiryTimestamp) { + this.idempotencyKey = idempotencyKey; + this.status = status.toString(); + this.expiryTimestamp = expiryTimestamp; + this.responseData = responseData; + this.payloadHash = payloadHash; + this.inProgressExpiryTimestamp = inProgressExpiryTimestamp; + } + + public String getIdempotencyKey() { + return idempotencyKey; + } + + /** + * Check if data record is expired (based on expiration configured in the {@link IdempotencyConfig}) + * + * @return Whether the record is currently expired or not + */ + public boolean isExpired(Instant now) { + return expiryTimestamp != 0 && now.isAfter(Instant.ofEpochSecond(expiryTimestamp)); + } + + public Status getStatus() { + Instant now = Instant.now(); + if (isExpired(now)) { + return Status.EXPIRED; + } else { + return Status.valueOf(status); + } + } + + public long getExpiryTimestamp() { + return expiryTimestamp; + } + + public OptionalLong getInProgressExpiryTimestamp() { + return inProgressExpiryTimestamp; + } + + public String getResponseData() { + return responseData; + } + + public String getPayloadHash() { + return payloadHash; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DataRecord record = (DataRecord) o; + return expiryTimestamp == record.expiryTimestamp + && idempotencyKey.equals(record.idempotencyKey) + && status.equals(record.status) + && Objects.equals(responseData, record.responseData) + && Objects.equals(payloadHash, record.payloadHash); + } + + @Override + public int hashCode() { + return Objects.hash(idempotencyKey, status, expiryTimestamp, responseData, payloadHash); + } + + @Override + public String toString() { + return "DataRecord{" + + "idempotencyKey='" + idempotencyKey + '\'' + + ", status='" + status + '\'' + + ", expiryTimestamp=" + expiryTimestamp + + ", payloadHash='" + payloadHash + '\'' + + '}'; + } + + /** + * Status of the record: + * <ul> + * <li>INPROGRESS: record initialized when function starts</li> + * <li>COMPLETED: record updated with the result of the function when it ends</li> + * <li>EXPIRED: record expired, idempotency will not happen</li> + * </ul> + */ + public enum Status { + INPROGRESS("INPROGRESS"), COMPLETED("COMPLETED"), EXPIRED("EXPIRED"); + + private final String status; + + Status(String status) { + this.status = status; + } + + public String toString() { + return status; + } + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/PersistenceStore.java b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/PersistenceStore.java new file mode 100644 index 000000000..b4f105720 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/PersistenceStore.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.persistence; + +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemAlreadyExistsException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemNotFoundException; + +import java.time.Instant; + +/** + * Persistence layer that will store the idempotency result. + * In order to provide another implementation, extends {@link BasePersistenceStore}. + */ +public interface PersistenceStore { + + /** + * Retrieve item from persistence store using idempotency key and return it as a DataRecord instance. + * + * @param idempotencyKey the key of the record + * @return DataRecord representation of existing record found in persistence store + * @throws IdempotencyItemNotFoundException Exception thrown if no record exists in persistence store with the idempotency key + */ + DataRecord getRecord(String idempotencyKey) throws IdempotencyItemNotFoundException; + + /** + * Add a DataRecord to persistence store if it does not already exist with that key + * + * @param record DataRecord instance + * @param now + * @throws IdempotencyItemAlreadyExistsException if a non-expired entry already exists. + */ + void putRecord(DataRecord record, Instant now) throws IdempotencyItemAlreadyExistsException; + + /** + * Update item in persistence store + * + * @param record DataRecord instance + */ + void updateRecord(DataRecord record); + + /** + * Remove item from persistence store + * + * @param idempotencyKey the key of the record + */ + void deleteRecord(String idempotencyKey); +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/IdempotencyTest.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/IdempotencyTest.java new file mode 100644 index 000000000..10664a25b --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/IdempotencyTest.java @@ -0,0 +1,311 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import java.util.HashMap; +import java.util.Map; +import java.util.OptionalInt; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; + +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; +import software.amazon.lambda.powertools.idempotency.handlers.IdempotencyFunctionalFunction; +import software.amazon.lambda.powertools.idempotency.handlers.IdempotencyMultiArgFunctionalFunction; +import software.amazon.lambda.powertools.idempotency.model.Basket; +import software.amazon.lambda.powertools.idempotency.model.Product; +import software.amazon.lambda.powertools.idempotency.persistence.BasePersistenceStore; +import software.amazon.lambda.powertools.idempotency.testutils.InMemoryPersistenceStore; + +@ExtendWith(MockitoExtension.class) +class IdempotencyTest { + + private Context context = new TestLambdaContext(); + + @Mock + private BasePersistenceStore store; + + @Test + void firstCall_shouldPutInStore() { + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("id") + .build()) + .configure(); + + IdempotencyFunctionalFunction function = new IdempotencyFunctionalFunction(); + + Product p = new Product(42, "fake product", 12); + Basket basket = function.handleRequest(p, context); + + assertThat(function.processCalled()).isTrue(); + assertThat(basket.getProducts()).hasSize(1); + + ArgumentCaptor<JsonNode> nodeCaptor = ArgumentCaptor.forClass(JsonNode.class); + ArgumentCaptor<OptionalInt> expiryCaptor = ArgumentCaptor.forClass(OptionalInt.class); + verify(store).saveInProgress(nodeCaptor.capture(), any(), expiryCaptor.capture()); + assertThat(nodeCaptor.getValue().get("id").asLong()).isEqualTo(p.getId()); + assertThat(nodeCaptor.getValue().get("name").asText()).isEqualTo(p.getName()); + assertThat(nodeCaptor.getValue().get("price").asDouble()).isEqualTo(p.getPrice()); + assertThat(expiryCaptor.getValue().orElse(-1)).isEqualTo(30000); + + ArgumentCaptor<Basket> resultCaptor = ArgumentCaptor.forClass(Basket.class); + verify(store).saveSuccess(any(), resultCaptor.capture(), any()); + assertThat(resultCaptor.getValue()).isEqualTo(basket); + } + + @Test + void testMakeIdempotentWithFunctionName() throws Throwable { + BasePersistenceStore spyStore = spy(BasePersistenceStore.class); + Idempotency.config() + .withPersistenceStore(spyStore) + .configure(); + Idempotency.registerLambdaContext(context); + + String result = Idempotency.makeIdempotent("myFunction", "test-key", () -> "test-result", + String.class); + + assertThat(result).isEqualTo("test-result"); + + ArgumentCaptor<String> functionNameCaptor = ArgumentCaptor.forClass(String.class); + verify(spyStore).configure(any(), functionNameCaptor.capture()); + assertThat(functionNameCaptor.getValue()).isEqualTo("myFunction"); + } + + @Test + void testMakeIdempotentWithMethodReferenceUsesDefaultName() throws Throwable { + BasePersistenceStore spyStore = spy(BasePersistenceStore.class); + Idempotency.config() + .withPersistenceStore(spyStore) + .configure(); + Idempotency.registerLambdaContext(context); + + String result = Idempotency.makeIdempotent("test-key", this::helperMethod, String.class); + + assertThat(result).isEqualTo("helper-result"); + + ArgumentCaptor<String> functionNameCaptor = ArgumentCaptor.forClass(String.class); + verify(spyStore).configure(any(), functionNameCaptor.capture()); + assertThat(functionNameCaptor.getValue()).isEqualTo("function"); + } + + private String helperMethod() { + return "helper-result"; + } + + @Test + void testMakeIdempotentWithFunctionOverload() throws Throwable { + BasePersistenceStore spyStore = spy(BasePersistenceStore.class); + Idempotency.config() + .withPersistenceStore(spyStore) + .configure(); + Idempotency.registerLambdaContext(context); + + Product p = new Product(42, "test product", 10); + Basket result = Idempotency.makeIdempotent(this::processProduct, p, Basket.class); + + assertThat(result.getProducts()).hasSize(1); + assertThat(result.getProducts().get(0)).isEqualTo(p); + + ArgumentCaptor<String> functionNameCaptor = ArgumentCaptor.forClass(String.class); + verify(spyStore).configure(any(), functionNameCaptor.capture()); + assertThat(functionNameCaptor.getValue()).isEqualTo("function"); + + ArgumentCaptor<JsonNode> nodeCaptor = ArgumentCaptor.forClass(JsonNode.class); + verify(spyStore).saveInProgress(nodeCaptor.capture(), any(), any()); + assertThat(nodeCaptor.getValue().get("id").asLong()).isEqualTo(p.getId()); + } + + private Basket processProduct(Product product) { + Basket basket = new Basket(); + basket.add(product); + return basket; + } + + @Test + void firstCall_withExplicitIdempotencyKey_shouldPutInStore() { + Idempotency.config() + .withPersistenceStore(store) + .configure(); + + IdempotencyMultiArgFunctionalFunction function = new IdempotencyMultiArgFunctionalFunction(); + + Product p = new Product(42, "fake product", 12); + Basket basket = function.handleRequest(p, context); + + assertThat(function.processCalled()).isTrue(); + assertThat(function.getExtraData()).isEqualTo("extra-data"); + assertThat(basket.getProducts()).hasSize(1); + + ArgumentCaptor<JsonNode> nodeCaptor = ArgumentCaptor.forClass(JsonNode.class); + verify(store).saveInProgress(nodeCaptor.capture(), any(), any()); + assertThat(nodeCaptor.getValue().asLong()).isEqualTo(p.getId()); + + ArgumentCaptor<Basket> resultCaptor = ArgumentCaptor.forClass(Basket.class); + verify(store).saveSuccess(any(), resultCaptor.capture(), any()); + assertThat(resultCaptor.getValue()).isEqualTo(basket); + } + + @Test + void secondCall_shouldRetrieveFromCacheAndDeserialize() throws Throwable { + InMemoryPersistenceStore inMemoryStore = new InMemoryPersistenceStore(); + + Idempotency.config() + .withPersistenceStore(inMemoryStore) + .configure(); + Idempotency.registerLambdaContext(context); + + Product p = new Product(42, "test product", 10); + int[] callCount = { 0 }; + + // First call - executes function and stores result + Basket result1 = Idempotency.makeIdempotent(p, () -> { + callCount[0]++; + return processProduct(p); + }, Basket.class); + assertThat(result1.getProducts()).hasSize(1); + assertThat(callCount[0]).isEqualTo(1); + + // Second call - should retrieve from cache, deserialize, and NOT execute function + Basket result2 = Idempotency.makeIdempotent(p, () -> { + callCount[0]++; + return processProduct(p); + }, Basket.class); + assertThat(result2.getProducts()).hasSize(1); + assertThat(result2.getProducts().get(0).getId()).isEqualTo(42); + assertThat(result2.getProducts().get(0).getName()).isEqualTo("test product"); + assertThat(callCount[0]).isEqualTo(1); // Function should NOT be called again + } + + @Test + void concurrentInvocations_shouldNotLeakContext() throws Exception { + Idempotency.config() + .withPersistenceStore(store) + .configure(); + + IdempotencyMultiArgFunctionalFunction function = new IdempotencyMultiArgFunctionalFunction(); + + // GIVEN + int threadCount = 10; + Thread[] threads = new Thread[threadCount]; + Context[] capturedContexts = new Context[threadCount]; + int[] capturedRemainingTimes = new int[threadCount]; + boolean[] success = new boolean[threadCount]; + + // WHEN - Multiple threads call handleRequest with different contexts + for (int i = 0; i < threadCount; i++) { + final int threadIndex = i; + final int expectedTime = (i + 1) * 2000; // 2000, 4000, 6000, ..., 20000 + + final Context threadContext = new TestLambdaContext() { + @Override + public int getRemainingTimeInMillis() { + return expectedTime; + } + }; + + threads[i] = new Thread(() -> { + try { + Product p = new Product(threadIndex, "product" + threadIndex, 10); + function.handleRequest(p, threadContext); + + // Capture the context that was actually stored in ThreadLocal by this thread + Context captured = Idempotency.getInstance().getConfig().getLambdaContext(); + capturedContexts[threadIndex] = captured; + capturedRemainingTimes[threadIndex] = captured != null ? captured.getRemainingTimeInMillis() : -1; + success[threadIndex] = true; + } catch (Exception e) { + success[threadIndex] = false; + } + }); + } + + // Start all threads + for (Thread thread : threads) { + thread.start(); + } + + // Wait for all threads to complete + for (Thread thread : threads) { + thread.join(); + } + + // THEN - All threads should complete successfully + for (boolean result : success) { + assertThat(result).isTrue(); + } + + // THEN - Each thread should have captured its own context (no leakage) + for (int i = 0; i < threadCount; i++) { + int expectedTime = (i + 1) * 2000; + assertThat(capturedRemainingTimes[i]) + .as("Thread %d should have remaining time %d", i, expectedTime) + .isEqualTo(expectedTime); + assertThat(capturedContexts[i]).as("Thread %d should have non-null context", i).isNotNull(); + } + } + + @Test + void testMakeIdempotentWithGenericType() throws Throwable { + InMemoryPersistenceStore inMemoryStore = new InMemoryPersistenceStore(); + + Idempotency.config() + .withPersistenceStore(inMemoryStore) + .configure(); + Idempotency.registerLambdaContext(context); + + int[] callCount = { 0 }; + + // First call - executes function and stores result + Map<String, Basket> result1 = Idempotency.makeIdempotent("test-key", () -> { + callCount[0]++; + Map<String, Basket> map = new HashMap<>(); + Basket basket = new Basket(); + basket.add(new Product(1, "product1", 10)); + map.put("basket1", basket); + return map; + }, new TypeReference<Map<String, Basket>>() { + }); + + assertThat(result1).hasSize(1); + assertThat(result1.get("basket1").getProducts()).hasSize(1); + assertThat(callCount[0]).isEqualTo(1); + + // Second call - should retrieve from cache and deserialize correctly + Map<String, Basket> result2 = Idempotency.makeIdempotent("test-key", () -> { + callCount[0]++; + return new HashMap<>(); + }, new TypeReference<Map<String, Basket>>() { + }); + + assertThat(result2).hasSize(1); + assertThat(result2.get("basket1").getProducts()).hasSize(1); + assertThat(result2.get("basket1").getProducts().get(0).getName()).isEqualTo("product1"); + assertThat(callCount[0]).isEqualTo(1); // Function should NOT be called again + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyEnabledFunction.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyEnabledFunction.java new file mode 100644 index 000000000..f12edc87e --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyEnabledFunction.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.model.Basket; +import software.amazon.lambda.powertools.idempotency.model.Product; + +/** + * Simple Lambda function with @{@link Idempotent} annotation on handleRequest method + */ +public class IdempotencyEnabledFunction implements RequestHandler<Product, Basket> { + + private boolean handlerCalled = false; + + public boolean handlerCalled() { + return handlerCalled; + } + + @Override + @Idempotent + public Basket handleRequest(Product input, Context context) { + handlerCalled = true; + Basket b = new Basket(); + b.add(input); + return b; + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyFunctionalFunction.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyFunctionalFunction.java new file mode 100644 index 000000000..c2a85d178 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyFunctionalFunction.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.model.Basket; +import software.amazon.lambda.powertools.idempotency.model.Product; + +/** + * Lambda function using Idempotency functional API without AspectJ annotations + */ +public class IdempotencyFunctionalFunction implements RequestHandler<Product, Basket> { + + private boolean processCalled = false; + + public boolean processCalled() { + return processCalled; + } + + @Override + public Basket handleRequest(Product input, Context context) { + Idempotency.registerLambdaContext(context); + + return Idempotency.makeIdempotent(this::process, input, Basket.class); + } + + private Basket process(Product input) { + processCalled = true; + Basket b = new Basket(); + b.add(input); + return b; + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyInternalFunction.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyInternalFunction.java new file mode 100644 index 000000000..34e3eb319 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyInternalFunction.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.IdempotencyKey; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.model.Basket; +import software.amazon.lambda.powertools.idempotency.model.Product; + +/** + * Simple Lambda function with @{@link Idempotent} annotation on a sub method (not the handleRequest one) + */ +public class IdempotencyInternalFunction implements RequestHandler<Product, Basket> { + + private final boolean registerContext; + private boolean called = false; + + public IdempotencyInternalFunction(boolean registerContext) { + this.registerContext = registerContext; + } + + @Override + public Basket handleRequest(Product input, Context context) { + if (registerContext) { + Idempotency.registerLambdaContext(context); + } + + return createBasket("fake", input); + } + + @Idempotent + private Basket createBasket(@IdempotencyKey String magicProduct, Product p) { + called = true; + Basket b = new Basket(p); + b.add(new Product(0, magicProduct, 0)); + return b; + } + + public boolean subMethodCalled() { + return called; + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyInternalFunctionInternalKey.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyInternalFunctionInternalKey.java new file mode 100644 index 000000000..3ae500341 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyInternalFunctionInternalKey.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.model.Basket; +import software.amazon.lambda.powertools.idempotency.model.Product; + +/** + * Simple Lambda function with @{@link Idempotent} annotation on a sub method (not the handleRequest one) + */ +public class IdempotencyInternalFunctionInternalKey implements RequestHandler<Product, Basket> { + + @Override + public Basket handleRequest(Product input, Context context) { + return createBasket(input); + } + + @Idempotent + private Basket createBasket(Product p) { + return new Basket(p); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyInternalFunctionInvalid.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyInternalFunctionInvalid.java new file mode 100644 index 000000000..42e438798 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyInternalFunctionInvalid.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.model.Basket; +import software.amazon.lambda.powertools.idempotency.model.Product; + +/** + * Simple Lambda function with @{@link Idempotent} annotation a sub method.<br/> + * This one is invalid as there are two parameters and <code>@IdempotencyKey</code> + * is not used to specify which one will be used as a key for persistence. + */ +public class IdempotencyInternalFunctionInvalid implements RequestHandler<Product, Basket> { + + @Override + public Basket handleRequest(Product input, Context context) { + return createBasket("fake", input); + } + + @Idempotent + private Basket createBasket(String magicProduct, Product p) { + Basket b = new Basket(p); + b.add(new Product(0, magicProduct, 0)); + return b; + } + +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyInternalFunctionVoid.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyInternalFunctionVoid.java new file mode 100644 index 000000000..384ed5e86 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyInternalFunctionVoid.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.idempotency.IdempotencyKey; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.model.Basket; +import software.amazon.lambda.powertools.idempotency.model.Product; + +/** + * Simple Lambda function with @{@link Idempotent} annotation a sub method.<br/> + * This one is invalid because the annotated method return type is void, thus we cannot store any response. + */ +public class IdempotencyInternalFunctionVoid implements RequestHandler<Product, Basket> { + + @Override + public Basket handleRequest(Product input, Context context) { + Basket b = new Basket(input); + addProduct("fake", b); + return b; + } + + @Idempotent + private void addProduct(@IdempotencyKey String productName, Basket b) { + b.add(new Product(0, productName, 0)); + } + +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyMultiArgFunctionalFunction.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyMultiArgFunctionalFunction.java new file mode 100644 index 000000000..42e75fd55 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyMultiArgFunctionalFunction.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.model.Basket; +import software.amazon.lambda.powertools.idempotency.model.Product; + +/** + * Lambda function using Idempotency functional API with explicit idempotency key + */ +public class IdempotencyMultiArgFunctionalFunction implements RequestHandler<Product, Basket> { + + private boolean processCalled = false; + private String extraData; + + public boolean processCalled() { + return processCalled; + } + + public String getExtraData() { + return extraData; + } + + @Override + public Basket handleRequest(Product input, Context context) { + Idempotency.registerLambdaContext(context); + + return Idempotency.makeIdempotent(input.getId(), () -> process(input, "extra-data"), Basket.class); + } + + private Basket process(Product input, String extraData) { + processCalled = true; + this.extraData = extraData; + Basket b = new Basket(); + b.add(input); + return b; + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyStringFunction.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyStringFunction.java new file mode 100644 index 000000000..3636657aa --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyStringFunction.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.model.Product; + +public class IdempotencyStringFunction implements RequestHandler<Product, String> { + + private boolean handlerCalled = false; + + public boolean handlerCalled() { + return handlerCalled; + } + + @Override + @Idempotent + public String handleRequest(Product input, Context context) { + handlerCalled = true; + return input.getName(); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyWithErrorFunction.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyWithErrorFunction.java new file mode 100644 index 000000000..f46576033 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyWithErrorFunction.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.model.Basket; +import software.amazon.lambda.powertools.idempotency.model.Product; + +/** + * Simple Lambda function with @{@link Idempotent} annotation on handleRequest method.<br/> + * This function throws an exception. + */ +public class IdempotencyWithErrorFunction implements RequestHandler<Product, Basket> { + + @Override + @Idempotent + public Basket handleRequest(Product input, Context context) { + throw new IndexOutOfBoundsException("Fake exception"); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/internal/IdempotencyAspectTest.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/internal/IdempotencyAspectTest.java new file mode 100644 index 000000000..384a20b2a --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/internal/IdempotencyAspectTest.java @@ -0,0 +1,610 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.internal; + +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +import java.time.Instant; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.SetEnvironmentVariable; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; + +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; +import software.amazon.lambda.powertools.idempotency.Constants; +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyAlreadyInProgressException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyConfigurationException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyInconsistentStateException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemAlreadyExistsException; +import software.amazon.lambda.powertools.idempotency.handlers.IdempotencyEnabledFunction; +import software.amazon.lambda.powertools.idempotency.handlers.IdempotencyInternalFunction; +import software.amazon.lambda.powertools.idempotency.handlers.IdempotencyInternalFunctionInternalKey; +import software.amazon.lambda.powertools.idempotency.handlers.IdempotencyInternalFunctionInvalid; +import software.amazon.lambda.powertools.idempotency.handlers.IdempotencyInternalFunctionVoid; +import software.amazon.lambda.powertools.idempotency.handlers.IdempotencyStringFunction; +import software.amazon.lambda.powertools.idempotency.handlers.IdempotencyWithErrorFunction; +import software.amazon.lambda.powertools.idempotency.model.Basket; +import software.amazon.lambda.powertools.idempotency.model.Product; +import software.amazon.lambda.powertools.idempotency.persistence.BasePersistenceStore; +import software.amazon.lambda.powertools.idempotency.persistence.DataRecord; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +@ExtendWith(MockitoExtension.class) +class IdempotencyAspectTest { + + private Context context = new TestLambdaContext(); + + @Mock + private BasePersistenceStore store; + + @Test + void firstCall_shouldPutInStore() { + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("id") + .build()) + .configure(); + + IdempotencyEnabledFunction function = new IdempotencyEnabledFunction(); + + Product p = new Product(42, "fake product", 12); + Basket basket = function.handleRequest(p, context); + assertThat(basket.getProducts()).hasSize(1); + assertThat(function.handlerCalled()).isTrue(); + + ArgumentCaptor<JsonNode> nodeCaptor = ArgumentCaptor.forClass(JsonNode.class); + ArgumentCaptor<OptionalInt> expiryCaptor = ArgumentCaptor.forClass(OptionalInt.class); + verify(store).saveInProgress(nodeCaptor.capture(), any(), expiryCaptor.capture()); + assertThat(nodeCaptor.getValue().get("id").asLong()).isEqualTo(p.getId()); + assertThat(nodeCaptor.getValue().get("name").asText()).isEqualTo(p.getName()); + assertThat(nodeCaptor.getValue().get("price").asDouble()).isEqualTo(p.getPrice()); + + assertThat(expiryCaptor.getValue().orElse(-1)).isEqualTo(30000); + + ArgumentCaptor<Basket> resultCaptor = ArgumentCaptor.forClass(Basket.class); + verify(store).saveSuccess(any(), resultCaptor.capture(), any()); + assertThat(resultCaptor.getValue()).isEqualTo(basket); + } + + @Test + void firstCall_shouldPutInStoreAndNotApplyResponseHook() { + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("id") + // This hook will add a second product to the basket. It should not run here. + // Only for + // idempotent responses. + .withResponseHook((responseData, dataRecord) -> { + final Basket basket = (Basket) responseData; + basket.add(new Product(42, "fake product 2", 12)); + return basket; + }) + .build()) + .configure(); + + IdempotencyEnabledFunction function = new IdempotencyEnabledFunction(); + + Product p = new Product(42, "fake product", 12); + Basket basket = function.handleRequest(p, context); + assertThat(basket.getProducts()).hasSize(1); // Size should be 1 because response hook should not run + assertThat(function.handlerCalled()).isTrue(); + + ArgumentCaptor<JsonNode> nodeCaptor = ArgumentCaptor.forClass(JsonNode.class); + ArgumentCaptor<OptionalInt> expiryCaptor = ArgumentCaptor.forClass(OptionalInt.class); + verify(store).saveInProgress(nodeCaptor.capture(), any(), expiryCaptor.capture()); + assertThat(nodeCaptor.getValue().get("id").asLong()).isEqualTo(p.getId()); + assertThat(nodeCaptor.getValue().get("name").asText()).isEqualTo(p.getName()); + assertThat(nodeCaptor.getValue().get("price").asDouble()).isEqualTo(p.getPrice()); + + assertThat(expiryCaptor.getValue().orElse(-1)).isEqualTo(30000); + + ArgumentCaptor<Basket> resultCaptor = ArgumentCaptor.forClass(Basket.class); + verify(store).saveSuccess(any(), resultCaptor.capture(), any()); + assertThat(resultCaptor.getValue()).isEqualTo(basket); + } + + @Test + void secondCall_notExpired_shouldGetFromStore() throws JsonProcessingException { + // GIVEN + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("id") + .build()) + .configure(); + + doThrow(new IdempotencyItemAlreadyExistsException()).when(store).saveInProgress(any(), any(), any()); + + Product p = new Product(42, "fake product", 12); + Basket b = new Basket(p); + DataRecord dr = new DataRecord( + "42", + DataRecord.Status.COMPLETED, + Instant.now().plus(356, SECONDS).getEpochSecond(), + JsonConfig.get().getObjectMapper().writer().writeValueAsString(b), + null); + doReturn(dr).when(store).getRecord(any(), any()); + + // WHEN + IdempotencyEnabledFunction function = new IdempotencyEnabledFunction(); + Basket basket = function.handleRequest(p, context); + + // THEN + assertThat(basket).isEqualTo(b); + assertThat(function.handlerCalled()).isFalse(); + } + + @Test + void secondCall_notExpired_shouldNotGetFromStoreIfPresentOnIdempotencyException() + throws JsonProcessingException { + // GIVEN + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("id") + .build()) + .configure(); + + Product p = new Product(42, "fake product", 12); + Basket b = new Basket(p); + DataRecord dr = new DataRecord( + "42", + DataRecord.Status.COMPLETED, + Instant.now().plus(356, SECONDS).getEpochSecond(), + JsonConfig.get().getObjectMapper().writer().writeValueAsString(b), + null); + + // A data record on this exception should take precedence over fetching a record + // from the store / cache + doThrow(new IdempotencyItemAlreadyExistsException( + "Test message", + new RuntimeException("Test Cause"), + dr)) + .when(store).saveInProgress(any(), any(), any()); + + // WHEN + IdempotencyEnabledFunction function = new IdempotencyEnabledFunction(); + Basket basket = function.handleRequest(p, context); + + // THEN + assertThat(basket).isEqualTo(b); + assertThat(function.handlerCalled()).isFalse(); + // Should never call the store because item is already present on + // IdempotencyItemAlreadyExistsException + verify(store, never()).getRecord(any(), any()); + } + + @Test + void secondCall_notExpired_shouldGetStringFromStore() { + // GIVEN + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("id") + .build()) + .configure(); + + doThrow(new IdempotencyItemAlreadyExistsException()).when(store).saveInProgress(any(), any(), any()); + + Product p = new Product(42, "fake product", 12); + DataRecord dr = new DataRecord( + "42", + DataRecord.Status.COMPLETED, + Instant.now().plus(356, SECONDS).getEpochSecond(), + p.getName(), + null); + doReturn(dr).when(store).getRecord(any(), any()); + + // WHEN + IdempotencyStringFunction function = new IdempotencyStringFunction(); + String name = function.handleRequest(p, context); + + // THEN + assertThat(name).isEqualTo(p.getName()); + assertThat(function.handlerCalled()).isFalse(); + } + + @Test + void secondCall_notExpired_shouldGetStringFromStoreWithResponseHook() { + // GIVEN + final String RESPONSE_HOOK_SUFFIX = " ResponseHook"; + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("id") + .withResponseHook((responseData, dataRecord) -> { + responseData += RESPONSE_HOOK_SUFFIX; + return responseData; + }) + .build()) + .configure(); + + doThrow(new IdempotencyItemAlreadyExistsException()).when(store).saveInProgress(any(), any(), any()); + + Product p = new Product(42, "fake product", 12); + DataRecord dr = new DataRecord( + "42", + DataRecord.Status.COMPLETED, + Instant.now().plus(356, SECONDS).getEpochSecond(), + p.getName(), + null); + doReturn(dr).when(store).getRecord(any(), any()); + + // WHEN + IdempotencyStringFunction function = new IdempotencyStringFunction(); + String name = function.handleRequest(p, context); + + // THEN + assertThat(name).isEqualTo(p.getName() + RESPONSE_HOOK_SUFFIX); + assertThat(function.handlerCalled()).isFalse(); + } + + @Test + void secondCall_inProgress_shouldThrowIdempotencyAlreadyInProgressException() + throws JsonProcessingException { + // GIVEN + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("id") + .build()) + .configure(); + + doThrow(new IdempotencyItemAlreadyExistsException()).when(store).saveInProgress(any(), any(), any()); + + Product p = new Product(42, "fake product", 12); + Basket b = new Basket(p); + OptionalLong timestampInFuture = OptionalLong.of(Instant.now().toEpochMilli() + 1000); // timeout not expired + // (in 1sec) + DataRecord dr = new DataRecord( + "42", + DataRecord.Status.INPROGRESS, + Instant.now().plus(356, SECONDS).getEpochSecond(), + JsonConfig.get().getObjectMapper().writer().writeValueAsString(b), + null, + timestampInFuture); + doReturn(dr).when(store).getRecord(any(), any()); + + // THEN + IdempotencyEnabledFunction function = new IdempotencyEnabledFunction(); + assertThatThrownBy(() -> function.handleRequest(p, context)).isInstanceOf( + IdempotencyAlreadyInProgressException.class); + } + + @Test + void secondCall_inProgress_lambdaTimeout_timeoutExpired_shouldThrowInconsistentState() + throws JsonProcessingException { + // GIVEN + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("id") + .build()) + .configure(); + + doThrow(new IdempotencyItemAlreadyExistsException()).when(store).saveInProgress(any(), any(), any()); + + Product p = new Product(42, "fake product", 12); + Basket b = new Basket(p); + OptionalLong timestampInThePast = OptionalLong.of(Instant.now().toEpochMilli() - 100); // timeout expired 100ms + // ago + DataRecord dr = new DataRecord( + "42", + DataRecord.Status.INPROGRESS, + Instant.now().plus(356, SECONDS).getEpochSecond(), + JsonConfig.get().getObjectMapper().writer().writeValueAsString(b), + null, + timestampInThePast); + doReturn(dr).when(store).getRecord(any(), any()); + + // THEN + IdempotencyEnabledFunction function = new IdempotencyEnabledFunction(); + assertThatThrownBy(() -> function.handleRequest(p, context)).isInstanceOf( + IdempotencyInconsistentStateException.class); + } + + @Test + void functionThrowException_shouldDeleteRecord_andThrowFunctionException() { + // GIVEN + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("id") + .build()) + .configure(); + + // WHEN / THEN + IdempotencyWithErrorFunction function = new IdempotencyWithErrorFunction(); + + Product p = new Product(42, "fake product", 12); + assertThatThrownBy(() -> function.handleRequest(p, context)) + .isInstanceOf(IndexOutOfBoundsException.class); + + verify(store).deleteRecord(any(), any(IndexOutOfBoundsException.class)); + } + + @Test + @SetEnvironmentVariable(key = Constants.IDEMPOTENCY_DISABLED_ENV, value = "true") + void testIdempotencyDisabled_shouldJustRunTheFunction() { + // GIVEN + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder() + .withEventKeyJMESPath("id") + .build()) + .configure(); + + // WHEN + IdempotencyEnabledFunction function = new IdempotencyEnabledFunction(); + Product p = new Product(42, "fake product", 12); + Basket basket = function.handleRequest(p, context); + + // THEN + verifyNoInteractions(store); + assertThat(basket.getProducts()).hasSize(1); + assertThat(function.handlerCalled()).isTrue(); + } + + @Test + void idempotencyOnSubMethodAnnotated_firstCall_shouldPutInStore() { + Idempotency.config() + .withPersistenceStore(store) + .configure(); + + // WHEN + boolean registerContext = true; + IdempotencyInternalFunction function = new IdempotencyInternalFunction(registerContext); + Product p = new Product(42, "fake product", 12); + Basket basket = function.handleRequest(p, context); + + // THEN + assertThat(basket.getProducts()).hasSize(2); + assertThat(function.subMethodCalled()).isTrue(); + + ArgumentCaptor<JsonNode> nodeCaptor = ArgumentCaptor.forClass(JsonNode.class); + ArgumentCaptor<OptionalInt> expiryCaptor = ArgumentCaptor.forClass(OptionalInt.class); + verify(store).saveInProgress(nodeCaptor.capture(), any(), expiryCaptor.capture()); + assertThat(nodeCaptor.getValue().asText()).isEqualTo("fake"); + assertThat(expiryCaptor.getValue().orElse(-1)).isEqualTo(30000); + + ArgumentCaptor<Basket> resultCaptor = ArgumentCaptor.forClass(Basket.class); + verify(store).saveSuccess(any(), resultCaptor.capture(), any()); + assertThat(resultCaptor.getValue().getProducts()).contains(basket.getProducts().get(0), + new Product(0, "fake", 0)); + } + + @Test + void idempotencyOnSubMethodAnnotated_firstCall_contextNotRegistered_shouldPutInStore() { + Idempotency.config() + .withPersistenceStore(store) + .configure(); + + // WHEN + boolean registerContext = false; + IdempotencyInternalFunction function = new IdempotencyInternalFunction(registerContext); + Product p = new Product(42, "fake product", 12); + Basket basket = function.handleRequest(p, context); + + // THEN + assertThat(basket.getProducts()).hasSize(2); + assertThat(function.subMethodCalled()).isTrue(); + + ArgumentCaptor<JsonNode> nodeCaptor = ArgumentCaptor.forClass(JsonNode.class); + ArgumentCaptor<OptionalInt> expiryCaptor = ArgumentCaptor.forClass(OptionalInt.class); + verify(store).saveInProgress(nodeCaptor.capture(), any(), expiryCaptor.capture()); + assertThat(nodeCaptor.getValue().asText()).isEqualTo("fake"); + assertThat(expiryCaptor.getValue()).isEmpty(); + + ArgumentCaptor<Basket> resultCaptor = ArgumentCaptor.forClass(Basket.class); + verify(store).saveSuccess(any(), resultCaptor.capture(), any()); + assertThat(resultCaptor.getValue().getProducts()).contains(basket.getProducts().get(0), + new Product(0, "fake", 0)); + } + + @Test + void idempotencyOnSubMethodAnnotated_secondCall_notExpired_shouldGetFromStore() + throws JsonProcessingException { + // GIVEN + Idempotency.config() + .withPersistenceStore(store) + .configure(); + + doThrow(new IdempotencyItemAlreadyExistsException()).when(store).saveInProgress(any(), any(), any()); + + Product p = new Product(42, "fake product", 12); + Basket b = new Basket(p); + DataRecord dr = new DataRecord( + "fake", + DataRecord.Status.COMPLETED, + Instant.now().plus(356, SECONDS).getEpochSecond(), + JsonConfig.get().getObjectMapper().writer().writeValueAsString(b), + null); + doReturn(dr).when(store).getRecord(any(), any()); + + // WHEN + IdempotencyInternalFunction function = new IdempotencyInternalFunction(false); + Basket basket = function.handleRequest(p, context); + + // THEN + assertThat(basket).isEqualTo(b); + assertThat(function.subMethodCalled()).isFalse(); + } + + @Test + void idempotencyOnSubMethodAnnotated_keyJMESPath_shouldPutInStoreWithKey() { + BasePersistenceStore persistenceStore = spy(BasePersistenceStore.class); + + Idempotency.config() + .withPersistenceStore(persistenceStore) + .withConfig(IdempotencyConfig.builder().withEventKeyJMESPath("id").build()) + .configure(); + + // WHEN + IdempotencyInternalFunctionInternalKey function = new IdempotencyInternalFunctionInternalKey(); + Product p = new Product(42, "fake product", 12); + function.handleRequest(p, context); + + // THEN + ArgumentCaptor<DataRecord> recordCaptor = ArgumentCaptor.forClass(DataRecord.class); + verify(persistenceStore).putRecord(recordCaptor.capture(), any()); + // a1d0c6e83f027327d8461063f4ac58a6 = MD5(42) + assertThat(recordCaptor.getValue().getIdempotencyKey()).isEqualTo( + "testFunction.createBasket#a1d0c6e83f027327d8461063f4ac58a6"); + } + + @Test + void idempotencyOnSubMethodAnnotated_keyJMESPathArray_shouldPutInStoreWithKey() { + BasePersistenceStore persistenceStore = spy(BasePersistenceStore.class); + + Idempotency.config() + .withPersistenceStore(persistenceStore) + .withConfig(IdempotencyConfig.builder().withEventKeyJMESPath("[id,name]").build()) + .configure(); + + // WHEN + IdempotencyInternalFunctionInternalKey function = new IdempotencyInternalFunctionInternalKey(); + Product p = new Product(42, "fake product", 12); + function.handleRequest(p, context); + + // THEN + ArgumentCaptor<DataRecord> recordCaptor = ArgumentCaptor.forClass(DataRecord.class); + verify(persistenceStore).putRecord(recordCaptor.capture(), any()); + // eec7cd392d9e3bb20deb2c9676697c3c = MD5([42,"fake product"]) + assertThat(recordCaptor.getValue().getIdempotencyKey()).isEqualTo( + "testFunction.createBasket#eec7cd392d9e3bb20deb2c9676697c3c"); + } + + @Test + void idempotencyOnSubMethodNotAnnotated_shouldThrowException() { + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder().build()).configure(); + + // WHEN + IdempotencyInternalFunctionInvalid function = new IdempotencyInternalFunctionInvalid(); + Product p = new Product(42, "fake product", 12); + + // THEN + assertThatThrownBy(() -> function.handleRequest(p, context)).isInstanceOf( + IdempotencyConfigurationException.class); + } + + @Test + void idempotencyOnSubMethodVoid_shouldThrowException() { + Idempotency.config() + .withPersistenceStore(store) + .withConfig(IdempotencyConfig.builder().build()).configure(); + + // WHEN + IdempotencyInternalFunctionVoid function = new IdempotencyInternalFunctionVoid(); + Product p = new Product(42, "fake product", 12); + + // THEN + assertThatThrownBy(() -> function.handleRequest(p, context)).isInstanceOf( + IdempotencyConfigurationException.class); + } + + @Test + void concurrentInvocations_shouldNotLeakContext() throws Exception { + Idempotency.config() + .withPersistenceStore(store) + .configure(); + + // Use IdempotencyInternalFunction which calls registerLambdaContext + IdempotencyInternalFunction function = new IdempotencyInternalFunction(true); + + // GIVEN + int threadCount = 10; + Thread[] threads = new Thread[threadCount]; + Context[] capturedContexts = new Context[threadCount]; + int[] capturedRemainingTimes = new int[threadCount]; + boolean[] success = new boolean[threadCount]; + + // WHEN - Multiple threads call handleRequest with different contexts + for (int i = 0; i < threadCount; i++) { + final int threadIndex = i; + final int expectedTime = (i + 1) * 1000; // 1000, 2000, 3000, ..., 10000 + + final Context threadContext = new TestLambdaContext() { + @Override + public int getRemainingTimeInMillis() { + return expectedTime; + } + }; + + threads[i] = new Thread(() -> { + try { + Product p = new Product(threadIndex, "product" + threadIndex, 10); + function.handleRequest(p, threadContext); + + // Capture the context that was actually stored in ThreadLocal by this thread + Context captured = Idempotency.getInstance().getConfig().getLambdaContext(); + capturedContexts[threadIndex] = captured; + capturedRemainingTimes[threadIndex] = captured != null ? captured.getRemainingTimeInMillis() : -1; + success[threadIndex] = true; + } catch (Exception e) { + success[threadIndex] = false; + } + }); + } + + // Start all threads + for (Thread thread : threads) { + thread.start(); + } + + // Wait for all threads to complete + for (Thread thread : threads) { + thread.join(); + } + + // THEN - All threads should complete successfully + for (boolean result : success) { + assertThat(result).isTrue(); + } + + // THEN - Each thread should have captured its own context (no leakage) + for (int i = 0; i < threadCount; i++) { + int expectedTime = (i + 1) * 1000; + assertThat(capturedRemainingTimes[i]) + .as("Thread %d should have remaining time %d", i, expectedTime) + .isEqualTo(expectedTime); + assertThat(capturedContexts[i]).as("Thread %d should have non-null context", i).isNotNull(); + } + } + +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/internal/cache/LRUCacheTest.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/internal/cache/LRUCacheTest.java new file mode 100644 index 000000000..d14f07315 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/internal/cache/LRUCacheTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.internal.cache; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class LRUCacheTest { + + @Test + void testLRUCache_shouldRemoveEldestEntry() { + LRUCache<String, String> cache = new LRUCache<>(3); + cache.put("key1", "value1"); + cache.put("key2", "value2"); + cache.put("key3", "value3"); + cache.put("key4", "value4"); + cache.put("key5", "value5"); + + assertThat(cache).hasSize(3).doesNotContainKeys("key1", "key2"); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/model/Basket.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/model/Basket.java new file mode 100644 index 000000000..a17bb8abf --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/model/Basket.java @@ -0,0 +1,60 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.model; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class Basket { + private List<Product> products = new ArrayList<>(); + + public Basket() { + } + + public Basket(Product... p) { + products.addAll(Arrays.asList(p)); + } + + public List<Product> getProducts() { + return products; + } + + public void setProducts(List<Product> products) { + this.products = products; + } + + public void add(Product product) { + products.add(product); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Basket basket = (Basket) o; + return products.equals(basket.products); + } + + @Override + public int hashCode() { + return Objects.hash(products); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/model/Product.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/model/Product.java new file mode 100644 index 000000000..7fa191d82 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/model/Product.java @@ -0,0 +1,75 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.model; + +import java.util.Objects; + +public class Product { + private long id; + + private String name; + + private double price; + + public Product() { + } + + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Product product = (Product) o; + return id == product.id && Double.compare(product.price, price) == 0 && Objects.equals(name, product.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, price); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/BasePersistenceStoreTest.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/BasePersistenceStoreTest.java new file mode 100644 index 000000000..8e46de1cc --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/BasePersistenceStoreTest.java @@ -0,0 +1,444 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.persistence; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.OptionalInt; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.tests.EventLoader; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.DoubleNode; +import com.fasterxml.jackson.databind.node.TextNode; + +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemAlreadyExistsException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemNotFoundException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyKeyException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyValidationException; +import software.amazon.lambda.powertools.idempotency.internal.cache.LRUCache; +import software.amazon.lambda.powertools.idempotency.model.Product; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +class BasePersistenceStoreTest { + + private DataRecord dr; + private BasePersistenceStore persistenceStore; + private int status = 0; + private String validationHash; + + @BeforeEach + void setup() { + validationHash = null; + dr = null; + status = -1; + persistenceStore = new BasePersistenceStore() { + @Override + public DataRecord getRecord(String idempotencyKey) throws IdempotencyItemNotFoundException { + status = 0; + return new DataRecord(idempotencyKey, DataRecord.Status.INPROGRESS, + Instant.now().plus(3600, ChronoUnit.SECONDS).getEpochSecond(), "Response", validationHash); + } + + @Override + public void putRecord(DataRecord dataRecord, Instant now) throws IdempotencyItemAlreadyExistsException { + dr = dataRecord; + status = 1; + } + + @Override + public void updateRecord(DataRecord dataRecord) { + dr = dataRecord; + status = 2; + } + + @Override + public void deleteRecord(String idempotencyKey) { + dr = null; + status = 3; + } + }; + } + + @Test + void saveInProgress_defaultConfig() { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + persistenceStore.configure(IdempotencyConfig.builder().build(), null); + + Instant now = Instant.now(); + persistenceStore.saveInProgress(JsonConfig.get().getObjectMapper().valueToTree(event), now, + OptionalInt.empty()); + assertThat(dr.getStatus()).isEqualTo(DataRecord.Status.INPROGRESS); + assertThat(dr.getExpiryTimestamp()).isEqualTo(now.plus(3600, ChronoUnit.SECONDS).getEpochSecond()); + assertThat(dr.getResponseData()).isNull(); + assertThat(dr.getIdempotencyKey()).isEqualTo("testFunction#8d6a8f173b46479eff55e0997864a514"); + assertThat(dr.getPayloadHash()).isEmpty(); + assertThat(dr.getInProgressExpiryTimestamp()).isEmpty(); + assertThat(status).isEqualTo(1); + } + + @Test + void saveInProgress_withRemainingTime() { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + persistenceStore.configure(IdempotencyConfig.builder().build(), null); + + int lambdaTimeoutMs = 30000; + Instant now = Instant.now(); + persistenceStore.saveInProgress(JsonConfig.get().getObjectMapper().valueToTree(event), now, + OptionalInt.of(lambdaTimeoutMs)); + assertThat(dr.getStatus()).isEqualTo(DataRecord.Status.INPROGRESS); + assertThat(dr.getExpiryTimestamp()).isEqualTo(now.plus(3600, ChronoUnit.SECONDS).getEpochSecond()); + assertThat(dr.getResponseData()).isNull(); + assertThat(dr.getIdempotencyKey()).isEqualTo("testFunction#8d6a8f173b46479eff55e0997864a514"); + assertThat(dr.getPayloadHash()).isEmpty(); + assertThat(dr.getInProgressExpiryTimestamp().orElse(-1)).isEqualTo( + now.plus(lambdaTimeoutMs, ChronoUnit.MILLIS).toEpochMilli()); + assertThat(status).isEqualTo(1); + } + + @Test + void saveInProgress_jmespath() { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + persistenceStore.configure(IdempotencyConfig.builder() + .withEventKeyJMESPath("powertools_json(body).id") + .build(), "myfunc"); + + Instant now = Instant.now(); + persistenceStore.saveInProgress(JsonConfig.get().getObjectMapper().valueToTree(event), now, + OptionalInt.empty()); + assertThat(dr.getStatus()).isEqualTo(DataRecord.Status.INPROGRESS); + assertThat(dr.getExpiryTimestamp()).isEqualTo(now.plus(3600, ChronoUnit.SECONDS).getEpochSecond()); + assertThat(dr.getResponseData()).isNull(); + assertThat(dr.getIdempotencyKey()).isEqualTo("testFunction.myfunc#2fef178cc82be5ce3da6c5e0466a6182"); + assertThat(dr.getPayloadHash()).isEmpty(); + assertThat(status).isEqualTo(1); + } + + @Test + void saveInProgress_jmespath_NotFound_shouldThrowException() { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + persistenceStore.configure(IdempotencyConfig.builder() + .withEventKeyJMESPath("unavailable") + .withThrowOnNoIdempotencyKey(true) // should throw + .build(), ""); + Instant now = Instant.now(); + assertThatThrownBy( + () -> persistenceStore.saveInProgress(JsonConfig.get().getObjectMapper().valueToTree(event), now, + OptionalInt.empty())) + .isInstanceOf(IdempotencyKeyException.class) + .hasMessageContaining("No data found to create a hashed idempotency key"); + assertThat(status).isEqualTo(-1); + } + + @Test + void saveInProgress_jmespath_NotFound_shouldNotPersist() { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + persistenceStore.configure(IdempotencyConfig.builder() + .withEventKeyJMESPath("unavailable") + .build(), ""); + Instant now = Instant.now(); + persistenceStore.saveInProgress(JsonConfig.get().getObjectMapper().valueToTree(event), now, + OptionalInt.empty()); + assertThat(dr).isNull(); + assertThat(status).isEqualTo(-1); + } + + @Test + void saveInProgress_withLocalCache_NotExpired_ShouldThrowException() { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + LRUCache<String, DataRecord> cache = new LRUCache<>(2); + persistenceStore.configure(IdempotencyConfig.builder() + .withUseLocalCache(true) + .withEventKeyJMESPath("powertools_json(body).id") + .build(), null, cache); + Instant now = Instant.now(); + cache.put("testFunction#2fef178cc82be5ce3da6c5e0466a6182", + new DataRecord( + "testFunction#2fef178cc82be5ce3da6c5e0466a6182", + DataRecord.Status.INPROGRESS, + now.plus(3600, ChronoUnit.SECONDS).getEpochSecond(), + null, null)); + assertThatThrownBy( + () -> persistenceStore.saveInProgress(JsonConfig.get().getObjectMapper().valueToTree(event), now, + OptionalInt.empty())) + .isInstanceOf(IdempotencyItemAlreadyExistsException.class); + assertThat(status).isEqualTo(-1); + } + + @Test + void saveInProgress_withLocalCache_Expired_ShouldRemoveFromCache() { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + LRUCache<String, DataRecord> cache = new LRUCache<>(2); + persistenceStore.configure(IdempotencyConfig.builder() + .withEventKeyJMESPath("powertools_json(body).id") + .withUseLocalCache(true) + .withExpiration(Duration.of(2, ChronoUnit.SECONDS)) + .build(), null, cache); + Instant now = Instant.now(); + cache.put("testFunction#2fef178cc82be5ce3da6c5e0466a6182", + new DataRecord( + "testFunction#2fef178cc82be5ce3da6c5e0466a6182", + DataRecord.Status.INPROGRESS, + now.minus(3, ChronoUnit.SECONDS).getEpochSecond(), + null, null)); + persistenceStore.saveInProgress(JsonConfig.get().getObjectMapper().valueToTree(event), now, + OptionalInt.empty()); + assertThat(dr.getStatus()).isEqualTo(DataRecord.Status.INPROGRESS); + assertThat(cache).isEmpty(); + assertThat(status).isEqualTo(1); + } + + @Test + void saveSuccess_shouldUpdateRecord() throws JsonProcessingException { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + LRUCache<String, DataRecord> cache = new LRUCache<>(2); + persistenceStore.configure(IdempotencyConfig.builder().build(), null, cache); + + Product product = new Product(34543, "product", 42); + Instant now = Instant.now(); + persistenceStore.saveSuccess(JsonConfig.get().getObjectMapper().valueToTree(event), product, now); + + assertThat(dr.getStatus()).isEqualTo(DataRecord.Status.COMPLETED); + assertThat(dr.getExpiryTimestamp()).isEqualTo(now.plus(3600, ChronoUnit.SECONDS).getEpochSecond()); + assertThat(dr.getResponseData()).isEqualTo(JsonConfig.get().getObjectMapper().writeValueAsString(product)); + assertThat(dr.getIdempotencyKey()).isEqualTo("testFunction#8d6a8f173b46479eff55e0997864a514"); + assertThat(dr.getPayloadHash()).isEmpty(); + assertThat(status).isEqualTo(2); + assertThat(cache).isEmpty(); + } + + @Test + void saveSuccess_withCacheEnabled_shouldSaveInCache() throws JsonProcessingException { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + LRUCache<String, DataRecord> cache = new LRUCache<>(2); + persistenceStore.configure(IdempotencyConfig.builder() + .withUseLocalCache(true).build(), null, cache); + + Product product = new Product(34543, "product", 42); + Instant now = Instant.now(); + persistenceStore.saveSuccess(JsonConfig.get().getObjectMapper().valueToTree(event), product, now); + + assertThat(status).isEqualTo(2); + assertThat(cache).hasSize(1); + DataRecord cachedDr = cache.get("testFunction#8d6a8f173b46479eff55e0997864a514"); + assertThat(cachedDr.getStatus()).isEqualTo(DataRecord.Status.COMPLETED); + assertThat(cachedDr.getExpiryTimestamp()).isEqualTo(now.plus(3600, ChronoUnit.SECONDS).getEpochSecond()); + assertThat(cachedDr.getResponseData()) + .isEqualTo(JsonConfig.get().getObjectMapper().writeValueAsString(product)); + assertThat(cachedDr.getIdempotencyKey()).isEqualTo("testFunction#8d6a8f173b46479eff55e0997864a514"); + assertThat(cachedDr.getPayloadHash()).isEmpty(); + } + + @Test + void getRecord_shouldReturnRecordFromPersistence() + throws IdempotencyItemNotFoundException, IdempotencyValidationException { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + LRUCache<String, DataRecord> cache = new LRUCache<>(2); + persistenceStore.configure(IdempotencyConfig.builder().build(), "myfunc", cache); + + Instant now = Instant.now(); + DataRecord freshDr = persistenceStore.getRecord(JsonConfig.get().getObjectMapper().valueToTree(event), now); + assertThat(freshDr.getIdempotencyKey()).isEqualTo("testFunction.myfunc#8d6a8f173b46479eff55e0997864a514"); + assertThat(freshDr.getStatus()).isEqualTo(DataRecord.Status.INPROGRESS); + assertThat(freshDr.getResponseData()).isEqualTo("Response"); + assertThat(status).isZero(); + } + + @Test + void getRecord_cacheEnabledNotExpired_shouldReturnRecordFromCache() + throws IdempotencyItemNotFoundException, IdempotencyValidationException { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + LRUCache<String, DataRecord> cache = new LRUCache<>(2); + persistenceStore.configure(IdempotencyConfig.builder() + .withUseLocalCache(true).build(), "myfunc", cache); + + Instant now = Instant.now(); + DataRecord dr1 = new DataRecord( + "testFunction.myfunc#8d6a8f173b46479eff55e0997864a514", + DataRecord.Status.COMPLETED, + now.plus(3600, ChronoUnit.SECONDS).getEpochSecond(), + "result of the function", + null); + cache.put("testFunction.myfunc#8d6a8f173b46479eff55e0997864a514", dr1); + + DataRecord dr2 = persistenceStore.getRecord(JsonConfig.get().getObjectMapper().valueToTree(event), now); + assertThat(dr2.getIdempotencyKey()).isEqualTo("testFunction.myfunc#8d6a8f173b46479eff55e0997864a514"); + assertThat(dr2.getStatus()).isEqualTo(DataRecord.Status.COMPLETED); + assertThat(dr2.getResponseData()).isEqualTo("result of the function"); + assertThat(status).isEqualTo(-1); // getRecord must not be called (retrieve from cache) + } + + @Test + void getRecord_cacheEnabledExpired_shouldReturnRecordFromPersistence() + throws IdempotencyItemNotFoundException, IdempotencyValidationException { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + LRUCache<String, DataRecord> cache = new LRUCache<>(2); + persistenceStore.configure(IdempotencyConfig.builder() + .withUseLocalCache(true).build(), "myfunc", cache); + + Instant now = Instant.now(); + DataRecord dr1 = new DataRecord( + "testFunction.myfunc#8d6a8f173b46479eff55e0997864a514", + DataRecord.Status.COMPLETED, + now.minus(3, ChronoUnit.SECONDS).getEpochSecond(), + "result of the function", + null); + cache.put("testFunction.myfunc#8d6a8f173b46479eff55e0997864a514", dr1); + + DataRecord dr2 = persistenceStore.getRecord(JsonConfig.get().getObjectMapper().valueToTree(event), now); + assertThat(dr2.getIdempotencyKey()).isEqualTo("testFunction.myfunc#8d6a8f173b46479eff55e0997864a514"); + assertThat(dr2.getStatus()).isEqualTo(DataRecord.Status.INPROGRESS); + assertThat(dr2.getResponseData()).isEqualTo("Response"); + assertThat(status).isZero(); + assertThat(cache).isEmpty(); + } + + @Test + void getRecord_invalidPayload_shouldThrowValidationException() { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + persistenceStore.configure(IdempotencyConfig.builder() + .withEventKeyJMESPath("powertools_json(body).id") + .withPayloadValidationJMESPath("powertools_json(body).message") + .build(), + "myfunc"); + + this.validationHash = "different hash"; // "Lambda rocks" ==> 70c24d88041893f7fbab4105b76fd9e1 + + assertThatThrownBy( + () -> persistenceStore.getRecord(JsonConfig.get().getObjectMapper().valueToTree(event), Instant.now())) + .isInstanceOf(IdempotencyValidationException.class); + } + + @Test + void saveInProgress_invalidPayload_shouldThrowValidationException() { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + persistenceStore = new BasePersistenceStore() { + @Override + public DataRecord getRecord(String idempotencyKey) throws IdempotencyItemNotFoundException { + return new DataRecord(idempotencyKey, DataRecord.Status.INPROGRESS, + Instant.now().plus(3600, ChronoUnit.SECONDS).getEpochSecond(), "Response", "different hash"); + } + + @Override + public void putRecord(DataRecord dataRecord, Instant now) throws IdempotencyItemAlreadyExistsException { + DataRecord existingRecord = new DataRecord( + dataRecord.getIdempotencyKey(), + DataRecord.Status.INPROGRESS, + Instant.now().plus(3600, ChronoUnit.SECONDS).getEpochSecond(), + null, + "different hash"); + throw new IdempotencyItemAlreadyExistsException("Item already exists", new Exception(), existingRecord); + } + + @Override + public void updateRecord(DataRecord dataRecord) { + // Not needed for this test. + } + + @Override + public void deleteRecord(String idempotencyKey) { + // Not needed for this test. + + } + }; + + persistenceStore.configure(IdempotencyConfig.builder() + .withEventKeyJMESPath("powertools_json(body).id") + .withPayloadValidationJMESPath("powertools_json(body).message") + .build(), + "myfunc"); + + Instant now = Instant.now(); + OptionalInt remainingTime = OptionalInt.empty(); + JsonNode eventJson = JsonConfig.get().getObjectMapper().valueToTree(event); + assertThatThrownBy(() -> persistenceStore.saveInProgress(eventJson, now, remainingTime)) + .isInstanceOf(IdempotencyValidationException.class); + } + + @Test + void deleteRecord_shouldDeleteRecordFromPersistence() { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + persistenceStore.configure(IdempotencyConfig.builder().build(), null); + + persistenceStore.deleteRecord(JsonConfig.get().getObjectMapper().valueToTree(event), new ArithmeticException()); + assertThat(status).isEqualTo(3); + } + + @Test + void deleteRecord_cacheEnabled_shouldDeleteRecordFromCache() { + APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json"); + LRUCache<String, DataRecord> cache = new LRUCache<>(2); + persistenceStore.configure(IdempotencyConfig.builder() + .withUseLocalCache(true).build(), null, cache); + + cache.put("testFunction#8d6a8f173b46479eff55e0997864a514", + new DataRecord("testFunction#8d6a8f173b46479eff55e0997864a514", DataRecord.Status.COMPLETED, 123, null, + null)); + persistenceStore.deleteRecord(JsonConfig.get().getObjectMapper().valueToTree(event), new ArithmeticException()); + assertThat(status).isEqualTo(3); + assertThat(cache).isEmpty(); + } + + @Test + void generateHashString_shouldGenerateMd5ofString() { + persistenceStore.configure(IdempotencyConfig.builder().build(), null); + String expectedHash = "70c24d88041893f7fbab4105b76fd9e1"; // MD5(Lambda rocks) + String generatedHash = persistenceStore.generateHash(new TextNode("Lambda rocks")); + assertThat(generatedHash).isEqualTo(expectedHash); + } + + @Test + void generateHashObject_shouldGenerateMd5ofJsonObject() { + persistenceStore.configure(IdempotencyConfig.builder().build(), null); + Product product = new Product(42, "Product", 12); + String expectedHash = "e71c41727848ed68050d82740894c29b"; // MD5({"id":42,"name":"Product","price":12.0}) + String generatedHash = persistenceStore.generateHash(JsonConfig.get().getObjectMapper().valueToTree(product)); + assertThat(generatedHash).isEqualTo(expectedHash); + } + + @Test + void generateHashDouble_shouldGenerateMd5ofDouble() { + persistenceStore.configure(IdempotencyConfig.builder().build(), null); + String expectedHash = "bb84c94278119c8838649706df4db42b"; // MD5(256.42) + String generatedHash = persistenceStore.generateHash(new DoubleNode(256.42)); + assertThat(generatedHash).isEqualTo(expectedHash); + } + + @Test + void generateHashString_withSha256Algorithm_shouldGenerateSha256ofString() { + persistenceStore.configure(IdempotencyConfig.builder().withHashFunction("SHA-256").build(), null); + String expectedHash = "e6139efa88ef3337e901e826e6f327337f414860fb499d9f26eefcff21d719af"; // SHA-256(Lambda + // rocks) + String generatedHash = persistenceStore.generateHash(new TextNode("Lambda rocks")); + assertThat(generatedHash).isEqualTo(expectedHash); + } + + @Test + void generateHashString_unknownAlgorithm_shouldGenerateMd5ofString() { + persistenceStore.configure(IdempotencyConfig.builder().withHashFunction("HASH").build(), null); + String expectedHash = "70c24d88041893f7fbab4105b76fd9e1"; // MD5(Lambda rocks) + String generatedHash = persistenceStore.generateHash(new TextNode("Lambda rocks")); + assertThat(generatedHash).isEqualTo(expectedHash); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/testutils/InMemoryPersistenceStore.java b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/testutils/InMemoryPersistenceStore.java new file mode 100644 index 000000000..5fec80a3c --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/testutils/InMemoryPersistenceStore.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.testutils; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemAlreadyExistsException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemNotFoundException; +import software.amazon.lambda.powertools.idempotency.persistence.BasePersistenceStore; +import software.amazon.lambda.powertools.idempotency.persistence.DataRecord; + +/** + * In-memory implementation of BasePersistenceStore for testing purposes. + */ +public class InMemoryPersistenceStore extends BasePersistenceStore { + private final Map<String, DataRecord> data = new HashMap<>(); + + @Override + public DataRecord getRecord(String idempotencyKey) throws IdempotencyItemNotFoundException { + DataRecord dr = data.get(idempotencyKey); + if (dr == null) { + throw new IdempotencyItemNotFoundException(idempotencyKey); + } + return dr; + } + + @Override + public void putRecord(DataRecord dr, Instant now) throws IdempotencyItemAlreadyExistsException { + if (data.containsKey(dr.getIdempotencyKey())) { + throw new IdempotencyItemAlreadyExistsException(); + } + data.put(dr.getIdempotencyKey(), dr); + } + + @Override + public void updateRecord(DataRecord dr) { + data.put(dr.getIdempotencyKey(), dr); + } + + @Override + public void deleteRecord(String idempotencyKey) { + data.remove(idempotencyKey); + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/resources/apigw_event.json b/powertools-idempotency/powertools-idempotency-core/src/test/resources/apigw_event.json new file mode 100644 index 000000000..4f5f95db0 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/resources/apigw_event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"Lambda rocks\", \"id\": 43876123454654}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/powertools-idempotency/powertools-idempotency-core/src/test/resources/simplelogger.properties b/powertools-idempotency/powertools-idempotency-core/src/test/resources/simplelogger.properties new file mode 100644 index 000000000..dcded6172 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-core/src/test/resources/simplelogger.properties @@ -0,0 +1,7 @@ +org.slf4j.simpleLogger.logFile=target/idempotency-core-test.log +org.slf4j.simpleLogger.defaultLogLevel=warn +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS +org.slf4j.simpleLogger.showThreadName=false +org.slf4j.simpleLogger.showLogName=true +org.slf4j.simpleLogger.showShortLogName=false diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/pom.xml b/powertools-idempotency/powertools-idempotency-dynamodb/pom.xml new file mode 100644 index 000000000..d223e0d2f --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/pom.xml @@ -0,0 +1,292 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>powertools-idempotency-dynamodb</artifactId> + <packaging>jar</packaging> + + <name>Powertools for AWS Lambda (Java) library Idempotency - DynamoDB</name> + <description> + DynamoDB implementation for the idempotency module + </description> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-core</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>url-connection-client</artifactId> + <version>${aws.sdk.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>dynamodb</artifactId> + <exclusions> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>netty-nio-client</artifactId> + </exclusion> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>apache-client</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + </dependency> + <dependency> + <groupId>org.crac</groupId> + <artifactId>crac</artifactId> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <!-- DynamoDB Local for testing --> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>DynamoDBLocal</artifactId> + <!-- >2.2.0 is not compatible with Java 11 anymore --> + <!-- https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocalHistory.html --> + <version>2.2.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>generate-graalvm-files</id> + <!-- The AWS DynamoDBClient uses com.amazonaws.xray.interceptors.TracingInterceptor if available on the + classpath. If the DynamoDB persistence store is used together with tracing, this would fail in GraalVM + otherwise since it tries to load the tracing interceptor at runtime. This makes sure it is included in + reflect-config.json --> + <dependencies> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-xray-recorder-sdk-aws-sdk-v2-instrumentor</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-idempotency-dynamodb,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-idempotency-dynamodb</imageName> + <exclusions> + <exclusion> + <groupId>com.amazonaws</groupId> + <artifactId>DynamoDBLocal</artifactId> + </exclusion> + </exclusions> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--enable-url-protocols=http</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + + <build> + <plugins> + <!-- Copy DynamoDB Local JAR --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>copy-dynamodb-local</id> + <phase>generate-test-resources</phase> + <goals> + <goal>copy-dependencies</goal> + </goals> + <configuration> + <includeScope>test</includeScope> + <excludeTransitive>false</excludeTransitive> + <!-- MDEP-187: Avoids Maven phase circular dependency by not trying to copy unpacked reactor + artifacts (powertools-idempotency-core) --> + <excludeGroupIds>software.amazon.lambda</excludeGroupIds> + <outputDirectory>${project.build.directory}/dynamodb-local</outputDirectory> + </configuration> + </execution> + </executions> + </plugin> + <!-- Start DynamoDB Local before tests --> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <executions> + <execution> + <id>start-dynamodb-local</id> + <phase>process-test-classes</phase> + <goals> + <goal>exec</goal> + </goals> + <configuration> + <executable>java</executable> + <workingDirectory>${project.build.directory}/dynamodb-local</workingDirectory> + <arguments> + <argument>-Djava.library.path=${project.build.directory}/dynamodb-local</argument> + <argument>-cp</argument> + <argument>${project.build.directory}/dynamodb-local/*</argument> + <argument>com.amazonaws.services.dynamodbv2.local.main.ServerRunner</argument> + <argument>-inMemory</argument> + <argument>-port</argument> + <argument>8000</argument> + </arguments> + <async>true</async> + <asyncDestroyOnShutdown>true</asyncDestroyOnShutdown> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>3.4.2</version> + <configuration> + <archive> + <manifestEntries> + <Automatic-Module-Name>software.amazon.awssdk.enhanced.dynamodb</Automatic-Module-Name> + </manifestEntries> + </archive> + </configuration> + </plugin> + <!-- Configure Surefire to expose external DynamoDB Local address --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <systemPropertyVariables> + <dynamodb.endpoint>http://localhost:8000</dynamodb.endpoint> + </systemPropertyVariables> + </configuration> + </plugin> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>${aspectj-maven-plugin.version}</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <Xlint>ignore</Xlint> + <encoding>${project.build.sourceEncoding}</encoding> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-idempotency-core</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + <goal>test-compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/main/java/software/amazon/lambda/powertools/idempotency/dynamodb/internal/IdempotencyDynamodbUserAgentInterceptor.java b/powertools-idempotency/powertools-idempotency-dynamodb/src/main/java/software/amazon/lambda/powertools/idempotency/dynamodb/internal/IdempotencyDynamodbUserAgentInterceptor.java new file mode 100644 index 000000000..98d7a7440 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/main/java/software/amazon/lambda/powertools/idempotency/dynamodb/internal/IdempotencyDynamodbUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.idempotency.dynamodb.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-idempotency-dynamodb module is on the classpath. + */ +public final class IdempotencyDynamodbUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("idempotency-dynamodb"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBPersistenceStore.java b/powertools-idempotency/powertools-idempotency-dynamodb/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBPersistenceStore.java new file mode 100644 index 000000000..c128fa5bd --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBPersistenceStore.java @@ -0,0 +1,479 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.persistence.dynamodb; + +import org.crac.Core; +import org.crac.Resource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; +import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; +import software.amazon.lambda.powertools.idempotency.Constants; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemAlreadyExistsException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemNotFoundException; +import software.amazon.lambda.powertools.idempotency.persistence.BasePersistenceStore; +import software.amazon.lambda.powertools.idempotency.persistence.DataRecord; +import software.amazon.lambda.powertools.idempotency.persistence.PersistenceStore; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.OptionalLong; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static software.amazon.lambda.powertools.common.internal.LambdaConstants.AWS_REGION_ENV; +import static software.amazon.lambda.powertools.common.internal.LambdaConstants.LAMBDA_FUNCTION_NAME_ENV; +import static software.amazon.lambda.powertools.idempotency.persistence.DataRecord.Status.INPROGRESS; + +/** + * DynamoDB version of the {@link PersistenceStore}. Will store idempotency data in DynamoDB.<br> + * Use the {@link Builder} to create a new instance. + */ +public final class DynamoDBPersistenceStore extends BasePersistenceStore implements PersistenceStore, Resource { + + public static final String IDEMPOTENCY = "idempotency"; + private static final Logger LOG = LoggerFactory.getLogger(DynamoDBPersistenceStore.class); + private final String tableName; + private final String keyAttr; + private final String staticPkValue; + private final String sortKeyAttr; + private final String expiryAttr; + + private final String inProgressExpiryAttr; + private final String statusAttr; + private final String dataAttr; + private final String validationAttr; + private final DynamoDbClient dynamoDbClient; + + /** + * Private: use the {@link Builder} to instantiate a new {@link DynamoDBPersistenceStore} + */ + private DynamoDBPersistenceStore(String tableName, + String keyAttr, + String staticPkValue, + String sortKeyAttr, + String expiryAttr, + String inProgressExpiryAttr, + String statusAttr, + String dataAttr, + String validationAttr, + DynamoDbClient client) { + this.tableName = tableName; + this.keyAttr = keyAttr; + this.staticPkValue = staticPkValue; + this.sortKeyAttr = sortKeyAttr; + this.expiryAttr = expiryAttr; + this.inProgressExpiryAttr = inProgressExpiryAttr; + this.statusAttr = statusAttr; + this.dataAttr = dataAttr; + this.validationAttr = validationAttr; + + if (client != null) { + this.dynamoDbClient = client; + } else { + String idempotencyDisabledEnv = System.getenv().get(Constants.IDEMPOTENCY_DISABLED_ENV); + if (idempotencyDisabledEnv == null || "false".equalsIgnoreCase(idempotencyDisabledEnv)) { + this.dynamoDbClient = DynamoDbClient.builder() + .httpClient(UrlConnectionHttpClient.builder().build()) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, + UserAgentConfigurator.getUserAgent(IDEMPOTENCY)).build()) + .region(Region.of(System.getenv(AWS_REGION_ENV))) + .build(); + } else { + // we do not want to create a DynamoDbClient if idempotency is disabled + // null is ok as idempotency won't be called + this.dynamoDbClient = null; + } + } + Core.getGlobalContext().register(this); + } + + /** + * Primes the persistent store by invoking the get record method with a key that doesn't exist. + * + * @param context + * @throws Exception + */ + @Override + public void beforeCheckpoint(org.crac.Context<? extends Resource> context) throws Exception { + try { + String primingRecordKey = "__invoke_prime__"; + Instant now = Instant.now(); + long expiry = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond(); + DataRecord primingDataRecord = new DataRecord( + primingRecordKey, + DataRecord.Status.COMPLETED, + expiry, + null, // no data + null // no validation + ); + putRecord(primingDataRecord, Instant.now()); + getRecord(primingRecordKey); + deleteRecord(primingRecordKey); + } catch (Exception unknown) { + // This is unexpected but we must continue without any interruption + } + } + + @Override + public void afterRestore(org.crac.Context<? extends Resource> context) throws Exception { + // This is a no-op, as we don't need to do anything after restore + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public DataRecord getRecord(String idempotencyKey) throws IdempotencyItemNotFoundException { + GetItemResponse response = dynamoDbClient.getItem( + GetItemRequest.builder() + .tableName(tableName) + .key(getKey(idempotencyKey)) + .consistentRead(true) + .build() + ); + + if (!response.hasItem()) { + throw new IdempotencyItemNotFoundException(idempotencyKey); + } + + return itemToRecord(response.item()); + } + + /** + * Store's the given idempotency record in the DDB store. If there + * is an existing record that has expired - either due to the + * cache expiry or due to the in_progress_expiry - the record + * will be overwritten and the idempotent operation can continue. + * + * <b>Note: This method writes only expiry and status information - not + * the results of the operation itself.</b> + * + * @param record DataRecord instance to store + * @param now + * @throws IdempotencyItemAlreadyExistsException + */ + @Override + public void putRecord(DataRecord record, Instant now) throws IdempotencyItemAlreadyExistsException { + Map<String, AttributeValue> item = new HashMap<>(getKey(record.getIdempotencyKey())); + item.put(this.expiryAttr, AttributeValue.builder().n(String.valueOf(record.getExpiryTimestamp())).build()); + item.put(this.statusAttr, AttributeValue.builder().s(record.getStatus().toString()).build()); + + if (record.getInProgressExpiryTimestamp().isPresent()) { + item.put(this.inProgressExpiryAttr, + AttributeValue.builder().n(String.valueOf(record.getInProgressExpiryTimestamp().getAsLong())) + .build()); + } + + if (this.payloadValidationEnabled) { + item.put(this.validationAttr, AttributeValue.builder().s(record.getPayloadHash()).build()); + } + + try { + LOG.debug("Putting record for idempotency key: {}", record.getIdempotencyKey()); + + Map<String, String> expressionAttributeNames = Stream.of( + new AbstractMap.SimpleEntry<>("#id", this.keyAttr), + new AbstractMap.SimpleEntry<>("#expiry", this.expiryAttr), + new AbstractMap.SimpleEntry<>("#in_progress_expiry", this.inProgressExpiryAttr), + new AbstractMap.SimpleEntry<>("#status", this.statusAttr)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + Map<String, AttributeValue> expressionAttributeValues = Stream.of( + new AbstractMap.SimpleEntry<>(":now", + AttributeValue.builder().n(String.valueOf(now.getEpochSecond())).build()), + new AbstractMap.SimpleEntry<>(":now_milliseconds", + AttributeValue.builder().n(String.valueOf(now.toEpochMilli())).build()), + new AbstractMap.SimpleEntry<>(":inprogress", + AttributeValue.builder().s(INPROGRESS.toString()).build()) + ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + + dynamoDbClient.putItem( + PutItemRequest.builder() + .tableName(tableName) + .item(item) + .conditionExpression( + "attribute_not_exists(#id) OR #expiry < :now OR (attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now_milliseconds AND #status = :inprogress)") + .expressionAttributeNames(expressionAttributeNames) + .expressionAttributeValues(expressionAttributeValues) + .returnValuesOnConditionCheckFailure("ALL_OLD") + .build()); + } catch (ConditionalCheckFailedException e) { + LOG.debug("Failed to put record for already existing idempotency key: {}", record.getIdempotencyKey()); + if (e.hasItem()) { + DataRecord existingRecord = itemToRecord(e.item()); + throw new IdempotencyItemAlreadyExistsException( + "Failed to put record for already existing idempotency key: " + record.getIdempotencyKey() + + ". Existing record: " + existingRecord, + e, existingRecord); + } + throw new IdempotencyItemAlreadyExistsException( + "Failed to put record for already existing idempotency key: " + record.getIdempotencyKey(), e); + } + } + + @Override + public void updateRecord(DataRecord record) { + LOG.debug("Updating record for idempotency key: {}", record.getIdempotencyKey()); + String updateExpression = "SET #response_data = :response_data, #expiry = :expiry, #status = :status"; + + Map<String, String> expressionAttributeNames = Stream.of( + new AbstractMap.SimpleEntry<>("#response_data", this.dataAttr), + new AbstractMap.SimpleEntry<>("#expiry", this.expiryAttr), + new AbstractMap.SimpleEntry<>("#status", this.statusAttr)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + Map<String, AttributeValue> expressionAttributeValues = Stream.of( + new AbstractMap.SimpleEntry<>(":response_data", + AttributeValue.builder().s(record.getResponseData()).build()), + new AbstractMap.SimpleEntry<>(":expiry", + AttributeValue.builder().n(String.valueOf(record.getExpiryTimestamp())).build()), + new AbstractMap.SimpleEntry<>(":status", + AttributeValue.builder().s(record.getStatus().toString()).build())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + if (payloadValidationEnabled) { + updateExpression += ", #validation_key = :validation_key"; + expressionAttributeNames.put("#validation_key", this.validationAttr); + expressionAttributeValues.put(":validation_key", + AttributeValue.builder().s(record.getPayloadHash()).build()); + } + + dynamoDbClient.updateItem(UpdateItemRequest.builder() + .tableName(tableName) + .key(getKey(record.getIdempotencyKey())) + .updateExpression(updateExpression) + .expressionAttributeNames(expressionAttributeNames) + .expressionAttributeValues(expressionAttributeValues) + .build() + ); + } + + @Override + public void deleteRecord(String idempotencyKey) { + LOG.debug("Deleting record for idempotency key: {}", idempotencyKey); + dynamoDbClient.deleteItem(DeleteItemRequest.builder() + .tableName(tableName) + .key(getKey(idempotencyKey)) + .build() + ); + } + + /** + * Get the key to use for requests (depending on if we have a sort key or not) + * + * @param idempotencyKey + * @return + */ + private Map<String, AttributeValue> getKey(String idempotencyKey) { + Map<String, AttributeValue> key = new HashMap<>(); + if (this.sortKeyAttr != null) { + key.put(this.keyAttr, AttributeValue.builder().s(this.staticPkValue).build()); + key.put(this.sortKeyAttr, AttributeValue.builder().s(idempotencyKey).build()); + } else { + key.put(this.keyAttr, AttributeValue.builder().s(idempotencyKey).build()); + } + return key; + } + + /** + * Translate raw item records from DynamoDB to DataRecord + * + * @param item Item from dynamodb response + * @return DataRecord instance + */ + private DataRecord itemToRecord(Map<String, AttributeValue> item) { + // data and validation payload may be null + AttributeValue data = item.get(this.dataAttr); + AttributeValue validation = item.get(this.validationAttr); + return new DataRecord(item.get(sortKeyAttr != null ? sortKeyAttr : keyAttr).s(), + DataRecord.Status.valueOf(item.get(this.statusAttr).s()), + Long.parseLong(item.get(this.expiryAttr).n()), + data != null ? data.s() : null, + validation != null ? validation.s() : null, + item.get(this.inProgressExpiryAttr) != null ? + OptionalLong.of(Long.parseLong(item.get(this.inProgressExpiryAttr).n())) : + OptionalLong.empty()); + } + + /** + * Use this builder to get an instance of {@link DynamoDBPersistenceStore}.<br/> + * With this builder you can configure the characteristics of the DynamoDB Table + * (name, key, sort key, and other field names).<br/> + * You can also set a custom {@link DynamoDbClient} for further tuning. + */ + public static class Builder { + private static final String funcEnv = System.getenv(LAMBDA_FUNCTION_NAME_ENV); + + private String tableName; + private String keyAttr = "id"; + private String staticPkValue = String.format("idempotency#%s", funcEnv != null ? funcEnv : ""); + private String sortKeyAttr; + private String expiryAttr = "expiration"; + + private String inProgressExpiryAttr = "in_progress_expiration"; + private String statusAttr = "status"; + private String dataAttr = "data"; + private String validationAttr = "validation"; + private DynamoDbClient dynamoDbClient; + + /** + * Initialize and return a new instance of {@link DynamoDBPersistenceStore}.<br/> + * Example:<br> + * <pre> + * DynamoDBPersistenceStore.builder().withTableName("idempotency_store").build(); + * </pre> + * + * @return an instance of the {@link DynamoDBPersistenceStore} + */ + public DynamoDBPersistenceStore build() { + if (tableName == null || "".equals(tableName)) { + throw new IllegalArgumentException("Table name is not specified"); + } + return new DynamoDBPersistenceStore(tableName, keyAttr, staticPkValue, sortKeyAttr, expiryAttr, + inProgressExpiryAttr, statusAttr, dataAttr, validationAttr, dynamoDbClient); + } + + /** + * Name of the table to use for storing execution records (mandatory) + * + * @param tableName Name of the DynamoDB table + * @return the builder instance (to chain operations) + */ + public Builder withTableName(String tableName) { + this.tableName = tableName; + return this; + } + + /** + * DynamoDB attribute name for partition key (optional), by default "id" + * + * @param keyAttr name of the key attribute in the table + * @return the builder instance (to chain operations) + */ + public Builder withKeyAttr(String keyAttr) { + this.keyAttr = keyAttr; + return this; + } + + /** + * DynamoDB attribute value for partition key (optional), by default "idempotency#[function-name]". + * This will be used if the {@link #sortKeyAttr} is set. + * + * @param staticPkValue name of the partition key attribute in the table + * @return the builder instance (to chain operations) + */ + public Builder withStaticPkValue(String staticPkValue) { + this.staticPkValue = staticPkValue; + return this; + } + + /** + * DynamoDB attribute name for the sort key (optional) + * + * @param sortKeyAttr name of the sort key attribute in the table + * @return the builder instance (to chain operations) + */ + public Builder withSortKeyAttr(String sortKeyAttr) { + this.sortKeyAttr = sortKeyAttr; + return this; + } + + /** + * DynamoDB attribute name for expiry timestamp (optional), by default "expiration" + * + * @param expiryAttr name of the expiry attribute in the table + * @return the builder instance (to chain operations) + */ + public Builder withExpiryAttr(String expiryAttr) { + this.expiryAttr = expiryAttr; + return this; + } + + /** + * DynamoDB attribute name for in progress expiry timestamp (optional), by default "in_progress_expiration" + * + * @param inProgressExpiryAttr name of the attribute in the table + * @return the builder instance (to chain operations) + */ + public Builder withInProgressExpiryAttr(String inProgressExpiryAttr) { + this.inProgressExpiryAttr = inProgressExpiryAttr; + return this; + } + + /** + * DynamoDB attribute name for status (optional), by default "status" + * + * @param statusAttr name of the status attribute in the table + * @return the builder instance (to chain operations) + */ + public Builder withStatusAttr(String statusAttr) { + this.statusAttr = statusAttr; + return this; + } + + /** + * DynamoDB attribute name for response data (optional), by default "data" + * + * @param dataAttr name of the data attribute in the table + * @return the builder instance (to chain operations) + */ + public Builder withDataAttr(String dataAttr) { + this.dataAttr = dataAttr; + return this; + } + + /** + * DynamoDB attribute name for validation (optional), by default "validation" + * + * @param validationAttr name of the validation attribute in the table + * @return the builder instance (to chain operations) + */ + public Builder withValidationAttr(String validationAttr) { + this.validationAttr = validationAttr; + return this; + } + + /** + * Custom {@link DynamoDbClient} used to query DynamoDB (optional).<br/> + * The default one uses {@link UrlConnectionHttpClient} as a http client and + * add com.amazonaws.xray.interceptors.TracingInterceptor (X-Ray) if available in the classpath. + * + * @param dynamoDbClient the {@link DynamoDbClient} instance to use + * @return the builder instance (to chain operations) + */ + public Builder withDynamoDbClient(DynamoDbClient dynamoDbClient) { + this.dynamoDbClient = dynamoDbClient; + return this; + } + } +} diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-idempotency-dynamodb/reflect-config.json b/powertools-idempotency/powertools-idempotency-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-idempotency-dynamodb/reflect-config.json new file mode 100644 index 000000000..6d6fbceb3 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-idempotency-dynamodb/reflect-config.json @@ -0,0 +1,329 @@ +[ +{ + "name":"[Lcom.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers;" +}, +{ + "name":"[Lcom.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.KeyDeserializers;" +}, +{ + "name":"[Lcom.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiators;" +}, +{ + "name":"[Lcom.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers;" +}, +{ + "name":"[Lcom.fasterxml.jackson.databind.deser.Deserializers;" +}, +{ + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.Context" +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getBody","parameterTypes":[] }, {"name":"getHeaders","parameterTypes":[] }, {"name":"getHttpMethod","parameterTypes":[] }, {"name":"getIsBase64Encoded","parameterTypes":[] }, {"name":"getMultiValueHeaders","parameterTypes":[] }, {"name":"getMultiValueQueryStringParameters","parameterTypes":[] }, {"name":"getPath","parameterTypes":[] }, {"name":"getPathParameters","parameterTypes":[] }, {"name":"getQueryStringParameters","parameterTypes":[] }, {"name":"getRequestContext","parameterTypes":[] }, {"name":"getResource","parameterTypes":[] }, {"name":"getStageVariables","parameterTypes":[] }, {"name":"getVersion","parameterTypes":[] }, {"name":"setBody","parameterTypes":["java.lang.String"] }, {"name":"setHeaders","parameterTypes":["java.util.Map"] }, {"name":"setHttpMethod","parameterTypes":["java.lang.String"] }, {"name":"setIsBase64Encoded","parameterTypes":["java.lang.Boolean"] }, {"name":"setPath","parameterTypes":["java.lang.String"] }, {"name":"setPathParameters","parameterTypes":["java.util.Map"] }, {"name":"setQueryStringParameters","parameterTypes":["java.util.Map"] }, {"name":"setRequestContext","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext"] }, {"name":"setResource","parameterTypes":["java.lang.String"] }, {"name":"setStageVariables","parameterTypes":["java.util.Map"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getAccountId","parameterTypes":[] }, {"name":"getApiId","parameterTypes":[] }, {"name":"getAuthorizer","parameterTypes":[] }, {"name":"getDomainName","parameterTypes":[] }, {"name":"getDomainPrefix","parameterTypes":[] }, {"name":"getExtendedRequestId","parameterTypes":[] }, {"name":"getHttpMethod","parameterTypes":[] }, {"name":"getIdentity","parameterTypes":[] }, {"name":"getOperationName","parameterTypes":[] }, {"name":"getPath","parameterTypes":[] }, {"name":"getProtocol","parameterTypes":[] }, {"name":"getRequestId","parameterTypes":[] }, {"name":"getRequestTime","parameterTypes":[] }, {"name":"getRequestTimeEpoch","parameterTypes":[] }, {"name":"getResourceId","parameterTypes":[] }, {"name":"getResourcePath","parameterTypes":[] }, {"name":"getStage","parameterTypes":[] }, {"name":"setAccountId","parameterTypes":["java.lang.String"] }, {"name":"setApiId","parameterTypes":["java.lang.String"] }, {"name":"setHttpMethod","parameterTypes":["java.lang.String"] }, {"name":"setIdentity","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity"] }, {"name":"setPath","parameterTypes":["java.lang.String"] }, {"name":"setProtocol","parameterTypes":["java.lang.String"] }, {"name":"setRequestId","parameterTypes":["java.lang.String"] }, {"name":"setRequestTime","parameterTypes":["java.lang.String"] }, {"name":"setRequestTimeEpoch","parameterTypes":["java.lang.Long"] }, {"name":"setResourceId","parameterTypes":["java.lang.String"] }, {"name":"setResourcePath","parameterTypes":["java.lang.String"] }, {"name":"setStage","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getAccessKey","parameterTypes":[] }, {"name":"getAccountId","parameterTypes":[] }, {"name":"getApiKey","parameterTypes":[] }, {"name":"getCaller","parameterTypes":[] }, {"name":"getCognitoAuthenticationProvider","parameterTypes":[] }, {"name":"getCognitoAuthenticationType","parameterTypes":[] }, {"name":"getCognitoIdentityId","parameterTypes":[] }, {"name":"getCognitoIdentityPoolId","parameterTypes":[] }, {"name":"getPrincipalOrgId","parameterTypes":[] }, {"name":"getSourceIp","parameterTypes":[] }, {"name":"getUser","parameterTypes":[] }, {"name":"getUserAgent","parameterTypes":[] }, {"name":"getUserArn","parameterTypes":[] }, {"name":"setAccessKey","parameterTypes":["java.lang.String"] }, {"name":"setAccountId","parameterTypes":["java.lang.String"] }, {"name":"setCaller","parameterTypes":["java.lang.String"] }, {"name":"setCognitoAuthenticationProvider","parameterTypes":["java.lang.String"] }, {"name":"setCognitoAuthenticationType","parameterTypes":["java.lang.String"] }, {"name":"setCognitoIdentityId","parameterTypes":["java.lang.String"] }, {"name":"setCognitoIdentityPoolId","parameterTypes":["java.lang.String"] }, {"name":"setSourceIp","parameterTypes":["java.lang.String"] }, {"name":"setUser","parameterTypes":["java.lang.String"] }, {"name":"setUserAgent","parameterTypes":["java.lang.String"] }, {"name":"setUserArn","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getBody","parameterTypes":[] }, {"name":"getHeaders","parameterTypes":[] }, {"name":"getIsBase64Encoded","parameterTypes":[] }, {"name":"getMultiValueHeaders","parameterTypes":[] }, {"name":"getStatusCode","parameterTypes":[] }, {"name":"setBody","parameterTypes":["java.lang.String"] }, {"name":"setHeaders","parameterTypes":["java.util.Map"] }, {"name":"setStatusCode","parameterTypes":["java.lang.Integer"] }] +}, +{ + "name":"com.amazonaws.xray.handlers.config.AWSOperationHandler", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.xray.handlers.config.AWSOperationHandlerManifest", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.xray.handlers.config.AWSOperationHandlerRequestDescriptor", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.xray.handlers.config.AWSOperationHandlerResponseDescriptor", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.xray.handlers.config.AWSServiceHandlerManifest", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.xray.interceptors.TracingInterceptor", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.xray.strategy.sampling.manifest.SamplingRuleManifest", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setDefaultRule","parameterTypes":["com.amazonaws.xray.strategy.sampling.rule.SamplingRule"] }, {"name":"setRules","parameterTypes":["java.util.List"] }, {"name":"setVersion","parameterTypes":["int"] }] +}, +{ + "name":"com.amazonaws.xray.strategy.sampling.reservoir.Reservoir", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.xray.strategy.sampling.rule.SamplingRule", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setFixedTarget","parameterTypes":["int"] }, {"name":"setRate","parameterTypes":["float"] }] +}, +{ + "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ARCFOURCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESedeCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"java.io.IOException", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.io.Serializable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Cloneable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.ProcessEnvironment", + "fields":[{"name":"theCaseInsensitiveEnvironment"}, {"name":"theEnvironment"}] +}, +{ + "name":"java.lang.String" +}, +{ + "name":"java.lang.Thread", + "fields":[{"name":"threadLocalRandomProbe"}], + "methods":[{"name":"getContextClassLoader","parameterTypes":[] }] +}, +{ + "name":"java.security.AlgorithmParametersSpi" +}, +{ + "name":"java.security.KeyStoreSpi" +}, +{ + "name":"java.security.SecureRandomParameters" +}, +{ + "name":"java.util.Collections$UnmodifiableMap", + "fields":[{"name":"m"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.Striped64", + "fields":[{"name":"base"}, {"name":"cellsBusy"}] +}, +{ + "name":"javax.crac.Resource" +}, +{ + "name":"javax.security.auth.x500.X500Principal", + "fields":[{"name":"thisX500Name"}], + "methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }] +}, +{ + "name":"jdk.crac.Resource" +}, +{ + "name":"kotlin.Metadata" +}, +{ + "name":"kotlin.Unit" +}, +{ + "name":"org.apache.commons.logging.LogFactory" +}, +{ + "name":"org.apache.commons.logging.impl.Jdk14Logger", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }, {"name":"setLogFactory","parameterTypes":["org.apache.commons.logging.LogFactory"] }] +}, +{ + "name":"org.apache.commons.logging.impl.Log4JLogger" +}, +{ + "name":"org.apache.commons.logging.impl.LogFactoryImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.commons.logging.impl.WeakHashtable", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apiguardian.api.API", + "queryAllPublicMethods":true +}, +{ + "name":"org.joda.time.DateTime" +}, +{ + "name":"scala.util.Properties" +}, +{ + "name":"software.amazon.awssdk.enhanced.dynamodb.internal.ApplyUserAgentInterceptor", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.MD5", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.SHA", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA2$SHA256", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.X509Factory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAKeyFactory$Legacy", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.SSLContextImpl$TLSContext", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.x509.AuthorityInfoAccessExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.AuthorityKeyIdentifierExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.BasicConstraintsExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CRLDistributionPointsExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CertificatePoliciesExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.ExtendedKeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.KeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.NetscapeCertTypeExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.PrivateKeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectKeyIdentifierExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"software.amazon.lambda.powertools.idempotency.dynamodb.internal.IdempotencyDynamodbUserAgentInterceptor", + "methods":[{"name":"<init>","parameterTypes":[] }] +} +] diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-idempotency-dynamodb/resource-config.json b/powertools-idempotency/powertools-idempotency-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-idempotency-dynamodb/resource-config.json new file mode 100644 index 000000000..98e775bb6 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-idempotency-dynamodb/resource-config.json @@ -0,0 +1,27 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.InetAddressResolverProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.apache.commons.logging.LogFactory\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\Qcom/amazonaws/xray/interceptors/DefaultOperationParameterWhitelist.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/xray/sdk.properties\\E" + }, { + "pattern":"\\Qcom/amazonaws/xray/strategy/sampling/DefaultSamplingRules.json\\E" + }, { + "pattern":"\\Qcommons-logging.properties\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/global/handlers/execution.interceptors\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/services/dynamodb/execution.interceptors\\E" + }]}, + "bundles":[] +} diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-idempotency/powertools-idempotency-dynamodb/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..a04caa403 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.idempotency.dynamodb.internal.IdempotencyDynamodbUserAgentInterceptor diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/dynamodb/internal/IdempotencyDynamodbUserAgentInterceptorTest.java b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/dynamodb/internal/IdempotencyDynamodbUserAgentInterceptorTest.java new file mode 100644 index 000000000..e6940a760 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/dynamodb/internal/IdempotencyDynamodbUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.idempotency.dynamodb.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class IdempotencyDynamodbUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/IDEMPOTENCY-DYNAMODB/"); + } +} diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBConfig.java b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBConfig.java new file mode 100644 index 000000000..9f6875689 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBConfig.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.persistence.dynamodb; + +import java.net.URI; + +import org.junit.jupiter.api.BeforeAll; + +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; +import software.amazon.awssdk.services.dynamodb.model.BillingMode; +import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; +import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; +import software.amazon.awssdk.services.dynamodb.model.KeyType; +import software.amazon.awssdk.services.dynamodb.model.ResourceInUseException; +import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; + +class DynamoDBConfig { + protected static final String TABLE_NAME = "idempotency_table"; + protected static DynamoDbClient client; + + @BeforeAll + static void setupDynamo() { + String endpoint = System.getProperty("dynamodb.endpoint", "http://localhost:8000"); + + client = DynamoDbClient.builder() + .httpClient(UrlConnectionHttpClient.builder().build()) + .region(Region.EU_WEST_1) + .endpointOverride(URI.create(endpoint)) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create("FAKE", "FAKE"))) + .build(); + + try { + client.createTable(CreateTableRequest.builder() + .tableName(TABLE_NAME) + .keySchema(KeySchemaElement.builder().keyType(KeyType.HASH).attributeName("id").build()) + .attributeDefinitions( + AttributeDefinition.builder().attributeName("id").attributeType(ScalarAttributeType.S) + .build()) + .billingMode(BillingMode.PAY_PER_REQUEST) + .build()); + } catch (ResourceInUseException e) { + // Table already exists, ignore + } catch (Exception e) { + throw new RuntimeException("Failed to create DynamoDB table", e); + } + } +} diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBPersistenceStoreTest.java b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBPersistenceStoreTest.java new file mode 100644 index 000000000..b5c816286 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBPersistenceStoreTest.java @@ -0,0 +1,388 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.persistence.dynamodb; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; + +import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.BillingMode; +import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; +import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; +import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; +import software.amazon.awssdk.services.dynamodb.model.KeyType; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; +import software.amazon.awssdk.services.dynamodb.model.ScanRequest; +import software.amazon.lambda.powertools.idempotency.Constants; +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemAlreadyExistsException; +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyItemNotFoundException; +import software.amazon.lambda.powertools.idempotency.persistence.DataRecord; + +/** + * These test are using DynamoDBLocal and sqlite, see https://nickolasfisher.com/blog/Configuring-an-In-Memory-DynamoDB-instance-with-Java-for-Integration-Testing + * NOTE: on a Mac with Apple Chipset, you need to use the Oracle JDK x86 64-bit + */ +class DynamoDBPersistenceStoreTest extends DynamoDBConfig { + protected static final String TABLE_NAME_CUSTOM = "idempotency_table_custom"; + private Map<String, AttributeValue> key; + private DynamoDBPersistenceStore dynamoDBPersistenceStore; + + @Test + void putRecord_shouldCreateRecordInDynamoDB() throws IdempotencyItemAlreadyExistsException { + Instant now = Instant.now(); + long expiry = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond(); + dynamoDBPersistenceStore.putRecord(new DataRecord("key", DataRecord.Status.COMPLETED, expiry, null, null), now); + + key = Collections.singletonMap("id", AttributeValue.builder().s("key").build()); + Map<String, AttributeValue> item = client + .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item(); + assertThat(item).isNotNull(); + assertThat(item.get("status").s()).isEqualTo("COMPLETED"); + assertThat(item.get("expiration").n()).isEqualTo(String.valueOf(expiry)); + } + + @Test + void putRecord_shouldCreateRecordInDynamoDB_IfPreviousExpired() { + key = Collections.singletonMap("id", AttributeValue.builder().s("key").build()); + + // GIVEN: Insert a fake item with same id and expired + Map<String, AttributeValue> item = new HashMap<>(key); + Instant now = Instant.now(); + long expiry = now.minus(30, ChronoUnit.SECONDS).getEpochSecond(); + item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build()); + item.put("status", AttributeValue.builder().s(DataRecord.Status.COMPLETED.toString()).build()); + item.put("data", AttributeValue.builder().s("Fake Data").build()); + client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build()); + + // WHEN: call putRecord + long expiry2 = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond(); + dynamoDBPersistenceStore.putRecord( + new DataRecord("key", + DataRecord.Status.INPROGRESS, + expiry2, + null, + null), + now); + + // THEN: an item is inserted + Map<String, AttributeValue> itemInDb = client + .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item(); + assertThat(itemInDb).isNotNull(); + assertThat(itemInDb.get("status").s()).isEqualTo("INPROGRESS"); + assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry2)); + } + + @Test + void putRecord_shouldCreateRecordInDynamoDB_IfLambdaWasInProgressAndTimedOut() { + key = Collections.singletonMap("id", AttributeValue.builder().s("key").build()); + + // GIVEN: Insert a fake item with same id and progress expired (Lambda timed out before and we allow a new + // execution) + Map<String, AttributeValue> item = new HashMap<>(key); + Instant now = Instant.now(); + long expiry = now.plus(30, ChronoUnit.SECONDS).getEpochSecond(); + long progressExpiry = now.minus(30, ChronoUnit.SECONDS).toEpochMilli(); + item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build()); + item.put("status", AttributeValue.builder().s(DataRecord.Status.INPROGRESS.toString()).build()); + item.put("data", AttributeValue.builder().s("Fake Data").build()); + item.put("in_progress_expiration", AttributeValue.builder().n(String.valueOf(progressExpiry)).build()); + client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build()); + + // WHEN: call putRecord + long expiry2 = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond(); + dynamoDBPersistenceStore.putRecord( + new DataRecord("key", + DataRecord.Status.INPROGRESS, + expiry2, + null, + null), + now); + + // THEN: an item is inserted + Map<String, AttributeValue> itemInDb = client + .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item(); + assertThat(itemInDb).isNotNull(); + assertThat(itemInDb.get("status").s()).isEqualTo("INPROGRESS"); + assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry2)); + } + + @Test + void putRecord_shouldThrowIdempotencyItemAlreadyExistsException_IfRecordAlreadyExist() { + key = Collections.singletonMap("id", AttributeValue.builder().s("key").build()); + + // GIVEN: Insert a fake item with same id + Map<String, AttributeValue> item = new HashMap<>(key); + Instant now = Instant.now(); + long expiry = now.plus(30, ChronoUnit.SECONDS).getEpochSecond(); + item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build()); // not expired + item.put("status", AttributeValue.builder().s(DataRecord.Status.COMPLETED.toString()).build()); + item.put("data", AttributeValue.builder().s("Fake Data").build()); + client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build()); + + // WHEN: call putRecord + long expiry2 = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond(); + DataRecord recordToInsert = new DataRecord("key", + DataRecord.Status.INPROGRESS, + expiry2, + null, + null); + assertThatThrownBy(() -> dynamoDBPersistenceStore.putRecord(recordToInsert, now)) + .isInstanceOf(IdempotencyItemAlreadyExistsException.class) + // DataRecord should be present due to returnValuesOnConditionCheckFailure("ALL_OLD") + .matches(e -> ((IdempotencyItemAlreadyExistsException) e).getDataRecord().isPresent()); + + // THEN: item was not updated, retrieve the initial one + Map<String, AttributeValue> itemInDb = client + .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item(); + assertThat(itemInDb).isNotNull(); + assertThat(itemInDb.get("status").s()).isEqualTo("COMPLETED"); + assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry)); + assertThat(itemInDb.get("data").s()).isEqualTo("Fake Data"); + } + + @Test + void putRecord_shouldBlockUpdate_IfRecordAlreadyExistAndProgressNotExpiredAfterLambdaTimedOut() { + key = Collections.singletonMap("id", AttributeValue.builder().s("key").build()); + + // GIVEN: Insert a fake item with same id + Map<String, AttributeValue> item = new HashMap<>(key); + Instant now = Instant.now(); + long expiry = now.plus(30, ChronoUnit.SECONDS).getEpochSecond(); // not expired + long progressExpiry = now.plus(30, ChronoUnit.SECONDS).toEpochMilli(); // not expired + item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build()); + item.put("status", AttributeValue.builder().s(DataRecord.Status.INPROGRESS.toString()).build()); + item.put("data", AttributeValue.builder().s("Fake Data").build()); + item.put("in_progress_expiration", AttributeValue.builder().n(String.valueOf(progressExpiry)).build()); + client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build()); + + // WHEN: call putRecord + long expiry2 = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond(); + DataRecord recordToInsert = new DataRecord("key", + DataRecord.Status.INPROGRESS, + expiry2, + "Fake Data 2", + null); + assertThatThrownBy(() -> dynamoDBPersistenceStore.putRecord(recordToInsert, now)) + .isInstanceOf(IdempotencyItemAlreadyExistsException.class) + // DataRecord should be present due to returnValuesOnConditionCheckFailure("ALL_OLD") + .matches(e -> ((IdempotencyItemAlreadyExistsException) e).getDataRecord().isPresent()); + + // THEN: item was not updated, retrieve the initial one + Map<String, AttributeValue> itemInDb = client + .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item(); + assertThat(itemInDb).isNotNull(); + assertThat(itemInDb.get("status").s()).isEqualTo("INPROGRESS"); + assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry)); + assertThat(itemInDb.get("data").s()).isEqualTo("Fake Data"); + } + + @Test + void getRecord_shouldReturnExistingRecord() throws IdempotencyItemNotFoundException { + key = Collections.singletonMap("id", AttributeValue.builder().s("key").build()); + + // GIVEN: Insert a fake item with same id + Map<String, AttributeValue> item = new HashMap<>(key); + Instant now = Instant.now(); + long expiry = now.plus(30, ChronoUnit.SECONDS).getEpochSecond(); + item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build()); + item.put("status", AttributeValue.builder().s(DataRecord.Status.COMPLETED.toString()).build()); + item.put("data", AttributeValue.builder().s("Fake Data").build()); + client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build()); + + // WHEN + DataRecord dr = dynamoDBPersistenceStore.getRecord("key"); + + // THEN + assertThat(dr.getIdempotencyKey()).isEqualTo("key"); + assertThat(dr.getStatus()).isEqualTo(DataRecord.Status.COMPLETED); + assertThat(dr.getResponseData()).isEqualTo("Fake Data"); + assertThat(dr.getExpiryTimestamp()).isEqualTo(expiry); + } + + @Test + void getRecord_shouldThrowException_whenRecordIsAbsent() { + assertThatThrownBy(() -> dynamoDBPersistenceStore.getRecord("key")) + .isInstanceOf(IdempotencyItemNotFoundException.class); + } + + @Test + void updateRecord_shouldUpdateRecord() { + // GIVEN: Insert a fake item with same id + key = Collections.singletonMap("id", AttributeValue.builder().s("key").build()); + Map<String, AttributeValue> item = new HashMap<>(key); + Instant now = Instant.now(); + long expiry = now.plus(360, ChronoUnit.SECONDS).getEpochSecond(); + item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build()); + item.put("status", AttributeValue.builder().s(DataRecord.Status.INPROGRESS.toString()).build()); + client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build()); + // enable payload validation + dynamoDBPersistenceStore.configure(IdempotencyConfig.builder().withPayloadValidationJMESPath("path").build(), + null); + + // WHEN + expiry = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond(); + DataRecord dr = new DataRecord("key", DataRecord.Status.COMPLETED, expiry, "Fake result", "hash"); + dynamoDBPersistenceStore.updateRecord(dr); + + // THEN + Map<String, AttributeValue> itemInDb = client + .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item(); + assertThat(itemInDb.get("status").s()).isEqualTo("COMPLETED"); + assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry)); + assertThat(itemInDb.get("data").s()).isEqualTo("Fake result"); + assertThat(itemInDb.get("validation").s()).isEqualTo("hash"); + } + + @Test + void deleteRecord_shouldDeleteRecord() { + // GIVEN: Insert a fake item with same id + key = Collections.singletonMap("id", AttributeValue.builder().s("key").build()); + Map<String, AttributeValue> item = new HashMap<>(key); + Instant now = Instant.now(); + long expiry = now.plus(360, ChronoUnit.SECONDS).getEpochSecond(); + item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build()); + item.put("status", AttributeValue.builder().s(DataRecord.Status.INPROGRESS.toString()).build()); + client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build()); + assertThat(client.scan(ScanRequest.builder().tableName(TABLE_NAME).build()).count()).isEqualTo(1); + + // WHEN + dynamoDBPersistenceStore.deleteRecord("key"); + + // THEN + assertThat(client.scan(ScanRequest.builder().tableName(TABLE_NAME).build()).count()).isZero(); + } + + @Test + void endToEndWithCustomAttrNamesAndSortKey() throws IdempotencyItemNotFoundException { + try { + client.createTable(CreateTableRequest.builder() + .tableName(TABLE_NAME_CUSTOM) + .keySchema( + KeySchemaElement.builder().keyType(KeyType.HASH).attributeName("key").build(), + KeySchemaElement.builder().keyType(KeyType.RANGE).attributeName("sortkey").build()) + .attributeDefinitions( + AttributeDefinition.builder().attributeName("key").attributeType(ScalarAttributeType.S) + .build(), + AttributeDefinition.builder().attributeName("sortkey").attributeType(ScalarAttributeType.S) + .build()) + .billingMode(BillingMode.PAY_PER_REQUEST) + .build()); + + DynamoDBPersistenceStore persistenceStore = DynamoDBPersistenceStore.builder() + .withTableName(TABLE_NAME_CUSTOM) + .withDynamoDbClient(client) + .withDataAttr("result") + .withExpiryAttr("expiry") + .withKeyAttr("key") + .withSortKeyAttr("sortkey") + .withStaticPkValue("pk") + .withStatusAttr("state") + .withValidationAttr("valid") + .build(); + + Instant now = Instant.now(); + DataRecord dr = new DataRecord( + "mykey", + DataRecord.Status.INPROGRESS, + now.plus(400, ChronoUnit.SECONDS).getEpochSecond(), + null, + null); + // PUT + persistenceStore.putRecord(dr, now); + + Map<String, AttributeValue> customKey = new HashMap<>(); + customKey.put("key", AttributeValue.builder().s("pk").build()); + customKey.put("sortkey", AttributeValue.builder().s("mykey").build()); + + Map<String, AttributeValue> itemInDb = client + .getItem(GetItemRequest.builder().tableName(TABLE_NAME_CUSTOM).key(customKey).build()).item(); + + // GET + DataRecord recordInDb = persistenceStore.getRecord("mykey"); + + assertThat(itemInDb).isNotNull(); + assertThat(itemInDb.get("key").s()).isEqualTo("pk"); + assertThat(itemInDb.get("sortkey").s()).isEqualTo(recordInDb.getIdempotencyKey()); + assertThat(itemInDb.get("state").s()).isEqualTo(recordInDb.getStatus().toString()); + assertThat(itemInDb.get("expiry").n()).isEqualTo(String.valueOf(recordInDb.getExpiryTimestamp())); + + // UPDATE + DataRecord updatedRecord = new DataRecord( + "mykey", + DataRecord.Status.COMPLETED, + now.plus(500, ChronoUnit.SECONDS).getEpochSecond(), + "response", + null); + persistenceStore.updateRecord(updatedRecord); + recordInDb = persistenceStore.getRecord("mykey"); + assertThat(recordInDb).isEqualTo(updatedRecord); + + // DELETE + persistenceStore.deleteRecord("mykey"); + assertThat(client.scan(ScanRequest.builder().tableName(TABLE_NAME_CUSTOM).build()).count()).isEqualTo(0); + + } finally { + try { + client.deleteTable(DeleteTableRequest.builder().tableName(TABLE_NAME_CUSTOM).build()); + } catch (Exception e) { + // OK + } + } + } + + @Test + @SetEnvironmentVariable(key = Constants.IDEMPOTENCY_DISABLED_ENV, value = "true") + void idempotencyDisabled_noClientShouldBeCreated() { + DynamoDBPersistenceStore store = DynamoDBPersistenceStore.builder().withTableName(TABLE_NAME).build(); + assertThatThrownBy(() -> store.getRecord("fake")) + .isInstanceOf(NullPointerException.class); + } + + @BeforeEach + void setup() { + dynamoDBPersistenceStore = DynamoDBPersistenceStore.builder() + .withTableName(TABLE_NAME) + .withDynamoDbClient(client) + .build(); + } + + @AfterEach + void emptyDB() { + // Clear all items from the table + client.scan(ScanRequest.builder().tableName(TABLE_NAME).build()) + .items() + .forEach(item -> { + Map<String, AttributeValue> itemKey = Collections.singletonMap("id", item.get("id")); + client.deleteItem(DeleteItemRequest.builder().tableName(TABLE_NAME).key(itemKey).build()); + }); + key = null; + } +} diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/IdempotencyTest.java b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/IdempotencyTest.java new file mode 100644 index 000000000..e85614580 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/IdempotencyTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.persistence.dynamodb; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.amazonaws.services.lambda.runtime.tests.EventLoader; + +import software.amazon.awssdk.services.dynamodb.model.ScanRequest; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; +import software.amazon.lambda.powertools.idempotency.persistence.dynamodb.handlers.IdempotencyFunction; + +class IdempotencyTest extends DynamoDBConfig { + + private Context context = new TestLambdaContext(); + + @Test + void endToEndTest() { + IdempotencyFunction function = new IdempotencyFunction(client); + + APIGatewayProxyResponseEvent response = function + .handleRequest(EventLoader.loadApiGatewayRestEvent("apigw_event2.json"), context); + assertThat(function.handlerExecuted).isTrue(); + + function.handlerExecuted = false; + + APIGatewayProxyResponseEvent response2 = function + .handleRequest(EventLoader.loadApiGatewayRestEvent("apigw_event2.json"), context); + assertThat(function.handlerExecuted).isFalse(); + + assertThat(response).isEqualTo(response2); + assertThat(response2.getBody()).contains("hello world"); + + assertThat(client.scan(ScanRequest.builder().tableName(TABLE_NAME).build()).count()).isEqualTo(1); + } +} diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/handlers/IdempotencyFunction.java b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/handlers/IdempotencyFunction.java new file mode 100644 index 000000000..d816af801 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/handlers/IdempotencyFunction.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.idempotency.persistence.dynamodb.handlers; + +import java.util.HashMap; +import java.util.Map; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.persistence.dynamodb.DynamoDBPersistenceStore; + +public class IdempotencyFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + public boolean handlerExecuted = false; + + public IdempotencyFunction(DynamoDbClient client) { + // we need to initialize idempotency configuration before the handleRequest method is called + Idempotency.config().withConfig( + IdempotencyConfig.builder() + .withEventKeyJMESPath("powertools_json(body).address") + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withTableName("idempotency_table") + .withDynamoDbClient(client) + .build()) + .configure(); + } + + @Idempotent + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + handlerExecuted = true; + Map<String, String> headers = new HashMap<>(); + + headers.put("Content-Type", "application/json"); + headers.put("Access-Control-Allow-Origin", "*"); + headers.put("Access-Control-Allow-Methods", "GET, OPTIONS"); + headers.put("Access-Control-Allow-Headers", "*"); + + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() + .withHeaders(headers); + + return response + .withStatusCode(200) + .withBody("{ \"message\": \"hello world\"}"); + + } +} diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/test/resources/apigw_event2.json b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/resources/apigw_event2.json new file mode 100644 index 000000000..a313815c1 --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/resources/apigw_event2.json @@ -0,0 +1,62 @@ +{ + "body": "{\"address\": \"https://checkip.amazonaws.com\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/test/resources/simplelogger.properties b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/resources/simplelogger.properties new file mode 100644 index 000000000..6d188691f --- /dev/null +++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/resources/simplelogger.properties @@ -0,0 +1,7 @@ +org.slf4j.simpleLogger.logFile=target/idempotency-dynamodb-test.log +org.slf4j.simpleLogger.defaultLogLevel=warn +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS +org.slf4j.simpleLogger.showThreadName=false +org.slf4j.simpleLogger.showLogName=true +org.slf4j.simpleLogger.showShortLogName=false \ No newline at end of file diff --git a/powertools-idempotency/spotbugs-exclude.xml b/powertools-idempotency/spotbugs-exclude.xml new file mode 100644 index 000000000..9a2369c75 --- /dev/null +++ b/powertools-idempotency/spotbugs-exclude.xml @@ -0,0 +1,46 @@ +<!-- This file specifies a spotbugs filter for excluding reports that + should not be considered errors. + The format of this file is documented at: + https://spotbugs.readthedocs.io/en/latest/filter.html + When possible, please specify the full names of the bug codes, + using the pattern attribute, to make it clearer what reports are + being suppressed. You can find a listing of codes at: + https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html + --> +<FindBugsFilter> + <!-- Internals of Log event for apache log4j--> + <Match> + <Bug pattern="EI_EXPOSE_REP"/> + <Or> + <And> + <Class name="software.amazon.lambda.powertools.idempotency.Idempotency"/> + <Method name="getPersistenceStore"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.idempotency.Idempotency"/> + <Method name="getConfig"/> + </And> + </Or> + </Match> + <Match> + <Bug pattern="EI_EXPOSE_REP2"/> + <Or> + <And> + <Class name="software.amazon.lambda.powertools.idempotency.Idempotency$Config"/> + <Field name="store"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.idempotency.Idempotency$Config"/> + <Field name="config"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.idempotency.internal.IdempotencyHandler"/> + <Field name="pjp"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.idempotency.persistence.dynamodb.DynamoDBPersistenceStore$Builder"/> + <Field name="dynamoDbClient"/> + </And> + </Or> + </Match> +</FindBugsFilter> \ No newline at end of file diff --git a/powertools-kafka/pom.xml b/powertools-kafka/pom.xml new file mode 100644 index 000000000..c71ef94f6 --- /dev/null +++ b/powertools-kafka/pom.xml @@ -0,0 +1,266 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>powertools-parent</artifactId> + <groupId>software.amazon.lambda</groupId> + <version>2.9.0</version> + </parent> + + <artifactId>powertools-kafka</artifactId> + <packaging>jar</packaging> + + <name>Powertools for AWS Lambda (Java) - Kafka Consumer</name> + <description> + The Kafka utility transparently handles message deserialization, provides an intuitive developer experience, + and integrates seamlessly with the rest of the Powertools for AWS Lambda ecosystem. + </description> + + <properties> + <kafka-clients.version>4.1.1</kafka-clients.version> + <avro.version>1.12.1</avro.version> + <protobuf.version>4.33.1</protobuf.version> + <lambda-serialization.version>1.1.6</lambda-serialization.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>org.apache.kafka</groupId> + <artifactId>kafka-clients</artifactId> + <version>${kafka-clients.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.avro</groupId> + <artifactId>avro</artifactId> + <version>${avro.version}</version> + </dependency> + <dependency> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-java</artifactId> + <version>${protobuf.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-serialization</artifactId> + <version>${lambda-serialization.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.crac</groupId> + <artifactId>crac</artifactId> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit-pioneer</groupId> + <artifactId>junit-pioneer</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <resources> + <resource> + <directory>src/main/resources</directory> + </resource> + </resources> + <testResources> + <testResource> + <directory>src/test/resources</directory> + </testResource> + </testResources> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>${aspectj-maven-plugin.version}</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <!-- https://junit-pioneer.org/docs/environment-variables/#warnings-for-reflective-access --> + <!-- @{argLine} makes sure not other args are lost. They are required for jacoco coverage reports. --> + <argLine> + @{argLine} + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + <!-- Avro plugin for test classes only --> + <plugin> + <groupId>org.apache.avro</groupId> + <artifactId>avro-maven-plugin</artifactId> + <version>${avro.version}</version> + <executions> + <execution> + <id>generate-test-sources</id> + <phase>generate-test-sources</phase> + <goals> + <goal>schema</goal> + </goals> + <configuration> + <sourceDirectory>${project.basedir}/src/test/avro/</sourceDirectory> + <outputDirectory>${project.basedir}/target/generated-test-sources/avro/</outputDirectory> + <stringType>String</stringType> + <testSourceDirectory>${project.basedir}/src/test/avro/</testSourceDirectory> + <testOutputDirectory>${project.basedir}/target/generated-test-sources/avro/</testOutputDirectory> + </configuration> + </execution> + </executions> + </plugin> + <!-- Protobuf plugin for test classes only --> + <plugin> + <groupId>io.github.ascopes</groupId> + <artifactId>protobuf-maven-plugin</artifactId> + <version>3.10.3</version> + <executions> + <execution> + <id>generate-test-sources</id> + <goals> + <goal>generate-test</goal> + </goals> + <phase>generate-test-sources</phase> + <configuration> + <protocVersion>${protobuf.version}</protocVersion> + <sourceDirectories> + <sourceDirectory>${project.basedir}/src/test/proto</sourceDirectory> + </sourceDirectories> + <outputDirectory>${project.basedir}/target/generated-test-sources/protobuf</outputDirectory> + </configuration> + </execution> + </executions> + </plugin> + <!-- Add generated test sources to build --> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <version>3.6.1</version> + <executions> + <execution> + <id>add-test-source</id> + <phase>generate-test-sources</phase> + <goals> + <goal>add-test-source</goal> + </goals> + <configuration> + <sources> + <source>${project.basedir}/target/generated-test-sources/avro</source> + <source>${project.basedir}/target/generated-test-sources/protobuf</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>generate-classesloaded-file</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Xlog:class+load=info:classesloaded.txt + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + +</project> diff --git a/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/Deserialization.java b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/Deserialization.java new file mode 100644 index 000000000..4b96c49db --- /dev/null +++ b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/Deserialization.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to specify the deserialization type for Kafka messages. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Deserialization { + /** + * The type of deserialization to use. + * @return the deserialization type + */ + DeserializationType type(); +} diff --git a/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/DeserializationType.java b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/DeserializationType.java new file mode 100644 index 000000000..a4ac95389 --- /dev/null +++ b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/DeserializationType.java @@ -0,0 +1,17 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka; + +public enum DeserializationType { + LAMBDA_DEFAULT, KAFKA_JSON, KAFKA_AVRO, KAFKA_PROTOBUF +} diff --git a/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/PowertoolsSerializer.java b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/PowertoolsSerializer.java new file mode 100644 index 000000000..619e46729 --- /dev/null +++ b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/PowertoolsSerializer.java @@ -0,0 +1,142 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka; + +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +import com.amazonaws.services.lambda.runtime.CustomPojoSerializer; +import com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory; +import com.amazonaws.services.lambda.runtime.events.KafkaEvent; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.crac.Context; +import org.crac.Core; +import org.crac.Resource; +import software.amazon.lambda.powertools.common.internal.ClassPreLoader; +import software.amazon.lambda.powertools.kafka.internal.DeserializationUtils; +import software.amazon.lambda.powertools.kafka.serializers.KafkaAvroDeserializer; +import software.amazon.lambda.powertools.kafka.serializers.KafkaJsonDeserializer; +import software.amazon.lambda.powertools.kafka.serializers.KafkaProtobufDeserializer; +import software.amazon.lambda.powertools.kafka.serializers.LambdaDefaultDeserializer; +import software.amazon.lambda.powertools.kafka.serializers.PowertoolsDeserializer; + +/** + * Custom Lambda serializer supporting Kafka events. It delegates to the appropriate deserializer based on the + * deserialization type specified by {@link software.amazon.lambda.powertools.kafka.Deserialization} annotation. + * + * Kafka serializers need to be specified explicitly, otherwise, the default Lambda serializer from + * {@link com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory} will be used. + */ +public class PowertoolsSerializer implements CustomPojoSerializer, Resource { + private static final Map<DeserializationType, PowertoolsDeserializer> DESERIALIZERS = Map.of( + DeserializationType.KAFKA_JSON, new KafkaJsonDeserializer(), + DeserializationType.KAFKA_AVRO, new KafkaAvroDeserializer(), + DeserializationType.KAFKA_PROTOBUF, new KafkaProtobufDeserializer(), + DeserializationType.LAMBDA_DEFAULT, new LambdaDefaultDeserializer()); + + private final PowertoolsDeserializer deserializer; + + private static final PowertoolsSerializer INSTANCE = new PowertoolsSerializer(); + + // CRaC registration happens at class loading time + static { + Core.getGlobalContext().register(INSTANCE); + } + + public PowertoolsSerializer() { + this.deserializer = DESERIALIZERS.getOrDefault( + DeserializationUtils.determineDeserializationType(), + new LambdaDefaultDeserializer()); + } + + @Override + public <T> T fromJson(InputStream input, Type type) { + return deserializer.fromJson(input, type); + } + + @Override + public <T> T fromJson(String input, Type type) { + return deserializer.fromJson(input, type); + } + + @Override + public <T> void toJson(T value, OutputStream output, Type type) { + // This is the Lambda default Output serialization + JacksonFactory.getInstance().getSerializer(type).toJson(value, output); + } + + @Override + public void beforeCheckpoint(Context<? extends Resource> context) throws Exception { + JacksonFactory.getInstance().getSerializer(KafkaEvent.class); + JacksonFactory.getInstance().getSerializer(ConsumerRecord.class); + JacksonFactory.getInstance().getSerializer(String.class); + + DeserializationUtils.determineDeserializationType(); + + jsonPriming(); + + ClassPreLoader.preloadClasses(); + } + + @Override + public void afterRestore(Context<? extends Resource> context) throws Exception { + // No action needed after restore + } + + private void jsonPriming() { + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {\n" + + " \"prime-topic-1\": [\n" + + " {\n" + + " \"topic\": \"prime-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 0,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": null,\n" + + " \"headers\": []\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + Type consumerRecords = createConsumerRecordsType(String.class, String.class); + PowertoolsDeserializer deserializers = DESERIALIZERS.get(DeserializationType.KAFKA_JSON); + deserializers.fromJson(kafkaJson, consumerRecords); + } + + private Type createConsumerRecordsType(Class<?> keyClass, Class<?> valueClass) { + return new ParameterizedType() { + @Override + public Type[] getActualTypeArguments() { + return new Type[] { keyClass, valueClass }; + } + + @Override + public Type getRawType() { + return ConsumerRecords.class; + } + + @Override + public Type getOwnerType() { + return null; + } + }; + } +} diff --git a/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/internal/DeserializationUtils.java b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/internal/DeserializationUtils.java new file mode 100644 index 000000000..1d2fe9aca --- /dev/null +++ b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/internal/DeserializationUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.internal; + +import java.lang.reflect.Method; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.kafka.Deserialization; +import software.amazon.lambda.powertools.kafka.DeserializationType; + +/** + * Utility class to determine the deserialization type from Lambda request handler methods annotated with + * {@link Deserialization} utility. + * + * Relies on the Lambda _HANDLER environment variable to detect the currently active handler method. + */ +public final class DeserializationUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(DeserializationUtils.class); + + private DeserializationUtils() { + } + + public static DeserializationType determineDeserializationType() { + String handler = System.getenv("_HANDLER"); + if (handler == null || handler.trim().isEmpty()) { + LOGGER.error("Cannot determine deserialization type. No valid handler found in _HANDLER: {}", handler); + return DeserializationType.LAMBDA_DEFAULT; + } + + try { + HandlerInfo handlerInfo = parseHandler(handler); + Class<?> handlerClazz = Class.forName(handlerInfo.className); + + if (!RequestHandler.class.isAssignableFrom(handlerClazz)) { + LOGGER.warn("Class '{}' does not implement RequestHandler. Ignoring.", handlerInfo.className); + return DeserializationType.LAMBDA_DEFAULT; + } + + return findDeserializationType(handlerClazz, handlerInfo.methodName); + } catch (Exception e) { + LOGGER.warn("Cannot determine deserialization type. Defaulting to standard.", e); + return DeserializationType.LAMBDA_DEFAULT; + } + } + + private static HandlerInfo parseHandler(String handler) { + if (handler.contains("::")) { + int separatorIndex = handler.indexOf("::"); + String className = handler.substring(0, separatorIndex); + String methodName = handler.substring(separatorIndex + 2); + return new HandlerInfo(className, methodName); + } + + return new HandlerInfo(handler); + } + + private static DeserializationType findDeserializationType(Class<?> handlerClass, String methodName) { + for (Method method : handlerClass.getDeclaredMethods()) { + if (method.getName().equals(methodName) && method.isAnnotationPresent(Deserialization.class)) { + Deserialization annotation = method.getAnnotation(Deserialization.class); + LOGGER.debug("Found deserialization type: {}", annotation.type()); + return annotation.type(); + } + } + + return DeserializationType.LAMBDA_DEFAULT; + } + + private static class HandlerInfo { + final String className; + final String methodName; + + HandlerInfo(String className) { + this(className, "handleRequest"); + } + + HandlerInfo(String className, String methodName) { + this.className = className; + this.methodName = methodName; + } + } +} diff --git a/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/internal/KafkaUserAgentInterceptor.java b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/internal/KafkaUserAgentInterceptor.java new file mode 100644 index 000000000..2db4789b3 --- /dev/null +++ b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/internal/KafkaUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.kafka.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-kafka module is on the classpath. + */ +public final class KafkaUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("kafka"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/AbstractKafkaDeserializer.java b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/AbstractKafkaDeserializer.java new file mode 100644 index 000000000..3de8320a5 --- /dev/null +++ b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/AbstractKafkaDeserializer.java @@ -0,0 +1,332 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.serializers; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.common.TopicPartition; +import org.apache.kafka.common.header.Headers; +import org.apache.kafka.common.header.internals.RecordHeaders; +import org.apache.kafka.common.record.TimestampType; + +import com.amazonaws.services.lambda.runtime.events.KafkaEvent; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Abstract base class for Kafka deserializers that implements common functionality. + */ +abstract class AbstractKafkaDeserializer implements PowertoolsDeserializer { + protected static final ObjectMapper objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + private static final Integer GLUE_SCHEMA_ID_LENGTH = 36; + + public enum SchemaRegistryType { + CONFLUENT, GLUE, NONE + } + + /** + * Deserialize JSON from InputStream into ConsumerRecords + * + * @param input InputStream containing JSON data + * @param type Type representing ConsumerRecords<K, V> + * @param <T> The type to deserialize to + * @return Deserialized ConsumerRecords object + * @throws IllegalArgumentException if type is not ConsumerRecords + */ + @SuppressWarnings("unchecked") + @Override + public <T> T fromJson(InputStream input, Type type) { + if (!isConsumerRecordsType(type)) { + throw new IllegalArgumentException("Type must be ConsumerRecords<K, V> when using this deserializer"); + } + + try { + // Parse the KafkaEvent from the input stream + KafkaEvent kafkaEvent = objectMapper.readValue(input, KafkaEvent.class); + + // Extract the key and value types from the ConsumerRecords<K, V> type + ParameterizedType parameterizedType = (ParameterizedType) type; + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + Class<?> keyType = (Class<?>) typeArguments[0]; + Class<?> valueType = (Class<?>) typeArguments[1]; + + // Convert KafkaEvent to ConsumerRecords + return (T) convertToConsumerRecords(kafkaEvent, keyType, valueType); + } catch (IOException e) { + throw new RuntimeException("Failed to deserialize Lambda handler input to ConsumerRecords", e); + } + } + + /** + * Deserialize JSON from String into ConsumerRecords + * + * @param input String containing JSON data + * @param type Type representing ConsumerRecords<K, V> + * @param <T> The type to deserialize to + * @return Deserialized ConsumerRecords object + * @throws IllegalArgumentException if type is not ConsumerRecords + */ + @SuppressWarnings("unchecked") + @Override + public <T> T fromJson(String input, Type type) { + if (!isConsumerRecordsType(type)) { + throw new IllegalArgumentException("Type must be ConsumerRecords<K, V> when using this deserializer"); + } + + try { + // Parse the KafkaEvent from the input string + KafkaEvent kafkaEvent = objectMapper.readValue(input, KafkaEvent.class); + + // Extract the key and value types from the ConsumerRecords<K, V> type + ParameterizedType parameterizedType = (ParameterizedType) type; + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + Class<?> keyType = (Class<?>) typeArguments[0]; + Class<?> valueType = (Class<?>) typeArguments[1]; + + // Convert KafkaEvent to ConsumerRecords + return (T) convertToConsumerRecords(kafkaEvent, keyType, valueType); + } catch (IOException e) { + throw new RuntimeException("Failed to deserialize Lambda handler input to ConsumerRecords", e); + } + } + + private boolean isConsumerRecordsType(Type type) { + if (!(type instanceof ParameterizedType)) { + return false; + } + + ParameterizedType parameterizedType = (ParameterizedType) type; + return parameterizedType.getRawType().equals(ConsumerRecords.class); + } + + private <K, V> ConsumerRecords<K, V> convertToConsumerRecords(KafkaEvent kafkaEvent, Class<K> keyType, + Class<V> valueType) { + // Validate that this is actually a Kafka event by checking for required properties + if (kafkaEvent == null || kafkaEvent.getEventSource() == null) { + throw new RuntimeException( + "Failed to deserialize Lambda handler input to ConsumerRecords: Input is not a valid Kafka event."); + } + + if (kafkaEvent.getRecords() == null) { + return ConsumerRecords.empty(); + } + + Map<TopicPartition, List<ConsumerRecord<K, V>>> recordsMap = new HashMap<>(); + + for (Map.Entry<String, List<KafkaEvent.KafkaEventRecord>> entry : kafkaEvent.getRecords().entrySet()) { + String topic = entry.getKey(); + + for (KafkaEvent.KafkaEventRecord eventRecord : entry.getValue()) { + ConsumerRecord<K, V> consumerRecord = convertToConsumerRecord(topic, eventRecord, keyType, valueType); + + TopicPartition topicPartition = new TopicPartition(topic, eventRecord.getPartition()); + recordsMap.computeIfAbsent(topicPartition, k -> new ArrayList<>()).add(consumerRecord); + } + } + + return createConsumerRecords(recordsMap); + } + + /** + * Creates ConsumerRecords with compatibility for both Kafka 3.x.x and 4.x.x. + * + * @param <K> Key type + * @param <V> Value type + * @param records Map of records by topic partition + * @return ConsumerRecords instance + */ + protected <K, V> ConsumerRecords<K, V> createConsumerRecords( + Map<TopicPartition, List<ConsumerRecord<K, V>>> records) { + try { + // Try to use the Kafka 4.x.x constructor with nextOffsets parameter + return new ConsumerRecords<>(records, Map.of()); + } catch (NoSuchMethodError e) { + // Fall back to Kafka 3.x.x constructor if 4.x.x is not available + return new ConsumerRecords<>(records); + } + } + + private <K, V> ConsumerRecord<K, V> convertToConsumerRecord( + String topic, + KafkaEvent.KafkaEventRecord eventRecord, + Class<K> keyType, + Class<V> valueType) { + + K key = deserializeField(eventRecord.getKey(), keyType, "key", extractSchemaRegistryType(eventRecord)); + V value = deserializeField(eventRecord.getValue(), valueType, "value", extractSchemaRegistryType(eventRecord)); + Headers headers = extractHeaders(eventRecord); + + return new ConsumerRecord<>( + topic, + eventRecord.getPartition(), + eventRecord.getOffset(), + eventRecord.getTimestamp(), + TimestampType.valueOf(eventRecord.getTimestampType()), + // We set these to NULL_SIZE since they are not relevant in the Lambda environment due to ESM + // pre-processing. + ConsumerRecord.NULL_SIZE, + ConsumerRecord.NULL_SIZE, + key, + value, + headers, + Optional.empty()); + } + + private <T> T deserializeField(String encodedData, Class<T> type, String fieldName, + SchemaRegistryType schemaRegistryType) { + if (encodedData == null) { + return null; + } + + try { + byte[] decodedBytes = Base64.getDecoder().decode(encodedData); + return deserialize(decodedBytes, type, schemaRegistryType); + } catch (Exception e) { + throw new RuntimeException("Failed to deserialize Kafka record " + fieldName + ".", e); + } + } + + private Headers extractHeaders(KafkaEvent.KafkaEventRecord eventRecord) { + Headers headers = new RecordHeaders(); + if (eventRecord.getHeaders() != null) { + for (Map<String, byte[]> headerMap : eventRecord.getHeaders()) { + for (Map.Entry<String, byte[]> header : headerMap.entrySet()) { + if (header.getValue() != null) { + headers.add(header.getKey(), header.getValue()); + } + } + } + } + + return headers; + } + + private String extractKeySchemaId(KafkaEvent.KafkaEventRecord eventRecord) { + if (eventRecord.getKeySchemaMetadata() != null) { + return eventRecord.getKeySchemaMetadata().getSchemaId(); + } + return null; + } + + private String extractValueSchemaId(KafkaEvent.KafkaEventRecord eventRecord) { + if (eventRecord.getValueSchemaMetadata() != null) { + return eventRecord.getValueSchemaMetadata().getSchemaId(); + } + return null; + } + + private SchemaRegistryType extractSchemaRegistryType(KafkaEvent.KafkaEventRecord eventRecord) { + // This method is used for both key and value, so we try to extract the schema id from both fields + String schemaId = extractValueSchemaId(eventRecord); + if (schemaId == null) { + schemaId = extractKeySchemaId(eventRecord); + } + + if (schemaId == null) { + return SchemaRegistryType.NONE; + } + + return schemaId.length() == GLUE_SCHEMA_ID_LENGTH ? SchemaRegistryType.GLUE : SchemaRegistryType.CONFLUENT; + } + + /** + * Template method to be implemented by subclasses for specific deserialization logic + * for complex types (non-primitives) and for specific Schema Registry type. + * + * @param <T> The type to deserialize to + * @param data The byte array to deserialize coming from the base64 decoded Kafka field + * @param type The class type to deserialize to + * @param schemaRegistryType Schema Registry type + * @return The deserialized object + * @throws IOException If deserialization fails + */ + protected abstract <T> T deserializeObject(byte[] data, Class<T> type, SchemaRegistryType schemaRegistryType) + throws IOException; + + /** + * Main deserialize method that handles primitive types and delegates to subclasses for complex types and + * for specific Schema Registry type. + * + * @param <T> The type to deserialize to + * @param data The byte array to deserialize + * @param type The class type to deserialize to + * @param schemaRegistryType Schema Registry type + * @return The deserialized object + * @throws IOException If deserialization fails + */ + private <T> T deserialize(byte[] data, Class<T> type, SchemaRegistryType schemaRegistryType) throws IOException { + // First try to deserialize as a primitive type + T result = deserializePrimitive(data, type); + if (result != null) { + return result; + } + + // Delegate to subclass for complex type deserialization + return deserializeObject(data, type, schemaRegistryType); + } + + /** + * Helper method for handling primitive types and String deserialization. + * + * @param <T> The type to deserialize to + * @param data The byte array to deserialize + * @param type The class type to deserialize to + * @return The deserialized primitive or String, or null if not a primitive or String + */ + @SuppressWarnings("unchecked") + private <T> T deserializePrimitive(byte[] data, Class<T> type) { + // Handle String type + if (type == String.class) { + return (T) new String(data, StandardCharsets.UTF_8); + } + + // Handle primitive types and their wrappers + String str = new String(data, StandardCharsets.UTF_8); + + if (type == Integer.class || type == int.class) { + return (T) Integer.valueOf(str); + } else if (type == Long.class || type == long.class) { + return (T) Long.valueOf(str); + } else if (type == Double.class || type == double.class) { + return (T) Double.valueOf(str); + } else if (type == Float.class || type == float.class) { + return (T) Float.valueOf(str); + } else if (type == Boolean.class || type == boolean.class) { + return (T) Boolean.valueOf(str); + } else if (type == Byte.class || type == byte.class) { + return (T) Byte.valueOf(str); + } else if (type == Short.class || type == short.class) { + return (T) Short.valueOf(str); + } else if (type == Character.class || type == char.class) { + if (!str.isEmpty()) { + return (T) Character.valueOf(str.charAt(0)); + } + throw new IllegalArgumentException("Cannot convert empty string to char"); + } + + return null; + } +} diff --git a/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/KafkaAvroDeserializer.java b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/KafkaAvroDeserializer.java new file mode 100644 index 000000000..70e4affac --- /dev/null +++ b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/KafkaAvroDeserializer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.serializers; + +import java.io.IOException; + +import org.apache.avro.io.DatumReader; +import org.apache.avro.io.Decoder; +import org.apache.avro.io.DecoderFactory; +import org.apache.avro.specific.SpecificDatumReader; +import org.apache.avro.specific.SpecificRecordBase; + +/** + * Deserializer for Kafka records using Avro format. + */ +public class KafkaAvroDeserializer extends AbstractKafkaDeserializer { + + @Override + protected <T> T deserializeObject(byte[] data, Class<T> type, SchemaRegistryType schemaRegistryType) + throws IOException { + // If no Avro generated class is passed we cannot deserialize using Avro + if (SpecificRecordBase.class.isAssignableFrom(type)) { + try { + DatumReader<T> datumReader = new SpecificDatumReader<>(type); + Decoder decoder = DecoderFactory.get().binaryDecoder(data, null); + + return datumReader.read(null, decoder); + } catch (Exception e) { + throw new IOException("Failed to deserialize Avro data.", e); + } + } else { + throw new IOException("Unsupported type for Avro deserialization: " + type.getName() + ". " + + "Avro deserialization requires a type of org.apache.avro.specific.SpecificRecord. " + + "Consider using an alternative Deserializer."); + } + } +} diff --git a/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/KafkaJsonDeserializer.java b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/KafkaJsonDeserializer.java new file mode 100644 index 000000000..733fe5d7d --- /dev/null +++ b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/KafkaJsonDeserializer.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.serializers; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Deserializer for Kafka records using JSON format. + */ +public class KafkaJsonDeserializer extends AbstractKafkaDeserializer { + + @Override + protected <T> T deserializeObject(byte[] data, Class<T> type, SchemaRegistryType schemaRegistryType) + throws IOException { + String decodedStr = new String(data, StandardCharsets.UTF_8); + + return objectMapper.readValue(decodedStr, type); + } +} diff --git a/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/KafkaProtobufDeserializer.java b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/KafkaProtobufDeserializer.java new file mode 100644 index 000000000..1a8085a61 --- /dev/null +++ b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/KafkaProtobufDeserializer.java @@ -0,0 +1,110 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.serializers; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.ByteBuffer; + +import org.apache.kafka.common.utils.ByteUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.Message; +import com.google.protobuf.Parser; + +/** + * Deserializer for Kafka records using Protocol Buffers format. + * Supports both standard protobuf serialization and Confluent / Glue Schema Registry serialization using messages + * indices. + * + * For Confluent-serialized data, assumes the magic byte and schema ID have already been stripped + * by the Kafka ESM, leaving only the message index (if present) and protobuf data. + * + * @see {@link https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format} + */ +public class KafkaProtobufDeserializer extends AbstractKafkaDeserializer { + + private static final Logger LOGGER = LoggerFactory.getLogger(KafkaProtobufDeserializer.class); + private static final String PROTOBUF_PARSER_METHOD = "parser"; + + @Override + protected <T> T deserializeObject(byte[] data, Class<T> type, SchemaRegistryType schemaRegistryType) + throws IOException { + // If no Protobuf generated class is passed, we cannot deserialize using Protobuf + if (Message.class.isAssignableFrom(type)) { + try { + switch (schemaRegistryType) { + case GLUE: + return glueDeserializer(data, type); + case CONFLUENT: + return confluentDeserializer(data, type); + default: + return defaultDeserializer(data, type); + } + } catch (Exception e) { + throw new IOException("Failed to deserialize Protobuf data.", e); + } + } else { + throw new IOException("Unsupported type for Protobuf deserialization: " + type.getName() + ". " + + "Protobuf deserialization requires a type of com.google.protobuf.Message. " + + "Consider using an alternative Deserializer."); + } + } + + @SuppressWarnings("unchecked") + private <T> T defaultDeserializer(byte[] data, Class<T> type) throws IOException { + try { + LOGGER.debug("Using default Protobuf deserializer"); + Parser<Message> parser = (Parser<Message>) type.getMethod(PROTOBUF_PARSER_METHOD).invoke(null); + Message message = parser.parseFrom(data); + return type.cast(message); + } catch (Exception e) { + throw new IOException("Failed to deserialize Protobuf data.", e); + } + } + + @SuppressWarnings("unchecked") + private <T> T confluentDeserializer(byte[] data, Class<T> type) + throws IOException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + LOGGER.debug("Using Confluent Deserializer"); + Parser<Message> parser = (Parser<Message>) type.getMethod(PROTOBUF_PARSER_METHOD).invoke(null); + ByteBuffer buffer = ByteBuffer.wrap(data); + int size = ByteUtils.readVarint(buffer); + + // Only if the size is greater than zero, continue reading varInt. + // https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format + if (size > 0) { + for (int i = 0; i < size; i++) { + ByteUtils.readVarint(buffer); + } + } + Message message = parser.parseFrom(buffer); + return type.cast(message); + } + + @SuppressWarnings("unchecked") + private <T> T glueDeserializer(byte[] data, Class<T> type) + throws IOException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + LOGGER.debug("Using Glue Deserializer"); + CodedInputStream codedInputStream = CodedInputStream.newInstance(data); + Parser<Message> parser = (Parser<Message>) type.getMethod(PROTOBUF_PARSER_METHOD).invoke(null); + + // Seek one byte forward. Based on Glue Proto deserializer implementation + codedInputStream.readUInt32(); + + Message message = parser.parseFrom(codedInputStream); + return type.cast(message); + } +} diff --git a/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/LambdaDefaultDeserializer.java b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/LambdaDefaultDeserializer.java new file mode 100644 index 000000000..a7ea15d2f --- /dev/null +++ b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/LambdaDefaultDeserializer.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.serializers; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; + +import com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory; + +/** + * Default deserializer for Kafka events proxying to Lambda default behavior. + * + * This deserializer uses the default Jackson ObjectMapper to deserialize the event from + * {@link com.amazonaws.services.lambda.runtime.serialization}. + */ +public class LambdaDefaultDeserializer implements PowertoolsDeserializer { + + @SuppressWarnings("unchecked") + @Override + public <T> T fromJson(InputStream input, Type type) { + // If the target type does not require conversion, simply return the value itself + if (type.equals(InputStream.class)) { + return (T) input; + } + + // If the target type is String, read the input stream as a String + if (type.equals(String.class)) { + try { + return (T) new String(input.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Failed to read input stream as String", e); + } + } + + return (T) JacksonFactory.getInstance().getSerializer(type).fromJson(input); + } + + @SuppressWarnings("unchecked") + @Override + public <T> T fromJson(String input, Type type) { + // If the target type does not require conversion, simply return the value itself + if (type.equals(String.class)) { + return (T) input; + } + + // If the target type is InputStream, read the input stream as a String + if (type.equals(InputStream.class)) { + return (T) input.getBytes(StandardCharsets.UTF_8); + } + + return (T) JacksonFactory.getInstance().getSerializer(type).fromJson(input); + } +} diff --git a/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/PowertoolsDeserializer.java b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/PowertoolsDeserializer.java new file mode 100644 index 000000000..1ac0ca0ba --- /dev/null +++ b/powertools-kafka/src/main/java/software/amazon/lambda/powertools/kafka/serializers/PowertoolsDeserializer.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.serializers; + +import java.io.InputStream; +import java.lang.reflect.Type; + +/** + * Interface for deserializers that can handle both String and InputStream inputs. + * + * Similar to {@link com.amazonaws.services.lambda.runtime.CustomPojoSerializer} but only for input deserialization. + */ +public interface PowertoolsDeserializer { + <T> T fromJson(InputStream input, Type type); + + <T> T fromJson(String input, Type type); +} diff --git a/powertools-kafka/src/main/resources/META-INF/services/com.amazonaws.services.lambda.runtime.CustomPojoSerializer b/powertools-kafka/src/main/resources/META-INF/services/com.amazonaws.services.lambda.runtime.CustomPojoSerializer new file mode 100644 index 000000000..abc84b035 --- /dev/null +++ b/powertools-kafka/src/main/resources/META-INF/services/com.amazonaws.services.lambda.runtime.CustomPojoSerializer @@ -0,0 +1 @@ +software.amazon.lambda.powertools.kafka.PowertoolsSerializer diff --git a/powertools-kafka/src/main/resources/classesloaded.txt b/powertools-kafka/src/main/resources/classesloaded.txt new file mode 100644 index 000000000..6b8c000bc --- /dev/null +++ b/powertools-kafka/src/main/resources/classesloaded.txt @@ -0,0 +1,6642 @@ +java.lang.Object +java.io.Serializable +java.lang.Comparable +java.lang.CharSequence +java.lang.constant.Constable +java.lang.constant.ConstantDesc +java.lang.String +java.lang.reflect.AnnotatedElement +java.lang.reflect.GenericDeclaration +java.lang.reflect.Type +java.lang.invoke.TypeDescriptor +java.lang.invoke.TypeDescriptor$OfField +java.lang.Class +java.lang.Cloneable +java.lang.ClassLoader +java.lang.System +java.lang.Throwable +java.lang.Error +java.lang.ThreadDeath +java.lang.Exception +java.lang.RuntimeException +java.lang.SecurityManager +java.security.ProtectionDomain +java.security.AccessControlContext +java.security.AccessController +java.security.SecureClassLoader +java.lang.ReflectiveOperationException +java.lang.ClassNotFoundException +java.lang.Record +java.lang.LinkageError +java.lang.NoClassDefFoundError +java.lang.ClassCastException +java.lang.ArrayStoreException +java.lang.VirtualMachineError +java.lang.InternalError +java.lang.OutOfMemoryError +java.lang.StackOverflowError +java.lang.IllegalMonitorStateException +java.lang.ref.Reference +java.lang.ref.SoftReference +java.lang.ref.WeakReference +java.lang.ref.FinalReference +java.lang.ref.PhantomReference +java.lang.ref.Finalizer +java.lang.Runnable +java.lang.Thread +java.lang.Thread$UncaughtExceptionHandler +java.lang.ThreadGroup +java.util.Dictionary +java.util.Map +java.util.Hashtable +java.util.Properties +java.lang.Module +java.lang.reflect.AccessibleObject +java.lang.reflect.Member +java.lang.reflect.Field +java.lang.reflect.Parameter +java.lang.reflect.Executable +java.lang.reflect.Method +java.lang.reflect.Constructor +jdk.internal.reflect.MagicAccessorImpl +jdk.internal.reflect.MethodAccessor +jdk.internal.reflect.MethodAccessorImpl +jdk.internal.reflect.ConstructorAccessor +jdk.internal.reflect.ConstructorAccessorImpl +jdk.internal.reflect.DelegatingClassLoader +jdk.internal.reflect.ConstantPool +jdk.internal.reflect.FieldAccessor +jdk.internal.reflect.FieldAccessorImpl +jdk.internal.reflect.UnsafeFieldAccessorImpl +jdk.internal.reflect.UnsafeStaticFieldAccessorImpl +java.lang.annotation.Annotation +jdk.internal.reflect.CallerSensitive +jdk.internal.reflect.NativeConstructorAccessorImpl +java.lang.invoke.MethodHandle +java.lang.invoke.DirectMethodHandle +java.lang.invoke.VarHandle +java.lang.invoke.MemberName +java.lang.invoke.ResolvedMethodName +java.lang.invoke.MethodHandleNatives +java.lang.invoke.LambdaForm +java.lang.invoke.TypeDescriptor$OfMethod +java.lang.invoke.MethodType +java.lang.BootstrapMethodError +java.lang.invoke.CallSite +jdk.internal.invoke.NativeEntryPoint +java.lang.invoke.MethodHandleNatives$CallSiteContext +java.lang.invoke.ConstantCallSite +java.lang.invoke.MutableCallSite +java.lang.invoke.VolatileCallSite +java.lang.AssertionStatusDirectives +java.lang.Appendable +java.lang.AbstractStringBuilder +java.lang.StringBuffer +java.lang.StringBuilder +jdk.internal.misc.UnsafeConstants +jdk.internal.misc.Unsafe +jdk.internal.module.Modules +java.lang.AutoCloseable +java.io.Closeable +java.io.InputStream +java.io.ByteArrayInputStream +java.net.URL +java.util.jar.Manifest +jdk.internal.loader.BuiltinClassLoader +jdk.internal.loader.ClassLoaders +jdk.internal.loader.ClassLoaders$AppClassLoader +jdk.internal.loader.ClassLoaders$PlatformClassLoader +java.security.CodeSource +java.util.AbstractMap +java.util.concurrent.ConcurrentMap +java.util.concurrent.ConcurrentHashMap +java.lang.Iterable +java.util.Collection +java.util.AbstractCollection +java.util.List +java.util.AbstractList +java.util.RandomAccess +java.util.ArrayList +java.lang.StackTraceElement +java.nio.Buffer +java.lang.StackWalker +java.lang.StackStreamFactory$AbstractStackWalker +java.lang.StackWalker$StackFrame +java.lang.StackFrameInfo +java.lang.LiveStackFrame +java.lang.LiveStackFrameInfo +java.util.concurrent.locks.AbstractOwnableSynchronizer +java.lang.Boolean +java.lang.Character +java.lang.Number +java.lang.Float +java.lang.Double +java.lang.Byte +java.lang.Short +java.lang.Integer +java.lang.Long +java.util.Iterator +java.lang.reflect.RecordComponent +jdk.internal.vm.vector.VectorSupport +jdk.internal.vm.vector.VectorSupport$VectorPayload +jdk.internal.vm.vector.VectorSupport$Vector +jdk.internal.vm.vector.VectorSupport$VectorMask +jdk.internal.vm.vector.VectorSupport$VectorShuffle +java.lang.Integer$IntegerCache +java.lang.Long$LongCache +java.lang.Byte$ByteCache +java.lang.Short$ShortCache +java.lang.Character$CharacterCache +java.util.jar.Attributes$Name +java.util.ImmutableCollections$AbstractImmutableMap +java.util.ImmutableCollections$MapN +sun.util.locale.BaseLocale +jdk.internal.module.ArchivedModuleGraph +java.lang.module.ModuleFinder +jdk.internal.module.SystemModuleFinders$SystemModuleFinder +java.util.ImmutableCollections$AbstractImmutableCollection +java.util.Set +java.util.ImmutableCollections$AbstractImmutableSet +java.util.ImmutableCollections$SetN +java.lang.module.ModuleReference +jdk.internal.module.ModuleReferenceImpl +java.lang.module.ModuleDescriptor +java.lang.module.ModuleDescriptor$Version +java.util.ImmutableCollections$Set12 +java.lang.module.ModuleDescriptor$Requires +java.lang.Enum +java.lang.module.ModuleDescriptor$Requires$Modifier +java.lang.module.ModuleDescriptor$Exports +java.net.URI +java.util.function.Supplier +jdk.internal.module.SystemModuleFinders$2 +jdk.internal.module.ModuleHashes$HashSupplier +jdk.internal.module.SystemModuleFinders$3 +java.lang.module.ModuleDescriptor$Provides +java.util.ImmutableCollections$AbstractImmutableList +java.util.ImmutableCollections$List12 +java.util.ImmutableCollections$ListN +java.lang.module.ModuleDescriptor$Opens +jdk.internal.module.ModuleTarget +jdk.internal.module.ModuleHashes +java.util.Collections$UnmodifiableMap +java.util.HashMap +java.util.Map$Entry +java.util.HashMap$Node +java.lang.module.Configuration +java.lang.module.ResolvedModule +java.util.function.Function +jdk.internal.module.ModuleLoaderMap$Mapper +java.util.ImmutableCollections +java.lang.ModuleLayer +jdk.internal.math.FDBigInteger +java.lang.NullPointerException +java.lang.ArithmeticException +java.io.ObjectStreamField +java.util.Comparator +java.lang.String$CaseInsensitiveComparator +java.lang.Module$ArchivedData +jdk.internal.misc.CDS +java.util.Objects +jdk.internal.access.JavaLangReflectAccess +java.lang.reflect.ReflectAccess +jdk.internal.access.SharedSecrets +java.lang.invoke.MethodHandles +java.lang.invoke.MemberName$Factory +java.security.Guard +java.security.Permission +java.security.BasicPermission +java.lang.reflect.ReflectPermission +java.lang.StringLatin1 +java.lang.invoke.MethodHandles$Lookup +jdk.internal.reflect.Reflection +java.lang.Math +java.util.AbstractSet +java.util.ImmutableCollections$MapN$1 +java.util.ImmutableCollections$MapN$MapNIterator +java.util.KeyValueHolder +java.util.LinkedHashMap$Entry +java.util.HashMap$TreeNode +java.lang.Runtime +java.util.concurrent.locks.Lock +java.util.concurrent.locks.ReentrantLock +java.util.concurrent.ConcurrentHashMap$Segment +java.util.concurrent.ConcurrentHashMap$CounterCell +java.util.concurrent.ConcurrentHashMap$Node +java.util.concurrent.locks.LockSupport +java.util.concurrent.ConcurrentHashMap$ReservationNode +java.security.PrivilegedAction +jdk.internal.reflect.ReflectionFactory$GetReflectionFactoryAction +jdk.internal.reflect.ReflectionFactory +java.lang.ref.Reference$ReferenceHandler +jdk.internal.ref.Cleaner +java.lang.ref.ReferenceQueue +java.lang.ref.ReferenceQueue$Null +java.lang.ref.ReferenceQueue$Lock +jdk.internal.access.JavaLangRefAccess +java.lang.ref.Reference$1 +java.lang.ref.Finalizer$FinalizerThread +jdk.internal.access.JavaLangAccess +java.lang.System$2 +jdk.internal.misc.VM +jdk.internal.util.SystemProps +jdk.internal.util.SystemProps$Raw +java.lang.StringConcatHelper +java.lang.VersionProps +java.util.Arrays +java.lang.CharacterData +java.lang.CharacterDataLatin1 +java.util.HashMap$EntrySet +java.util.HashMap$HashIterator +java.util.HashMap$EntryIterator +jdk.internal.util.StaticProperty +java.io.FileInputStream +java.io.FileDescriptor +jdk.internal.access.JavaIOFileDescriptorAccess +java.io.FileDescriptor$1 +java.io.Flushable +java.io.OutputStream +java.io.FileOutputStream +java.io.FilterInputStream +java.io.BufferedInputStream +java.io.FilterOutputStream +java.io.PrintStream +java.io.BufferedOutputStream +java.io.Writer +java.io.OutputStreamWriter +java.nio.charset.Charset +java.nio.charset.spi.CharsetProvider +sun.nio.cs.StandardCharsets +java.lang.ThreadLocal +java.util.concurrent.atomic.AtomicInteger +sun.security.action.GetPropertyAction +sun.nio.cs.HistoricallyNamedCharset +sun.nio.cs.Unicode +sun.nio.cs.UTF_8 +sun.nio.cs.StreamEncoder +java.nio.charset.CharsetEncoder +sun.nio.cs.UTF_8$Encoder +java.nio.charset.CodingErrorAction +java.nio.ByteBuffer +jdk.internal.misc.ScopedMemoryAccess +jdk.internal.access.JavaNioAccess +java.nio.Buffer$1 +java.nio.HeapByteBuffer +java.nio.ByteOrder +java.io.BufferedWriter +java.lang.Terminator +jdk.internal.misc.Signal$Handler +java.lang.Terminator$1 +jdk.internal.misc.Signal +java.util.Hashtable$Entry +jdk.internal.misc.Signal$NativeHandler +jdk.internal.misc.OSEnvironment +java.util.Collections +java.util.Collections$EmptySet +java.util.Collections$EmptyList +java.util.Collections$EmptyMap +java.lang.IllegalArgumentException +java.lang.invoke.MethodHandleStatics +jdk.internal.module.ModuleBootstrap +sun.invoke.util.VerifyAccess +java.lang.reflect.Modifier +jdk.internal.access.JavaLangModuleAccess +java.lang.module.ModuleDescriptor$1 +java.io.File +java.io.DefaultFileSystem +java.io.FileSystem +java.io.UnixFileSystem +jdk.internal.util.ArraysSupport +jdk.internal.module.ModulePatcher +jdk.internal.module.ModuleBootstrap$Counters +jdk.internal.module.ArchivedBootLayer +jdk.internal.access.JavaNetUriAccess +java.net.URI$1 +jdk.internal.loader.ArchivedClassLoaders +jdk.internal.loader.ClassLoaders$BootClassLoader +java.security.cert.Certificate +java.lang.ClassLoader$ParallelLoaders +java.util.WeakHashMap +java.util.WeakHashMap$Entry +java.util.Collections$SetFromMap +java.util.WeakHashMap$KeySet +jdk.internal.access.JavaSecurityAccess +java.security.ProtectionDomain$JavaSecurityAccessImpl +java.security.ProtectionDomain$Key +java.security.Principal +jdk.internal.loader.NativeLibraries +jdk.internal.loader.ClassLoaderHelper +java.util.HashSet +java.util.Queue +java.util.Deque +java.util.ArrayDeque +jdk.internal.loader.URLClassPath +java.net.URLStreamHandlerFactory +java.net.URL$DefaultFactory +jdk.internal.access.JavaNetURLAccess +java.net.URL$3 +java.io.File$PathStatus +sun.net.www.ParseUtil +java.util.HexFormat +java.net.URLStreamHandler +sun.net.www.protocol.file.Handler +sun.net.util.IPAddressUtil +jdk.internal.util.Preconditions +sun.net.www.protocol.jar.Handler +jdk.internal.module.ServicesCatalog +jdk.internal.loader.AbstractClassLoaderValue +jdk.internal.loader.ClassLoaderValue +java.util.Optional +jdk.internal.loader.BootLoader +jdk.internal.loader.BuiltinClassLoader$LoadedModule +java.util.ImmutableCollections$SetN$SetNIterator +java.util.ImmutableCollections$Set12$1 +java.util.ListIterator +java.util.ImmutableCollections$ListItr +jdk.internal.module.ModuleLoaderMap +jdk.internal.loader.AbstractClassLoaderValue$Memoizer +jdk.internal.module.ServicesCatalog$ServiceProvider +java.util.concurrent.CopyOnWriteArrayList +java.util.HashMap$KeySet +java.util.HashMap$KeyIterator +java.lang.ModuleLayer$Controller +java.lang.invoke.LambdaMetafactory +java.lang.invoke.MethodType$ConcurrentWeakInternSet +java.lang.Void +java.lang.invoke.MethodTypeForm +java.lang.invoke.MethodType$ConcurrentWeakInternSet$WeakEntry +sun.invoke.util.Wrapper +sun.invoke.util.Wrapper$Format +java.lang.invoke.LambdaForm$NamedFunction +java.lang.invoke.DirectMethodHandle$Holder +sun.invoke.util.ValueConversions +java.lang.invoke.MethodHandleImpl +java.lang.invoke.Invokers +java.lang.invoke.LambdaForm$Kind +java.lang.NoSuchMethodException +java.lang.invoke.LambdaForm$BasicType +java.lang.reflect.Array +java.lang.invoke.LambdaForm$Name +java.lang.invoke.LambdaForm$Holder +java.lang.invoke.InvokerBytecodeGenerator +java.lang.invoke.InvokerBytecodeGenerator$2 +java.lang.invoke.MethodHandleImpl$Intrinsic +java.lang.invoke.BootstrapMethodInvoker +java.lang.invoke.VarHandle$AccessMode +java.lang.invoke.VarHandle$AccessType +java.lang.invoke.Invokers$Holder +jdk.internal.access.JavaLangInvokeAccess +java.lang.invoke.MethodHandleImpl$1 +java.lang.invoke.AbstractValidatingLambdaMetafactory +java.lang.invoke.InnerClassLambdaMetafactory +jdk.internal.org.objectweb.asm.Type +sun.security.action.GetBooleanAction +jdk.internal.org.objectweb.asm.Handle +sun.invoke.util.BytecodeDescriptor +jdk.internal.org.objectweb.asm.ConstantDynamic +java.lang.invoke.MethodHandleInfo +java.lang.invoke.InfoFromMemberName +jdk.internal.org.objectweb.asm.ClassVisitor +jdk.internal.org.objectweb.asm.ClassWriter +jdk.internal.org.objectweb.asm.SymbolTable +jdk.internal.org.objectweb.asm.Symbol +jdk.internal.org.objectweb.asm.SymbolTable$Entry +jdk.internal.org.objectweb.asm.ByteVector +java.lang.invoke.LambdaProxyClassArchive +jdk.internal.org.objectweb.asm.MethodVisitor +jdk.internal.org.objectweb.asm.MethodWriter +jdk.internal.org.objectweb.asm.Label +java.lang.invoke.TypeConvertingMethodAdapter +java.lang.invoke.InnerClassLambdaMetafactory$ForwardingMethodGenerator +jdk.internal.org.objectweb.asm.Handler +jdk.internal.org.objectweb.asm.Attribute +jdk.internal.org.objectweb.asm.AnnotationVisitor +jdk.internal.org.objectweb.asm.AnnotationWriter +java.lang.invoke.MethodHandles$Lookup$ClassOption +java.lang.invoke.MethodHandles$Lookup$ClassFile +jdk.internal.org.objectweb.asm.ClassReader +java.lang.StringUTF16 +java.lang.invoke.MethodHandles$Lookup$ClassDefiner +jdk.internal.module.ModuleBootstrap$$Lambda$1/0x00007f4204040850 +java.lang.invoke.InnerClassLambdaMetafactory$1 +java.lang.Class$ReflectionData +java.lang.Class$Atomic +jdk.internal.reflect.DelegatingConstructorAccessorImpl +java.lang.invoke.BoundMethodHandle +java.lang.invoke.ClassSpecializer +java.lang.invoke.BoundMethodHandle$Specializer +java.lang.invoke.ClassSpecializer$1 +java.lang.invoke.ClassSpecializer$SpeciesData +java.lang.invoke.BoundMethodHandle$SpeciesData +java.lang.invoke.ClassSpecializer$Factory +java.lang.invoke.BoundMethodHandle$Specializer$Factory +java.lang.invoke.SimpleMethodHandle +java.lang.NoSuchFieldException +java.lang.invoke.BoundMethodHandle$Species_L +sun.invoke.util.VerifyType +sun.invoke.empty.Empty +java.lang.invoke.DirectMethodHandle$2 +java.lang.invoke.DirectMethodHandle$Accessor +java.lang.invoke.DelegatingMethodHandle +java.lang.invoke.MethodHandleImpl$IntrinsicMethodHandle +java.lang.invoke.DelegatingMethodHandle$Holder +sun.invoke.util.Wrapper$1 +java.lang.invoke.LambdaFormEditor +java.lang.invoke.LambdaFormEditor$TransformKey +java.lang.invoke.LambdaFormBuffer +java.lang.invoke.LambdaFormEditor$Transform +jdk.internal.org.objectweb.asm.Frame +java.lang.invoke.InvokerBytecodeGenerator$ClassData +java.util.ArrayList$Itr +jdk.internal.org.objectweb.asm.FieldVisitor +jdk.internal.org.objectweb.asm.FieldWriter +java.lang.invoke.LambdaForm$MH/0x00007f4204000400 +jdk.internal.ref.CleanerFactory +java.util.concurrent.ThreadFactory +jdk.internal.ref.CleanerFactory$1 +java.lang.ref.Cleaner +java.lang.ref.Cleaner$1 +jdk.internal.ref.CleanerImpl +java.lang.ref.Cleaner$Cleanable +jdk.internal.ref.PhantomCleanable +jdk.internal.ref.CleanerImpl$PhantomCleanableRef +jdk.internal.ref.CleanerImpl$CleanerCleanable +jdk.internal.misc.InnocuousThread +java.util.ArrayList$SubList +java.lang.Module$ReflectionData +java.lang.WeakPairMap +java.lang.WeakPairMap$Pair +java.lang.WeakPairMap$Pair$Lookup +java.util.function.BiFunction +java.lang.Module$$Lambda$2/0x00007f4204040a90 +java.lang.WeakPairMap$WeakRefPeer +java.lang.WeakPairMap$Pair$Weak +java.lang.WeakPairMap$Pair$Weak$1 +java.lang.WeakPairMap$$Lambda$3/0x00007f42040413e8 +java.lang.invoke.DirectMethodHandle$Constructor +java.lang.invoke.StringConcatFactory +java.lang.invoke.StringConcatFactory$1 +java.lang.invoke.StringConcatFactory$2 +java.lang.invoke.StringConcatFactory$3 +sun.launcher.LauncherHelper +java.lang.StringCoding +java.util.zip.ZipConstants +java.util.zip.ZipFile +java.util.jar.JarFile +jdk.internal.access.JavaUtilZipFileAccess +java.util.zip.ZipFile$1 +jdk.internal.access.JavaUtilJarAccess +java.util.jar.JavaUtilJarAccessImpl +java.lang.Runtime$Version +java.util.zip.ZipFile$CleanableResource +java.util.zip.ZipCoder +java.util.zip.ZipCoder$UTF8ZipCoder +java.util.zip.ZipFile$Source +sun.nio.fs.DefaultFileSystemProvider +java.nio.file.spi.FileSystemProvider +sun.nio.fs.AbstractFileSystemProvider +sun.nio.fs.UnixFileSystemProvider +sun.nio.fs.LinuxFileSystemProvider +java.nio.file.OpenOption +java.nio.file.StandardOpenOption +java.nio.file.FileSystem +sun.nio.fs.UnixFileSystem +sun.nio.fs.LinuxFileSystem +java.nio.file.Watchable +java.nio.file.Path +sun.nio.fs.UnixPath +sun.nio.fs.Util +sun.nio.fs.UnixNativeDispatcher +jdk.internal.loader.NativeLibraries$LibraryPaths +jdk.internal.loader.NativeLibraries$1 +java.util.ArrayDeque$DeqIterator +jdk.internal.loader.NativeLibrary +jdk.internal.loader.NativeLibraries$NativeLibraryImpl +java.util.concurrent.ConcurrentHashMap$CollectionView +java.util.concurrent.ConcurrentHashMap$ValuesView +java.util.concurrent.ConcurrentHashMap$Traverser +java.util.concurrent.ConcurrentHashMap$BaseIterator +java.util.Enumeration +java.util.concurrent.ConcurrentHashMap$ValueIterator +java.nio.file.attribute.BasicFileAttributes +java.nio.file.attribute.PosixFileAttributes +sun.nio.fs.UnixFileAttributes +sun.nio.fs.UnixFileStoreAttributes +sun.nio.fs.UnixMountEntry +java.util.zip.ZipFile$Source$Key +java.nio.file.CopyOption +java.nio.file.LinkOption +java.nio.file.Files +java.nio.file.attribute.DosFileAttributes +java.nio.file.attribute.AttributeView +java.nio.file.attribute.FileAttributeView +java.nio.file.attribute.BasicFileAttributeView +java.nio.file.attribute.DosFileAttributeView +java.nio.file.attribute.UserDefinedFileAttributeView +sun.nio.fs.UnixFileAttributeViews +sun.nio.fs.DynamicFileAttributeView +sun.nio.fs.AbstractBasicFileAttributeView +sun.nio.fs.UnixFileAttributeViews$Basic +sun.nio.fs.NativeBuffers +jdk.internal.misc.TerminatingThreadLocal +sun.nio.fs.NativeBuffers$1 +jdk.internal.misc.TerminatingThreadLocal$1 +java.lang.ThreadLocal$ThreadLocalMap +java.lang.ThreadLocal$ThreadLocalMap$Entry +java.util.IdentityHashMap +java.util.IdentityHashMap$KeySet +sun.nio.fs.NativeBuffer +sun.nio.fs.NativeBuffer$Deallocator +sun.nio.fs.UnixFileAttributes$UnixAsBasicFileAttributes +java.io.DataOutput +java.io.DataInput +java.io.RandomAccessFile +jdk.internal.access.JavaIORandomAccessFileAccess +java.io.RandomAccessFile$2 +java.io.FileCleanable +java.util.zip.ZipFile$Source$End +java.util.zip.ZipUtils +java.util.concurrent.TimeUnit +java.nio.file.attribute.FileTime +jdk.internal.perf.PerfCounter +jdk.internal.perf.Perf$GetPerfAction +jdk.internal.perf.Perf +jdk.internal.perf.PerfCounter$CoreCounters +sun.nio.ch.DirectBuffer +java.nio.MappedByteBuffer +java.nio.DirectByteBuffer +java.nio.Bits +java.util.concurrent.atomic.AtomicLong +jdk.internal.misc.VM$BufferPool +java.nio.Bits$1 +java.nio.LongBuffer +java.nio.DirectLongBufferU +java.util.zip.ZipEntry +java.util.jar.JarEntry +java.util.jar.JarFile$JarFileEntry +java.util.zip.ZipFile$ZipFileInputStream +java.util.zip.InflaterInputStream +java.util.zip.ZipFile$ZipFileInflaterInputStream +java.util.zip.Inflater +java.util.zip.Inflater$InflaterZStreamRef +java.util.zip.ZipFile$InflaterCleanupAction +sun.security.util.SignatureFileVerifier +sun.security.util.Debug +java.util.Locale +sun.util.locale.LocaleUtils +sun.security.action.GetIntegerAction +java.util.jar.JarVerifier +java.security.CodeSigner +java.io.ByteArrayOutputStream +java.util.jar.Attributes +java.util.LinkedHashMap +java.util.jar.Manifest$FastInputStream +java.io.RandomAccessFile$1 +sun.net.util.URLUtil +java.security.PrivilegedExceptionAction +jdk.internal.loader.URLClassPath$3 +jdk.internal.loader.URLClassPath$Loader +jdk.internal.loader.URLClassPath$JarLoader +jdk.internal.loader.URLClassPath$JarLoader$1 +jdk.internal.loader.FileURLMapper +jdk.internal.util.jar.JarIndex +java.util.StringTokenizer +jdk.internal.loader.Resource +jdk.internal.loader.URLClassPath$JarLoader$2 +java.lang.NamedPackage +java.lang.Package +java.lang.Package$VersionInfo +sun.nio.ByteBuffered +java.util.zip.Checksum +java.util.zip.CRC32 +java.util.zip.Checksum$1 +java.security.SecureClassLoader$CodeSourceKey +java.security.SecureClassLoader$1 +java.security.PermissionCollection +sun.security.util.LazyCodeSourcePermissionCollection +java.security.Permissions +java.lang.RuntimePermission +java.security.BasicPermissionCollection +java.security.AllPermission +java.security.UnresolvedPermission +java.security.SecureClassLoader$DebugHolder +org.apache.maven.surefire.booter.ForkedBooter +org.apache.maven.surefire.api.fork.ForkNodeArguments +org.apache.maven.plugin.surefire.log.api.ConsoleLogger +java.lang.SecurityException +java.security.AccessControlException +java.io.IOException +org.apache.maven.surefire.api.provider.CommandListener +org.apache.maven.surefire.api.report.ReporterFactory +java.util.concurrent.ConcurrentHashMap$ForwardingNode +org.apache.maven.surefire.api.provider.CommandChainReader +java.lang.InterruptedException +java.util.concurrent.Executor +java.util.concurrent.ExecutorService +java.util.concurrent.ScheduledExecutorService +java.lang.PublicMethods$MethodList +java.lang.PublicMethods$Key +java.util.concurrent.Semaphore +java.util.concurrent.locks.AbstractQueuedSynchronizer +java.util.concurrent.Semaphore$Sync +java.util.concurrent.Semaphore$NonfairSync +org.apache.maven.surefire.booter.BooterDeserializer +org.apache.maven.surefire.booter.SystemPropertyManager +java.util.Properties$LineReader +java.util.Properties$EntrySet +java.util.concurrent.ConcurrentHashMap$EntrySetView +java.util.Collections$SynchronizedCollection +java.util.Collections$SynchronizedSet +java.util.concurrent.ConcurrentHashMap$EntryIterator +java.util.concurrent.ConcurrentHashMap$MapEntry +java.util.Collections$UnmodifiableCollection +java.util.Collections$UnmodifiableSet +java.util.Collections$UnmodifiableCollection$1 +org.apache.maven.surefire.booter.KeyValueSource +org.apache.maven.surefire.booter.PropertiesWrapper +java.lang.IllegalStateException +java.io.FileInputStream$1 +org.apache.maven.surefire.booter.TypeEncodedValue +org.apache.maven.surefire.api.testset.DirectoryScannerParameters +org.apache.maven.surefire.api.util.RunOrder +org.apache.maven.surefire.api.testset.RunOrderParameters +org.apache.maven.surefire.api.testset.TestArtifactInfo +org.apache.maven.surefire.api.testset.TestRequest +org.apache.maven.surefire.api.testset.TestFilter +org.apache.maven.surefire.api.testset.GenericTestPattern +org.apache.maven.surefire.api.testset.TestListResolver +java.util.Collections$SingletonSet +org.apache.maven.surefire.api.testset.IncludedExcludedPatterns +java.util.LinkedHashSet +java.util.Collections$1 +org.apache.maven.surefire.shared.utils.StringUtils +java.lang.IndexOutOfBoundsException +java.lang.StringIndexOutOfBoundsException +org.apache.maven.surefire.api.testset.ResolvedTest +org.apache.maven.surefire.api.testset.ResolvedTest$Type +org.apache.maven.surefire.api.testset.ResolvedTest$ClassMatcher +org.apache.maven.surefire.api.testset.ResolvedTest$MethodMatcher +org.apache.maven.surefire.api.report.ReporterConfiguration +org.apache.maven.surefire.api.booter.Shutdown +java.lang.Class$3 +jdk.internal.reflect.NativeMethodAccessorImpl +jdk.internal.reflect.DelegatingMethodAccessorImpl +org.apache.maven.surefire.booter.ProviderConfiguration +org.apache.maven.surefire.api.cli.CommandLineOption +org.apache.maven.surefire.api.booter.DumpErrorSingleton +org.apache.maven.surefire.api.util.internal.DumpFileUtils +java.lang.ProcessEnvironment +java.lang.ProcessEnvironment$ExternalData +java.lang.ProcessEnvironment$Variable +java.lang.ProcessEnvironment$Value +java.lang.ProcessEnvironment$StringEnvironment +java.lang.management.ManagementFactory +java.lang.IncompatibleClassChangeError +java.lang.NoSuchMethodError +java.lang.invoke.LambdaForm$DMH/0x00007f4204006000 +java.lang.management.ManagementFactory$$Lambda$4/0x00007f42040451f8 +java.lang.management.PlatformManagedObject +java.lang.management.RuntimeMXBean +java.lang.management.ManagementFactory$PlatformMBeanFinder +java.lang.management.ManagementFactory$PlatformMBeanFinder$1 +java.io.FilePermission +jdk.internal.access.JavaIOFilePermissionAccess +java.io.FilePermission$1 +sun.security.util.FilePermCompat +sun.security.util.SecurityProperties +java.security.Security +jdk.internal.access.JavaSecuritySystemConfiguratorAccess +java.security.Security$1 +java.security.Security$2 +java.security.SystemConfigurator +java.security.SystemConfigurator$1 +sun.security.util.PropertyExpander +java.net.URLConnection +sun.net.www.URLConnection +sun.net.www.protocol.file.FileURLConnection +sun.net.www.MessageHeader +sun.net.ProgressMonitor +sun.net.ProgressMeteringPolicy +sun.net.DefaultProgressMeteringPolicy +jdk.internal.access.JavaSecurityPropertiesAccess +java.security.Security$3 +sun.management.spi.PlatformMBeanProvider +java.util.ServiceLoader +java.util.ServiceLoader$ModuleServicesLookupIterator +java.util.ServiceLoader$LazyClassPathLookupIterator +java.util.ServiceLoader$2 +java.util.ServiceLoader$3 +java.util.concurrent.CopyOnWriteArrayList$COWIterator +com.sun.management.internal.PlatformMBeanProviderImpl +java.util.ServiceLoader$1 +java.util.ServiceLoader$Provider +java.util.ServiceLoader$ProviderImpl +com.sun.management.internal.PlatformMBeanProviderImpl$$Lambda$5/0x00007f42040468a0 +sun.management.spi.PlatformMBeanProvider$PlatformComponent +com.sun.management.internal.PlatformMBeanProviderImpl$1 +java.util.stream.BaseStream +java.util.stream.Stream +java.util.Spliterators +java.util.Spliterators$EmptySpliterator +java.util.Spliterator +java.util.Spliterators$EmptySpliterator$OfRef +java.util.Spliterator$OfPrimitive +java.util.Spliterator$OfInt +java.util.Spliterators$EmptySpliterator$OfInt +java.util.Spliterator$OfLong +java.util.Spliterators$EmptySpliterator$OfLong +java.util.Spliterator$OfDouble +java.util.Spliterators$EmptySpliterator$OfDouble +java.util.Spliterators$ArraySpliterator +java.util.stream.StreamSupport +java.util.stream.PipelineHelper +java.util.stream.AbstractPipeline +java.util.stream.ReferencePipeline +java.util.stream.ReferencePipeline$Head +java.util.stream.StreamOpFlag +java.util.stream.StreamOpFlag$Type +java.util.stream.StreamOpFlag$MaskBuilder +java.util.EnumMap +java.util.EnumMap$1 +sun.reflect.annotation.AnnotationParser +java.util.stream.Collectors +java.util.stream.Collector$Characteristics +java.util.EnumSet +java.util.RegularEnumSet +java.util.stream.Collector +java.util.stream.Collectors$CollectorImpl +java.util.stream.Collectors$$Lambda$30/0x800000041 +java.util.function.BiConsumer +java.lang.invoke.DirectMethodHandle$Interface +java.util.stream.Collectors$$Lambda$22/0x800000035 +java.util.function.BinaryOperator +java.util.stream.Collectors$$Lambda$25/0x80000003c +java.util.stream.Collectors$$Lambda$27/0x80000003e +java.util.stream.ReduceOps +java.util.stream.TerminalOp +java.util.stream.ReduceOps$ReduceOp +java.util.stream.ReduceOps$3 +java.util.stream.StreamShape +java.util.stream.ReduceOps$Box +java.util.function.Consumer +java.util.stream.Sink +java.util.stream.TerminalSink +java.util.stream.ReduceOps$AccumulatingSink +java.util.stream.ReduceOps$3ReducingSink +com.sun.management.internal.PlatformMBeanProviderImpl$2 +com.sun.management.internal.PlatformMBeanProviderImpl$3 +com.sun.management.internal.PlatformMBeanProviderImpl$4 +javax.management.DynamicMBean +com.sun.management.DiagnosticCommandMBean +javax.management.NotificationBroadcaster +javax.management.NotificationEmitter +sun.management.NotificationEmitterSupport +com.sun.management.internal.DiagnosticCommandImpl +sun.management.ManagementFactoryHelper +sun.management.VMManagement +sun.management.VMManagementImpl +com.sun.management.internal.PlatformMBeanProviderImpl$5 +java.util.Collections$UnmodifiableList +java.util.Collections$UnmodifiableRandomAccessList +jdk.management.jfr.internal.FlightRecorderMXBeanProvider +java.util.concurrent.Callable +java.util.Collections$EmptyEnumeration +java.lang.management.DefaultPlatformMBeanProvider +java.lang.management.DefaultPlatformMBeanProvider$1 +java.lang.management.DefaultPlatformMBeanProvider$2 +java.lang.management.DefaultPlatformMBeanProvider$3 +java.lang.management.DefaultPlatformMBeanProvider$4 +java.lang.management.DefaultPlatformMBeanProvider$5 +java.lang.management.DefaultPlatformMBeanProvider$6 +java.lang.management.DefaultPlatformMBeanProvider$7 +java.lang.management.DefaultPlatformMBeanProvider$8 +sun.management.ManagementFactoryHelper$LoggingMXBeanAccess +sun.management.ManagementFactoryHelper$LoggingMXBeanAccess$1 +java.util.logging.LogManager +java.lang.management.DefaultPlatformMBeanProvider$9 +java.lang.management.DefaultPlatformMBeanProvider$10 +java.lang.management.DefaultPlatformMBeanProvider$11 +jdk.management.jfr.FlightRecorderMXBean +jdk.management.jfr.internal.FlightRecorderMXBeanProvider$SingleMBeanComponent +java.util.Collections$SingletonList +java.util.HashMap$Values +java.util.HashMap$HashMapSpliterator +java.util.HashMap$ValueSpliterator +java.util.function.Predicate +java.lang.management.ManagementFactory$PlatformMBeanFinder$$Lambda$10/0x00007f420404c288 +java.util.stream.ReferencePipeline$StatelessOp +java.util.stream.ReferencePipeline$2 +java.lang.management.ManagementFactory$PlatformMBeanFinder$$Lambda$11/0x00007f420404c4e0 +java.util.stream.ReduceOps$2 +java.util.stream.ReduceOps$2ReducingSink +java.util.stream.Sink$ChainedReference +java.util.stream.ReferencePipeline$2$1 +sun.management.RuntimeImpl +java.util.Collections$SingletonMap +java.util.Collections$2 +java.lang.invoke.LambdaForm$DMH/0x00007f4204006400 +sun.management.spi.PlatformMBeanProvider$PlatformComponent$$Lambda$12/0x00007f420404d2d0 +sun.management.spi.PlatformMBeanProvider$PlatformComponent$$Lambda$13/0x00007f420404d528 +java.util.stream.ReferencePipeline$3 +java.util.stream.Collectors$$Lambda$14/0x00007f420404d770 +java.util.stream.Collectors$$Lambda$15/0x00007f420404d990 +java.util.stream.Collectors$$Lambda$16/0x00007f420404dbc0 +java.util.stream.ReferencePipeline$3$1 +sun.management.Util +java.lang.management.ManagementPermission +java.util.Arrays$ArrayList +java.util.Arrays$ArrayItr +org.apache.maven.surefire.booter.ClassLoaderConfiguration +org.apache.maven.surefire.booter.AbstractPathConfiguration +org.apache.maven.surefire.booter.ClasspathConfiguration +org.apache.maven.surefire.booter.Classpath +java.net.MalformedURLException +java.net.URLClassLoader +org.apache.maven.surefire.booter.IsolatedClassLoader +org.apache.maven.surefire.booter.SurefireExecutionException +org.apache.maven.surefire.booter.ProcessCheckerType +org.apache.maven.surefire.booter.StartupConfiguration +org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory +java.util.Spliterators$1Adapter +java.util.HashMap$ValueIterator +jdk.internal.module.Resources +jdk.internal.loader.BuiltinClassLoader$2 +jdk.internal.loader.BuiltinClassLoader$5 +java.lang.module.ModuleReader +jdk.internal.module.SystemModuleFinders$SystemModuleReader +jdk.internal.module.SystemModuleFinders$SystemImage +jdk.internal.jimage.ImageReaderFactory +java.nio.file.Paths +java.nio.file.FileSystems +java.nio.file.FileSystems$DefaultFileSystemHolder +java.nio.file.FileSystems$DefaultFileSystemHolder$1 +java.net.URI$Parser +jdk.internal.jimage.ImageReaderFactory$1 +jdk.internal.jimage.ImageReader +jdk.internal.jimage.BasicImageReader +jdk.internal.jimage.ImageReader$SharedImageReader +jdk.internal.jimage.BasicImageReader$1 +jdk.internal.jimage.NativeImageBuffer +jdk.internal.jimage.NativeImageBuffer$1 +jdk.internal.jimage.ImageHeader +java.nio.IntBuffer +java.nio.DirectIntBufferU +java.nio.DirectByteBufferR +java.nio.DirectIntBufferRU +jdk.internal.jimage.ImageStrings +jdk.internal.jimage.ImageStringsReader +jdk.internal.jimage.decompressor.Decompressor +jdk.internal.jimage.ImageLocation +java.util.Collections$EmptyIterator +jdk.internal.loader.BuiltinClassLoader$1 +java.lang.CompoundEnumeration +jdk.internal.loader.URLClassPath$1 +jdk.internal.loader.URLClassPath$FileLoader +java.util.SortedSet +java.util.NavigableSet +java.util.TreeSet +java.util.SortedMap +java.util.NavigableMap +java.util.TreeMap +java.util.TreeMap$Entry +java.util.TreeMap$KeySet +java.util.TreeMap$PrivateEntryIterator +java.util.TreeMap$KeyIterator +java.lang.Readable +java.io.Reader +java.io.BufferedReader +java.io.InputStreamReader +sun.nio.cs.StreamDecoder +java.nio.charset.CharsetDecoder +sun.nio.cs.UTF_8$Decoder +java.util.Vector +java.nio.CharBuffer +java.nio.HeapCharBuffer +java.nio.charset.CoderResult +java.util.AbstractSequentialList +java.util.LinkedList +java.util.LinkedList$Node +java.net.JarURLConnection +sun.net.www.protocol.jar.JarURLConnection +sun.net.www.protocol.jar.URLJarFile$URLJarFileCloseController +sun.net.www.protocol.jar.JarFileFactory +sun.net.www.protocol.jar.URLJarFile +sun.nio.fs.UnixFileKey +sun.net.www.protocol.jar.URLJarFile$URLJarFileEntry +sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream +java.util.LinkedHashMap$LinkedKeySet +java.util.LinkedHashMap$LinkedHashIterator +java.util.LinkedHashMap$LinkedKeyIterator +org.apache.maven.surefire.booter.spi.AbstractMasterProcessChannelProcessorFactory +org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory +org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder +org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder +org.apache.maven.surefire.api.util.internal.DaemonThreadFactory +java.util.concurrent.Executors +java.util.concurrent.Executors$DefaultThreadFactory +org.apache.maven.surefire.api.util.internal.DaemonThreadFactory$NamedThreadFactory +java.util.concurrent.AbstractExecutorService +java.util.concurrent.ThreadPoolExecutor +java.util.concurrent.ScheduledThreadPoolExecutor +java.util.concurrent.RejectedExecutionHandler +java.util.concurrent.ThreadPoolExecutor$AbortPolicy +java.util.concurrent.BlockingQueue +java.util.AbstractQueue +java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue +java.util.concurrent.Future +java.util.concurrent.RunnableFuture +java.util.concurrent.Delayed +java.util.concurrent.ScheduledFuture +java.util.concurrent.RunnableScheduledFuture +java.util.concurrent.locks.ReentrantLock$Sync +java.util.concurrent.locks.ReentrantLock$NonfairSync +java.util.concurrent.locks.Condition +java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject +org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory +java.net.URISyntaxException +java.util.concurrent.ExecutionException +java.net.SocketAddress +java.net.InetSocketAddress +java.nio.channels.Channel +java.nio.channels.AsynchronousChannel +java.nio.channels.AsynchronousByteChannel +org.apache.maven.surefire.booter.ForkedNodeArg +java.lang.UnsupportedOperationException +org.apache.maven.plugin.surefire.log.api.NullConsoleLogger +org.apache.maven.surefire.api.util.internal.Channels +org.apache.maven.surefire.api.util.internal.Channels$2 +org.apache.maven.surefire.api.util.internal.Channels$1 +java.nio.channels.WritableByteChannel +org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel +java.nio.channels.ReadableByteChannel +org.apache.maven.surefire.api.util.internal.AbstractNoninterruptibleWritableChannel +org.apache.maven.surefire.api.util.internal.Channels$4 +java.nio.channels.ClosedChannelException +java.nio.channels.NonWritableChannelException +org.apache.maven.surefire.booter.spi.AbstractMasterProcessChannelProcessorFactory$1 +java.util.concurrent.FutureTask +java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask +java.lang.invoke.VarHandles +java.lang.invoke.VarHandleInts$FieldInstanceReadOnly +java.lang.invoke.VarHandleInts$FieldInstanceReadWrite +java.lang.invoke.VarHandle$1 +jdk.internal.util.Preconditions$1 +java.lang.invoke.VarHandleGuards +java.lang.invoke.VarForm +java.lang.invoke.VarHandleReferences$FieldInstanceReadOnly +java.lang.invoke.VarHandleReferences$FieldInstanceReadWrite +java.util.concurrent.FutureTask$WaitNode +java.util.concurrent.Executors$RunnableAdapter +java.util.concurrent.ThreadPoolExecutor$Worker +java.lang.Thread$State +java.util.concurrent.TimeUnit$1 +java.time.temporal.TemporalUnit +java.time.temporal.ChronoUnit +java.time.temporal.TemporalAmount +java.time.Duration +java.math.BigInteger +java.lang.invoke.VarHandle$AccessDescriptor +org.apache.maven.surefire.api.stream.AbstractStreamEncoder +java.util.concurrent.ForkJoinPool$ManagedBlocker +org.apache.maven.surefire.booter.stream.EventEncoder +org.apache.maven.surefire.booter.spi.EventChannelEncoder +java.util.concurrent.locks.AbstractQueuedSynchronizer$Node +java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode +java.lang.AssertionError +org.apache.maven.surefire.api.booter.ForkedProcessEventType +org.apache.maven.surefire.api.report.ReportEntry +java.util.concurrent.atomic.AtomicBoolean +org.apache.maven.surefire.booter.spi.CommandChannelDecoder +org.apache.maven.surefire.api.stream.MalformedChannelException +org.apache.maven.surefire.api.stream.AbstractStreamDecoder +org.apache.maven.surefire.booter.stream.CommandDecoder +org.apache.maven.surefire.api.util.internal.AbstractNoninterruptibleReadableChannel +org.apache.maven.surefire.api.util.internal.Channels$3 +java.nio.channels.NonReadableChannelException +java.io.EOFException +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$MalformedFrameException +java.io.FileNotFoundException +org.apache.maven.surefire.api.stream.SegmentType +org.apache.maven.surefire.api.booter.Constants +java.nio.charset.StandardCharsets +sun.nio.cs.US_ASCII +sun.nio.cs.ISO_8859_1 +sun.nio.cs.UTF_16BE +sun.nio.cs.UTF_16LE +sun.nio.cs.UTF_16 +org.apache.maven.surefire.api.booter.MasterProcessCommand +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$Segment +org.apache.maven.surefire.booter.ForkedBooter$8 +org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils +java.lang.ApplicationShutdownHooks +java.lang.ApplicationShutdownHooks$1 +java.lang.Shutdown +java.lang.Shutdown$Lock +org.apache.maven.surefire.api.booter.ForkingReporterFactory +org.apache.maven.surefire.api.report.RunListener +org.apache.maven.surefire.api.report.TestOutputReceiver +org.apache.maven.surefire.api.report.TestReportListener +org.apache.maven.surefire.api.booter.ForkingRunListener +org.apache.maven.surefire.booter.CommandReader +org.apache.maven.surefire.api.testset.TestSetFailedException +java.util.concurrent.ConcurrentLinkedQueue +java.util.concurrent.ConcurrentLinkedQueue$Node +org.apache.maven.surefire.booter.CommandReader$CommandRunnable +java.util.concurrent.atomic.AtomicReference +java.util.concurrent.CountDownLatch +java.util.concurrent.CountDownLatch$Sync +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$Memento +org.apache.maven.surefire.booter.PpidChecker +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$BufferedStream +org.apache.maven.surefire.booter.PpidChecker$ProcessInfoConsumer +org.apache.maven.surefire.booter.PpidChecker$1 +org.apache.maven.surefire.booter.PpidChecker$2 +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$StreamReadStatus +org.apache.maven.surefire.booter.stream.CommandDecoder$1 +java.lang.NoSuchFieldError +org.apache.maven.surefire.shared.lang3.SystemUtils +org.apache.maven.surefire.api.booter.Command +org.apache.maven.surefire.booter.CommandReader$1 +java.util.concurrent.ConcurrentLinkedQueue$Itr +org.apache.maven.surefire.shared.lang3.SystemProperties +org.apache.maven.surefire.shared.lang3.function.Suppliers +org.apache.maven.surefire.shared.lang3.function.Suppliers$$Lambda$17/0x00007f4204010000 +org.apache.maven.surefire.shared.lang3.StringUtils +java.util.regex.Pattern +java.util.regex.Pattern$Node +java.util.regex.Pattern$LastNode +java.util.regex.Pattern$GroupHead +java.util.regex.CharPredicates +java.lang.Character$Subset +java.lang.Character$UnicodeBlock +java.util.regex.Pattern$CharPredicate +java.lang.invoke.LambdaForm$DMH/0x00007f4204014000 +java.util.regex.CharPredicates$$Lambda$18/0x00007f420405bdc8 +java.util.regex.Pattern$BmpCharPredicate +java.util.regex.Pattern$CharProperty +java.util.regex.Pattern$Qtype +java.util.regex.Pattern$BmpCharProperty +java.util.regex.Pattern$CharPropertyGreedy +java.util.regex.Pattern$SliceNode +java.util.regex.Pattern$Slice +java.util.regex.Pattern$Begin +java.util.regex.Pattern$First +java.util.regex.Pattern$Start +java.util.regex.Pattern$StartS +java.util.regex.Pattern$TreeInfo +org.apache.maven.surefire.shared.lang3.JavaVersion +org.apache.maven.surefire.shared.lang3.SystemProperties$$Lambda$19/0x00007f4204010678 +org.apache.maven.surefire.shared.lang3.math.NumberUtils +java.lang.NumberFormatException +java.math.BigDecimal +jdk.internal.math.FloatingDecimal +jdk.internal.math.FloatingDecimal$BinaryToASCIIConverter +jdk.internal.math.FloatingDecimal$ExceptionalBinaryToASCIIBuffer +jdk.internal.math.FloatingDecimal$BinaryToASCIIBuffer +jdk.internal.math.FloatingDecimal$1 +jdk.internal.math.FloatingDecimal$ASCIIToBinaryConverter +jdk.internal.math.FloatingDecimal$PreparedASCIIToBinaryBuffer +jdk.internal.math.FloatingDecimal$ASCIIToBinaryBuffer +org.apache.maven.surefire.shared.lang3.SystemUtils$$Lambda$20/0x00007f4204010ca0 +java.util.regex.Pattern$GroupTail +java.util.regex.CharPredicates$$Lambda$16/0x800000024 +java.util.regex.Pattern$BmpCharPropertyGreedy +java.util.regex.Pattern$$Lambda$18/0x800000028 +java.util.regex.Pattern$Ques +java.util.regex.Pattern$BranchConn +java.util.regex.Pattern$Branch +java.util.regex.ASCII +java.util.regex.Pattern$Curly +java.util.regex.CharPredicates$$Lambda$17/0x800000025 +java.util.regex.Pattern$Dollar +java.util.regex.Pattern$BitClass +org.apache.maven.surefire.booter.ForkedBooter$4 +org.apache.maven.surefire.api.booter.BiProperty +org.apache.maven.surefire.booter.ForkedBooter$3 +org.apache.maven.surefire.booter.ForkedBooter$PingScheduler +org.apache.maven.surefire.api.provider.ProviderParameters +org.apache.maven.surefire.api.booter.BaseProviderFactory +org.apache.maven.surefire.api.util.DirectoryScanner +org.apache.maven.surefire.api.util.ScanResult +org.apache.maven.surefire.api.util.RunOrderCalculator +org.apache.maven.surefire.api.util.ReflectionUtils +org.apache.maven.surefire.api.util.SurefireReflectionException +java.lang.reflect.InvocationTargetException +java.lang.IllegalAccessException +org.apache.maven.surefire.api.provider.SurefireProvider +org.apache.maven.surefire.api.provider.AbstractProvider +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +org.junit.platform.launcher.Launcher +org.apache.maven.surefire.api.util.ScannerFilter +java.io.StringReader +java.io.UncheckedIOException +org.apache.maven.surefire.junitplatform.LazyLauncher +org.junit.platform.launcher.TagFilter +org.junit.platform.commons.JUnitException +org.junit.platform.commons.util.PreconditionViolationException +org.junit.platform.commons.PreconditionViolationException +org.junit.platform.engine.Filter +org.junit.platform.launcher.PostDiscoveryFilter +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$24/0x00007f4204016200 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$25/0x00007f4204016440 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$26/0x00007f4204016678 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$27/0x00007f42040168b8 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$28/0x00007f4204016af0 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$29/0x00007f4204016d40 +org.apache.maven.surefire.junitplatform.TestMethodFilter +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$30/0x00007f42040171f0 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$31/0x00007f4204017430 +org.junit.platform.launcher.EngineFilter +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$32/0x00007f42040178c0 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$33/0x00007f4204017b00 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$34/0x00007f4204017d38 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$35/0x00007f4204015000 +org.junit.platform.launcher.TestExecutionListener +org.apache.maven.surefire.report.RunModeSetter +org.apache.maven.surefire.junitplatform.RunListenerAdapter +org.apache.maven.surefire.api.report.OutputReportEntry +org.apache.maven.surefire.api.report.TestSetReportEntry +org.apache.maven.surefire.api.report.StackTraceWriter +org.apache.maven.surefire.report.ClassMethodIndexer +org.apache.maven.surefire.api.report.RunMode +org.apache.maven.surefire.api.report.ConsoleOutputCapture +org.apache.maven.surefire.api.report.ConsoleOutputCapture$ForwardingPrintStream +org.apache.maven.surefire.api.report.ConsoleOutputCapture$NullOutputStream +java.util.logging.Logger +java.util.logging.Handler +java.util.logging.Level +java.util.logging.Level$KnownLevel +java.util.logging.Level$KnownLevel$$Lambda$13/0x800000021 +java.util.logging.Level$KnownLevel$$Lambda$14/0x800000022 +java.util.logging.Logger$LoggerBundle +java.util.logging.Logger$ConfigurationData +java.util.logging.LogManager$1 +java.util.logging.LogManager$LoggerContext +java.util.logging.LogManager$SystemLoggerContext +java.util.logging.LogManager$LogNode +java.util.Collections$SynchronizedMap +java.util.logging.LogManager$Cleaner +java.util.logging.LoggingPermission +sun.util.logging.internal.LoggingProviderImpl$LogManagerAccess +java.util.logging.LogManager$LoggingProviderAccess +java.lang.System$LoggerFinder +jdk.internal.logger.DefaultLoggerFinder +sun.util.logging.internal.LoggingProviderImpl +java.util.logging.LogManager$2 +java.util.logging.LogManager$RootLogger +java.util.logging.LogManager$LoggerWeakRef +java.lang.invoke.MethodHandleImpl$AsVarargsCollector +java.lang.invoke.BoundMethodHandle$Species_LL +java.lang.invoke.LambdaForm$MH/0x00007f420401c000 +java.util.logging.LogManager$VisitedLoggers +java.util.logging.LogManager$LoggerContext$1 +java.util.concurrent.ConcurrentHashMap$KeySetView +java.util.Collections$3 +java.util.concurrent.ConcurrentHashMap$KeyIterator +java.util.Hashtable$Enumerator +java.util.logging.Level$$Lambda$12/0x800000010 +java.util.ArrayList$ArrayListSpliterator +java.util.logging.Level$KnownLevel$$Lambda$15/0x800000023 +java.util.stream.ReferencePipeline$7 +java.util.stream.FindOps +java.util.stream.FindOps$FindSink +java.util.stream.FindOps$FindSink$OfRef +java.util.stream.FindOps$FindOp +java.util.stream.FindOps$FindSink$OfRef$$Lambda$40/0x80000004b +java.util.stream.FindOps$FindSink$OfRef$$Lambda$38/0x800000049 +java.util.stream.FindOps$FindSink$OfRef$$Lambda$39/0x80000004a +java.util.stream.FindOps$FindSink$OfRef$$Lambda$37/0x800000048 +java.util.stream.ReferencePipeline$7$1 +java.util.stream.Streams$AbstractStreamBuilderImpl +java.util.stream.Stream$Builder +java.util.stream.Streams$StreamBuilderImpl +java.util.stream.Streams +java.util.IdentityHashMap$Values +java.lang.System$Logger +sun.util.logging.PlatformLogger$Bridge +sun.util.logging.PlatformLogger$ConfigurableBridge +jdk.internal.logger.BootstrapLogger +jdk.internal.logger.BootstrapLogger$DetectBackend +jdk.internal.logger.BootstrapLogger$DetectBackend$1 +jdk.internal.logger.BootstrapLogger$LoggingBackend +jdk.internal.logger.BootstrapLogger$RedirectedLoggers +jdk.internal.logger.BootstrapLogger$BootstrapExecutors +java.util.logging.LogManager$4 +java.util.logging.Logger$SystemLoggerHelper +java.util.logging.Logger$SystemLoggerHelper$1 +jdk.internal.logger.DefaultLoggerFinder$1 +org.apache.maven.surefire.junitplatform.TestPlanScannerFilter +org.apache.maven.surefire.api.util.DefaultScanResult +jdk.internal.loader.URLClassPath$FileLoader$1 +software.amazon.lambda.powertools.kafka.internal.DeserializationUtilsTest +org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder +org.junit.platform.engine.ConfigurationParameters +org.junit.platform.engine.EngineDiscoveryRequest +org.junit.platform.launcher.LauncherDiscoveryRequest +org.junit.platform.engine.reporting.OutputDirectoryProvider +org.junit.platform.engine.DiscoverySelector +org.junit.platform.engine.discovery.DiscoverySelectors +org.junit.platform.commons.util.Preconditions +org.junit.platform.commons.util.StringUtils +org.junit.platform.commons.util.StringUtils$TwoPartSplitResult +java.util.regex.CharPredicates$$Lambda$44/0x00007f420405d900 +org.junit.platform.engine.discovery.ClassSelector +java.lang.invoke.LambdaForm$DMH/0x00007f420401c400 +org.junit.platform.commons.util.Preconditions$$Lambda$45/0x00007f420401a5b0 +org.junit.platform.commons.util.Preconditions$$Lambda$46/0x00007f420401a7e8 +java.lang.invoke.LambdaForm$DMH/0x00007f420401c800 +java.lang.invoke.DirectMethodHandle$Special +org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder$$Lambda$47/0x00007f420401aa20 +org.junit.platform.launcher.core.LauncherConfigurationParameters +org.junit.platform.commons.logging.LoggerFactory +org.junit.platform.commons.logging.Logger +org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger +org.junit.platform.launcher.core.LauncherConfigurationParameters$Builder +org.junit.platform.launcher.core.LauncherConfigurationParameters$Builder$$Lambda$48/0x00007f420401b760 +org.junit.platform.commons.util.CollectionUtils +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$2 +org.junit.platform.commons.util.ClassLoaderUtils +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$3 +org.junit.platform.launcher.core.LauncherConfigurationParameters$$Lambda$49/0x00007f420401e458 +org.junit.platform.launcher.core.LauncherConfigurationParameters$$Lambda$50/0x00007f420401e6a0 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners +org.junit.platform.engine.EngineDiscoveryListener +org.junit.platform.launcher.LauncherDiscoveryListener +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$LauncherDiscoveryListenerType +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$LauncherDiscoveryListenerType$$Lambda$51/0x00007f420401f140 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$LauncherDiscoveryListenerType$$Lambda$52/0x00007f420401f360 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$$Lambda$53/0x00007f420401f778 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$$Lambda$54/0x00007f420401f9d0 +org.junit.platform.launcher.listeners.discovery.AbortOnFailureLauncherDiscoveryListener +org.junit.platform.engine.EngineDiscoveryListener$1 +org.junit.platform.launcher.LauncherDiscoveryListener$1 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$$Lambda$55/0x00007f420401d4c0 +org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider +java.util.regex.Pattern$$Lambda$56/0x00007f420405df20 +java.util.regex.Pattern$CharPredicate$$Lambda$57/0x00007f420405e180 +java.util.regex.Pattern$CharPredicate$$Lambda$21/0x80000002d +org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder$$Lambda$59/0x00007f420401d928 +org.junit.platform.launcher.core.DefaultDiscoveryRequest +org.junit.platform.launcher.LauncherSession +org.junit.platform.launcher.core.LauncherFactory +org.junit.platform.launcher.core.LauncherConfig +org.junit.platform.launcher.core.LauncherConfig$Builder +org.junit.platform.launcher.core.DefaultLauncherConfig +org.junit.platform.launcher.core.DefaultLauncherSession +org.junit.platform.launcher.LauncherInterceptor +org.junit.platform.launcher.core.DefaultLauncherSession$1 +org.junit.platform.launcher.core.LauncherConfigurationParameters$$Lambda$60/0x00007f4204020dd0 +org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor +org.junit.platform.launcher.LauncherSessionListener +org.junit.platform.launcher.core.LauncherFactory$$Lambda$61/0x00007f4204021448 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore +org.junit.platform.launcher.core.LauncherFactory$$Lambda$62/0x00007f4204021898 +org.junit.platform.engine.support.store.NamespacedHierarchicalStoreException +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$CloseAction +java.lang.invoke.LambdaForm$DMH/0x00007f4204024000 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$CloseAction$$Lambda$63/0x00007f4204021f40 +java.util.stream.SliceOps +java.util.stream.ReferencePipeline$StatefulOp +java.util.stream.SliceOps$1 +org.junit.platform.launcher.core.DefaultLauncherSession$$Lambda$64/0x00007f4204022160 +java.util.stream.ReduceOps$1 +java.util.stream.ReduceOps$1ReducingSink +java.util.stream.SliceOps$1$1 +org.junit.platform.launcher.LauncherInterceptor$Invocation +java.lang.invoke.LambdaForm$DMH/0x00007f4204024400 +org.junit.platform.launcher.core.DefaultLauncherSession$$Lambda$65/0x00007f42040225a8 +org.junit.platform.launcher.core.ListenerRegistry +org.junit.platform.launcher.listeners.session.LauncherSessionListeners +org.junit.platform.launcher.core.ListenerRegistry$$Lambda$66/0x00007f4204022c08 +org.junit.platform.launcher.core.ServiceLoaderRegistry +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$67/0x00007f4204023050 +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$68/0x00007f42040232a0 +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$69/0x00007f42040234e8 +org.junit.platform.commons.util.ServiceLoaderUtils +java.util.ServiceLoader$ProviderSpliterator +org.junit.platform.commons.util.ServiceLoaderUtils$$Lambda$70/0x00007f4204023948 +org.junit.platform.commons.util.ServiceLoaderUtils$$Lambda$71/0x00007f4204023ba0 +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$72/0x00007f4204026000 +java.lang.invoke.LambdaForm$DMH/0x00007f4204024800 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$73/0x00007f4204026228 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$74/0x00007f4204026460 +org.junit.platform.launcher.LauncherSessionListener$1 +org.junit.platform.launcher.core.DelegatingLauncher +org.junit.platform.launcher.core.InterceptingLauncher +org.junit.platform.launcher.core.DefaultLauncherSession$$Lambda$75/0x00007f4204026db0 +org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry +org.junit.platform.engine.TestEngine +org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry$$Lambda$76/0x00007f42040271d8 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$77/0x00007f4204027400 +org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine +org.junit.jupiter.engine.JupiterTestEngine +org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService +org.junit.jupiter.engine.config.JupiterConfiguration +org.junit.platform.engine.TestDescriptor +org.junit.platform.engine.support.hierarchical.EngineExecutionContext +org.junit.platform.launcher.core.LauncherFactory$$Lambda$78/0x00007f4204025400 +org.junit.platform.launcher.core.DefaultLauncher +org.junit.platform.launcher.TestPlan +org.junit.platform.launcher.core.InternalTestPlan +org.junit.platform.launcher.core.LauncherListenerRegistry +org.junit.platform.launcher.core.ListenerRegistry$$Lambda$79/0x00007f4204024c00 +org.junit.platform.launcher.core.CompositeTestExecutionListener +org.junit.platform.launcher.core.ListenerRegistry$$Lambda$80/0x00007f42040282a0 +org.junit.platform.launcher.core.EngineExecutionOrchestrator +org.junit.platform.launcher.TestPlan$Visitor +org.junit.platform.engine.EngineExecutionListener +org.junit.platform.launcher.core.DiscoveryIssueException +org.junit.platform.launcher.core.DefaultLauncher$$Lambda$81/0x00007f4204028d68 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator +org.junit.platform.launcher.core.EngineDiscoveryResultValidator +org.junit.platform.launcher.core.EngineIdValidator +org.junit.platform.launcher.core.EngineIdValidator$$Lambda$82/0x00007f42040295d0 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$83/0x00007f42040297f8 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$84/0x00007f4204029a30 +org.junit.platform.commons.util.ClassNamePatternFilterUtils +org.junit.platform.launcher.core.LauncherFactory$$Lambda$85/0x00007f4204029e70 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$86/0x00007f420402a0b0 +java.lang.invoke.LambdaForm$DMH/0x00007f420402c000 +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$87/0x00007f420402a300 +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$88/0x00007f420402a558 +org.junitpioneer.jupiter.issue.IssueExtensionExecutionListener +org.junitpioneer.jupiter.IssueProcessor +org.junit.platform.launcher.listeners.UniqueIdTrackingListener +org.junit.platform.launcher.core.LauncherFactory$$Lambda$89/0x00007f420402aee8 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$90/0x00007f420402b120 +org.junit.platform.launcher.core.InterceptingLauncher$$Lambda$91/0x00007f420402b358 +org.junit.platform.launcher.core.LauncherPhase +org.junit.platform.engine.UniqueId +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$92/0x00007f420402bbf0 +org.junit.platform.launcher.core.DiscoveryIssueCollector +org.junit.platform.engine.TestSource +org.junit.platform.launcher.listeners.discovery.CompositeLauncherDiscoveryListener +org.junit.platform.launcher.core.DelegatingLauncherDiscoveryRequest +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$1 +org.junit.platform.launcher.listeners.discovery.CompositeLauncherDiscoveryListener$$Lambda$93/0x00007f420402ecc8 +org.junit.platform.launcher.core.EngineFilterer +org.junit.platform.engine.FilterResult +org.junit.platform.launcher.core.EngineFilterer$$Lambda$94/0x00007f420402f348 +java.lang.invoke.LambdaForm$DMH/0x00007f420402c400 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$95/0x00007f420402f590 +java.util.stream.MatchOps$MatchKind +java.util.stream.MatchOps +java.util.stream.MatchOps$MatchOp +java.util.stream.MatchOps$BooleanTerminalSink +java.util.stream.MatchOps$$Lambda$96/0x00007f4204060150 +java.util.stream.MatchOps$1MatchSink +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$97/0x00007f420402f7e0 +org.junit.platform.engine.UniqueIdFormat +java.io.UnsupportedEncodingException +java.util.Formatter +java.util.regex.Pattern$$Lambda$19/0x800000029 +java.util.regex.Pattern$BmpCharPredicate$$Lambda$20/0x80000002b +java.util.Locale$Category +java.util.Formatter$Conversion +java.util.Formatter$FormatString +java.util.Formatter$FormatSpecifier +java.util.Formatter$Flags +java.util.Formatter$FixedString +java.util.Formattable +java.util.regex.Pattern$$Lambda$100/0x00007f4204060cd8 +java.lang.invoke.LambdaForm$DMH/0x00007f420402c800 +org.junit.platform.engine.UniqueIdFormat$$Lambda$101/0x00007f420402fc28 +java.net.URLEncoder +java.util.BitSet +java.io.CharArrayWriter +org.junit.platform.engine.UniqueIdFormat$$Lambda$102/0x00007f420402d000 +org.junit.platform.engine.UniqueIdFormat$$Lambda$103/0x00007f420402d240 +org.junit.platform.engine.UniqueIdFormat$$Lambda$104/0x00007f420402d480 +org.junit.platform.engine.UniqueIdFormat$$Lambda$105/0x00007f420402d6c0 +org.junit.platform.engine.UniqueIdFormat$$Lambda$106/0x00007f420402d900 +org.junit.platform.engine.UniqueId$Segment +org.junit.platform.launcher.listeners.discovery.CompositeLauncherDiscoveryListener$$Lambda$107/0x00007f420402dd60 +org.junit.jupiter.engine.config.CachingJupiterConfiguration +org.junit.jupiter.engine.config.DefaultJupiterConfiguration +org.junit.jupiter.api.parallel.ExecutionMode +org.junit.jupiter.api.TestInstance$Lifecycle +org.junit.jupiter.api.io.CleanupMode +org.junit.jupiter.api.extension.TestInstantiationAwareExtension$ExtensionContextScope +org.junit.jupiter.engine.config.EnumConfigurationParameterConverter +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter +org.junit.jupiter.api.DisplayNameGenerator +org.junit.jupiter.api.MethodOrderer +org.junit.jupiter.api.ClassOrderer +org.junit.jupiter.api.io.TempDirFactory +org.junit.platform.engine.support.hierarchical.Node +org.junit.platform.engine.support.descriptor.AbstractTestDescriptor +org.junit.platform.engine.support.descriptor.EngineDescriptor +org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor +org.junit.jupiter.engine.extension.ExtensionRegistry +org.junit.jupiter.api.extension.ExtensionContext +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver +org.junit.platform.engine.support.discovery.SelectorResolver +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$InitializationContext +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$Builder +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$108/0x00007f42040333f8 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$Builder$$Lambda$109/0x00007f4204033638 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$110/0x00007f4204033880 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$111/0x00007f4204033ac0 +org.junit.platform.engine.TestDescriptor$Visitor +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$112/0x00007f4204033f00 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter +org.junit.platform.engine.DiscoveryIssue +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$$Lambda$113/0x00007f4204034540 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$$Lambda$114/0x00007f4204034788 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$DefaultInitializationContext +org.junit.platform.engine.DiscoveryFilter +org.junit.platform.engine.discovery.ClassNameFilter +org.junit.platform.launcher.core.DefaultDiscoveryRequest$$Lambda$115/0x00007f4204035040 +org.junit.platform.launcher.core.DefaultDiscoveryRequest$$Lambda$116/0x00007f4204035298 +org.junit.platform.engine.discovery.PackageNameFilter +org.junit.platform.engine.CompositeFilter +org.junit.platform.engine.CompositeFilter$1 +org.junit.platform.engine.CompositeFilter$1$$Lambda$117/0x00007f4204035b58 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$$Lambda$118/0x00007f4204035da8 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$$Lambda$119/0x00007f4204035ff0 +java.util.stream.Collectors$$Lambda$120/0x00007f4204061750 +java.util.stream.Collectors$$Lambda$121/0x00007f4204061980 +org.junit.platform.engine.support.discovery.ClassContainerSelectorResolver +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$122/0x00007f4204036740 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$123/0x00007f4204036990 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$124/0x00007f4204036be0 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$125/0x00007f4204036e38 +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod +org.junit.jupiter.engine.discovery.predicates.IsTestMethod +org.junit.jupiter.api.Test +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$Condition +org.junit.jupiter.engine.discovery.predicates.IsTestMethod$$Lambda$126/0x00007f4204037960 +org.junit.platform.commons.support.ModifierSupport +java.lang.invoke.LambdaForm$DMH/0x00007f4204038000 +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod$$Lambda$127/0x00007f4204037d98 +java.lang.invoke.MethodHandle$1 +java.lang.invoke.LambdaForm$DMH/0x00007f4204038400 +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod$$Lambda$128/0x00007f420403c000 +java.lang.invoke.LambdaForm$DMH/0x00007f4204038800 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$$Lambda$129/0x00007f420403c248 +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod$$Lambda$130/0x00007f420403c4a0 +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod$$Lambda$131/0x00007f420403c6f0 +java.lang.invoke.LambdaForm$DMH/0x00007f4204038c00 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$Condition$$Lambda$132/0x00007f420403c938 +org.junit.platform.commons.util.ReflectionUtils +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod$$Lambda$133/0x00007f420403cd98 +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod$$Lambda$134/0x00007f420403cfe8 +org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod +org.junit.jupiter.api.DynamicNode +java.util.regex.MatchResult +java.util.regex.Matcher +java.util.regex.IntHashSet +org.junit.jupiter.api.TestFactory +org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod$$Lambda$135/0x00007f420403d8b8 +org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod$$Lambda$136/0x00007f420403dae8 +org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod$$Lambda$137/0x00007f420403dd40 +java.util.function.Predicate$$Lambda$138/0x00007f4204062210 +org.junit.jupiter.engine.discovery.predicates.IsTestTemplateMethod +org.junit.jupiter.api.TestTemplate +org.junit.jupiter.engine.discovery.predicates.IsTestTemplateMethod$$Lambda$139/0x00007f420403e3f0 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$140/0x00007f420403e620 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$141/0x00007f420403e870 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$142/0x00007f420403eab8 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$143/0x00007f420403ed08 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$144/0x00007f420403ef48 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$145/0x00007f420403f198 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$146/0x00007f420403f3d8 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$147/0x00007f420403f628 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$148/0x00007f420403f868 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$149/0x00007f420403fab8 +org.junit.jupiter.engine.discovery.ClassSelectorResolver +org.junit.jupiter.engine.descriptor.ResourceLockAware +org.junit.jupiter.engine.descriptor.TestClassAware +org.junit.jupiter.engine.descriptor.Validatable +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor +org.junit.jupiter.engine.descriptor.ClassTestDescriptor +org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor +org.junit.jupiter.api.extension.ClassTemplateInvocationContext +org.junit.jupiter.engine.descriptor.Filterable +org.junit.jupiter.engine.descriptor.ClassTemplateTestDescriptor +org.junit.jupiter.engine.discovery.MethodSelectorResolver +org.junit.jupiter.engine.discovery.MethodFinder +java.util.regex.Pattern$$Lambda$150/0x00007f4204062660 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$TestDescriptorFactory +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor +org.junit.jupiter.engine.extension.ExtensionRegistrar +java.lang.invoke.LambdaForm$DMH/0x00007f4204084000 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$151/0x00007f4204080f00 +org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor +org.junit.jupiter.engine.descriptor.DynamicNodeTestDescriptor +org.junit.jupiter.engine.descriptor.DynamicContainerTestDescriptor +org.junit.jupiter.engine.descriptor.DynamicTestTestDescriptor +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$152/0x00007f4204082438 +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$153/0x00007f4204082bf0 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor +org.junit.jupiter.engine.discovery.ClassOrderingVisitor +org.junit.jupiter.api.ClassOrdererContext +org.junit.platform.commons.util.LruCache +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$154/0x00007f4204083af0 +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter$$Lambda$155/0x00007f4204083d38 +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter$$Lambda$156/0x00007f4204086000 +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter$$Lambda$157/0x00007f4204086250 +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter$$Lambda$158/0x00007f4204086498 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$DescriptorWrapperOrderer +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$MessageGenerator +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$DescriptorWrapperOrderer$$Lambda$159/0x00007f4204086ad0 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$DescriptorWrapperOrderer$$Lambda$160/0x00007f4204086cf0 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$161/0x00007f4204086f10 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$162/0x00007f4204087160 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor +org.junit.jupiter.api.MethodOrdererContext +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$163/0x00007f42040877e8 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$164/0x00007f4204087a38 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$165/0x00007f4204087c78 +java.lang.invoke.LambdaForm$MH/0x00007f4204084400 +java.util.Comparator$$Lambda$166/0x00007f42040628c0 +java.util.Collections$ReverseComparator +java.util.Comparators$NaturalOrderComparator +java.util.Collections$ReverseComparator2 +java.util.function.UnaryOperator +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$167/0x00007f4204085000 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$168/0x00007f4204085260 +org.junit.platform.engine.CompositeTestDescriptorVisitor +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution +org.junit.platform.engine.support.discovery.SelectorResolver$Context +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$DefaultContext +org.junit.platform.engine.support.discovery.SelectorResolver$Match +org.junit.platform.engine.support.discovery.SelectorResolver$Match$$Lambda$169/0x00007f4204084800 +org.junit.platform.engine.support.discovery.SelectorResolver$Match$Type +org.junit.platform.launcher.core.DefaultDiscoveryRequest$$Lambda$170/0x00007f4204088000 +org.junit.platform.launcher.core.DefaultDiscoveryRequest$$Lambda$171/0x00007f4204088258 +java.util.ArrayDeque$$Lambda$172/0x00007f4204063970 +org.junit.platform.engine.discovery.UniqueIdSelector +org.junit.platform.engine.support.discovery.SelectorResolver$Resolution +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$173/0x00007f4204088900 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$174/0x00007f4204088b48 +org.junit.platform.engine.discovery.ClasspathResourceSelector +org.junit.platform.engine.discovery.ClasspathRootSelector +org.junit.platform.commons.support.ReflectionSupport +org.junit.platform.commons.util.ClasspathScannerLoader +org.junit.platform.commons.support.scanning.ClasspathScanner +java.util.Spliterators$IteratorSpliterator +jdk.internal.misc.ScopedMemoryAccess$Scope +org.junit.platform.commons.support.scanning.DefaultClasspathScanner +java.nio.file.FileVisitor +org.junit.platform.commons.util.ClasspathScannerLoader$$Lambda$175/0x00007f4204089a88 +org.junit.platform.commons.function.Try +java.lang.invoke.LambdaForm$DMH/0x00007f420408c000 +org.junit.platform.commons.util.ClasspathScannerLoader$$Lambda$176/0x00007f4204089ef8 +java.lang.invoke.LambdaForm$DMH/0x00007f420408c400 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$177/0x00007f420408a128 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$178/0x00007f420408a360 +org.junit.platform.commons.function.Try$Failure +org.junit.platform.commons.function.Try$Success +org.junit.platform.commons.function.Try$$Lambda$179/0x00007f420408aa38 +java.util.regex.Pattern$1 +org.junit.platform.engine.discovery.ClassSelector$$Lambda$180/0x00007f420408ac60 +org.junit.jupiter.api.Nested +org.junit.platform.commons.support.AnnotationSupport +org.junit.platform.commons.util.AnnotationUtils +java.lang.annotation.Inherited +sun.reflect.generics.parser.SignatureParser +sun.reflect.generics.tree.Tree +sun.reflect.generics.tree.TypeTree +sun.reflect.generics.tree.TypeArgument +sun.reflect.generics.tree.ReturnType +sun.reflect.generics.tree.TypeSignature +sun.reflect.generics.tree.BaseType +sun.reflect.generics.tree.FieldTypeSignature +sun.reflect.generics.tree.SimpleClassTypeSignature +sun.reflect.generics.tree.ClassTypeSignature +sun.reflect.generics.scope.Scope +sun.reflect.generics.scope.AbstractScope +sun.reflect.generics.scope.ClassScope +sun.reflect.generics.factory.GenericsFactory +sun.reflect.generics.factory.CoreReflectionFactory +sun.reflect.generics.visitor.TypeTreeVisitor +sun.reflect.generics.visitor.Reifier +java.lang.annotation.Target +java.lang.reflect.GenericArrayType +sun.reflect.annotation.AnnotationType +sun.reflect.annotation.AnnotationType$1 +java.lang.annotation.ElementType +java.lang.annotation.Retention +java.lang.annotation.Documented +java.lang.annotation.RetentionPolicy +sun.reflect.annotation.ExceptionProxy +sun.reflect.annotation.AnnotationTypeMismatchExceptionProxy +sun.reflect.annotation.AnnotationParser$1 +java.lang.reflect.InvocationHandler +sun.reflect.annotation.AnnotationInvocationHandler +java.lang.reflect.Proxy +java.lang.ClassValue +java.lang.reflect.Proxy$1 +java.lang.ClassValue$Entry +java.lang.ClassValue$Identity +java.lang.ClassValue$Version +jdk.internal.loader.AbstractClassLoaderValue$Sub +java.lang.reflect.Proxy$$Lambda$181/0x00007f420406bf50 +java.lang.reflect.Proxy$ProxyBuilder +java.lang.PublicMethods +java.util.LinkedHashMap$LinkedValues +java.util.LinkedHashMap$LinkedValueIterator +java.lang.reflect.Proxy$ProxyBuilder$$Lambda$182/0x00007f420406cb68 +java.lang.module.ModuleDescriptor$Modifier +java.lang.module.ModuleDescriptor$Builder +jdk.internal.module.Checks +java.lang.module.ModuleDescriptor$Builder$$Lambda$1/0x800000002 +java.lang.reflect.Proxy$$Lambda$184/0x00007f420406cd98 +java.lang.reflect.ProxyGenerator +java.lang.reflect.ProxyGenerator$ProxyMethod +java.util.StringJoiner +java.lang.reflect.ProxyGenerator$$Lambda$185/0x00007f420406d4e8 +java.lang.reflect.ProxyGenerator$$Lambda$186/0x00007f420406d728 +java.lang.reflect.ProxyGenerator$PrimitiveTypeInfo +jdk.internal.org.objectweb.asm.Edge +jdk.proxy1.$Proxy0 +java.lang.reflect.Proxy$ProxyBuilder$1 +sun.reflect.annotation.AnnotationParser$$Lambda$187/0x00007f420406e218 +java.lang.invoke.LambdaForm$DMH/0x00007f420408c800 +jdk.proxy1.$Proxy1 +jdk.proxy1.$Proxy2 +org.apiguardian.api.API +org.apiguardian.api.API$Status +jdk.proxy2.$Proxy3 +java.lang.reflect.UndeclaredThrowableException +java.lang.Class$AnnotationData +org.junit.platform.commons.util.KotlinReflectionUtils +org.junit.platform.commons.function.Try$Transformer +org.junit.platform.commons.util.KotlinReflectionUtils$$Lambda$188/0x00007f420408e200 +org.junit.jupiter.api.ClassTemplate +jdk.proxy1.$Proxy4 +org.junit.platform.commons.annotation.Testable +jdk.proxy2.$Proxy5 +org.junit.platform.commons.util.ReflectionUtils$HierarchyTraversalMode +org.junit.platform.commons.util.ReflectionUtils$$Lambda$189/0x00007f420408eeb0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$190/0x00007f420408f100 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$191/0x00007f420408f320 +java.util.Arrays$LegacyMergeSort +java.util.TimSort +jdk.proxy2.$Proxy6 +org.junitpioneer.jupiter.SetEnvironmentVariable +java.lang.annotation.Repeatable +org.junitpioneer.jupiter.WritesEnvironmentVariable +org.junit.jupiter.api.extension.ExtendWith +jdk.proxy2.$Proxy7 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$192/0x00007f420408d260 +org.junit.jupiter.api.extension.ExtensionConfigurationException +org.junit.jupiter.api.extension.TestInstanceFactoryContext +org.junit.jupiter.api.extension.Extension +org.junit.jupiter.api.extension.TestInstantiationAwareExtension +org.junit.jupiter.api.extension.TestInstantiationException +org.junit.jupiter.engine.execution.ConditionEvaluator +org.junit.jupiter.engine.execution.ConditionEvaluationException +org.junit.jupiter.api.extension.ConditionEvaluationResult +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker +org.junit.jupiter.api.extension.ReflectiveInvocationContext +org.junit.jupiter.api.extension.InvocationInterceptor$Invocation +org.junit.jupiter.engine.execution.InvocationInterceptorChain +org.junit.jupiter.engine.descriptor.DisplayNameUtils +org.junit.jupiter.api.DisplayNameGenerator$Standard +org.junit.jupiter.api.DisplayNameGenerator$Simple +org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores +org.junit.jupiter.api.DisplayNameGenerator$IndicativeSentences +org.junit.jupiter.api.DisplayNameGenerator$IndicativeSentences$$Lambda$193/0x00007f4204091850 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$194/0x00007f4204091aa0 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$195/0x00007f4204091cc0 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$196/0x00007f4204091ef8 +org.junit.platform.engine.support.descriptor.ClassSource +org.junit.jupiter.api.DisplayName +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$197/0x00007f4204092540 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$198/0x00007f4204092780 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$199/0x00007f42040929d0 +org.junit.jupiter.api.DisplayNameGeneration +java.util.AbstractList$Itr +java.util.AbstractList$ListItr +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$200/0x00007f4204092e10 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$201/0x00007f4204093050 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$202/0x00007f4204093290 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$203/0x00007f42040934d8 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$204/0x00007f4204093700 +org.junit.jupiter.engine.config.DefaultJupiterConfiguration$$Lambda$205/0x00007f4204093948 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$ClassInfo +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$ClassInfo$$Lambda$206/0x00007f4204093d78 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$ClassInfo$$Lambda$207/0x00007f4204093fa0 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$ClassInfo$$Lambda$208/0x00007f42040941c8 +org.junit.jupiter.api.Tag +org.junit.jupiter.api.Tags +jdk.proxy1.$Proxy8 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$209/0x00007f4204094800 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$210/0x00007f4204094a28 +java.lang.invoke.LambdaForm$DMH/0x00007f4204098000 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$211/0x00007f4204094c68 +org.junit.platform.engine.TestTag +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$212/0x00007f42040950d0 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$213/0x00007f4204095310 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$214/0x00007f4204095530 +java.lang.invoke.LambdaForm$DMH/0x00007f4204098400 +java.util.function.Function$$Lambda$215/0x00007f420406fd40 +org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils +org.junit.jupiter.api.TestInstance +org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils$$Lambda$216/0x00007f4204095b78 +org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils$$Lambda$217/0x00007f4204095db8 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$218/0x00007f4204095fe0 +java.lang.invoke.LambdaForm$DMH/0x00007f4204098800 +org.junit.jupiter.engine.config.EnumConfigurationParameterConverter$$Lambda$219/0x00007f4204096228 +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$1 +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$DefaultExclusiveResourceCollector +org.junit.jupiter.api.parallel.ResourceLock +jdk.internal.reflect.ClassFileConstants +jdk.internal.reflect.AccessorGenerator +jdk.internal.reflect.MethodAccessorGenerator +jdk.internal.reflect.ByteVectorFactory +jdk.internal.reflect.ByteVector +jdk.internal.reflect.ByteVectorImpl +jdk.internal.reflect.ClassFileAssembler +jdk.internal.reflect.UTF8 +jdk.internal.reflect.Label +jdk.internal.reflect.Label$PatchInfo +jdk.internal.reflect.MethodAccessorGenerator$1 +jdk.internal.reflect.ClassDefiner +jdk.internal.reflect.ClassDefiner$1 +jdk.internal.reflect.GeneratedConstructorAccessor1 +java.lang.Class$1 +jdk.internal.reflect.BootstrapConstructorAccessorImpl +org.junit.jupiter.api.parallel.ResourceLocks +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$LifecycleMethods +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$$Lambda$220/0x00007f4204097110 +java.lang.invoke.LambdaForm$DMH/0x00007f4204099000 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$$Lambda$221/0x00007f4204097348 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils +org.junit.jupiter.api.BeforeAll +org.junit.platform.commons.support.HierarchyTraversalMode +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$222/0x00007f420409c000 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$223/0x00007f420409c248 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$224/0x00007f420409c498 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$225/0x00007f420409c6e0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$226/0x00007f420409c938 +java.util.function.IntFunction +org.junit.platform.commons.util.ReflectionUtils$$Lambda$227/0x00007f4204097dd8 +java.util.stream.Nodes +java.util.stream.Node +java.util.stream.Nodes$EmptyNode +java.util.stream.Nodes$EmptyNode$OfRef +java.util.stream.Node$OfPrimitive +java.util.stream.Node$OfInt +java.util.stream.Nodes$EmptyNode$OfInt +java.util.stream.Node$OfLong +java.util.stream.Nodes$EmptyNode$OfLong +java.util.stream.Node$OfDouble +java.util.stream.Nodes$EmptyNode$OfDouble +java.util.stream.Node$Builder +java.util.stream.AbstractSpinedBuffer +java.util.stream.SpinedBuffer +java.util.stream.Nodes$SpinedNodeBuilder +org.junit.platform.commons.util.ReflectionUtils$$Lambda$228/0x00007f420409cb88 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$229/0x00007f420409cde0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$230/0x00007f420409d000 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$231/0x00007f420409d258 +java.util.stream.DistinctOps +java.util.stream.DistinctOps$1 +org.junit.platform.commons.util.CollectionUtils$$Lambda$232/0x00007f420409d478 +java.util.stream.DistinctOps$1$2 +java.util.LinkedHashMap$LinkedEntrySet +java.util.LinkedHashMap$LinkedEntryIterator +org.junitpioneer.jupiter.SetEnvironmentVariable$SetEnvironmentVariables +jdk.proxy2.$Proxy9 +sun.reflect.annotation.AnnotationParser$$Lambda$233/0x00007f42040748a8 +org.junit.jupiter.api.extension.BeforeEachCallback +org.junit.jupiter.api.extension.AfterEachCallback +org.junit.jupiter.api.extension.BeforeAllCallback +org.junit.jupiter.api.extension.AfterAllCallback +org.junitpioneer.jupiter.AbstractEntryBasedExtension +org.junitpioneer.jupiter.EnvironmentVariableExtension +jdk.proxy2.$Proxy10 +org.junit.jupiter.api.parallel.ResourceLockTarget +org.junit.jupiter.api.parallel.ResourceAccessMode +jdk.proxy2.$Proxy11 +jdk.internal.reflect.GeneratedConstructorAccessor2 +org.junit.jupiter.api.extension.Extensions +sun.reflect.annotation.AnnotationInvocationHandler$1 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$234/0x00007f420409f808 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$235/0x00007f420409fa30 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$236/0x00007f420409fc80 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$Condition$$Lambda$237/0x00007f420409a000 +java.util.stream.ReferencePipeline$15 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$238/0x00007f420409a238 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$239/0x00007f420409a480 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$240/0x00007f420409a6d0 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$Condition$$Lambda$241/0x00007f420409a918 +java.util.stream.ReferencePipeline$15$1 +org.junit.jupiter.api.AfterAll +jdk.internal.reflect.GeneratedConstructorAccessor3 +org.junit.jupiter.api.BeforeEach +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$242/0x00007f420409af70 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$243/0x00007f420409b1b8 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$244/0x00007f420409b408 +org.junit.jupiter.api.AfterEach +jdk.internal.reflect.GeneratedConstructorAccessor4 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$245/0x00007f420409b850 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$246/0x00007f420409ba98 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$247/0x00007f420409bcc0 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$248/0x00007f42040a0000 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$249/0x00007f42040a0248 +org.junit.platform.engine.SelectorResolutionResult +org.junit.platform.engine.SelectorResolutionResult$Status +org.junit.platform.launcher.listeners.discovery.CompositeLauncherDiscoveryListener$$Lambda$250/0x00007f42040a0ae0 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$251/0x00007f42040a0d18 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$252/0x00007f42040a0f68 +java.util.stream.ForEachOps +java.util.stream.ForEachOps$ForEachOp +java.util.stream.ForEachOps$ForEachOp$OfRef +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$253/0x00007f42040a11a0 +org.junit.platform.commons.util.ReflectionUtils$CycleErrorHandling +org.junit.platform.commons.util.ReflectionUtils$CycleErrorHandling$1 +org.junit.platform.commons.util.ReflectionUtils$CycleErrorHandling$2 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$254/0x00007f42040a1cd0 +java.lang.invoke.LambdaForm$DMH/0x00007f42040a4000 +java.util.function.Predicate$$Lambda$255/0x00007f42040754b8 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$256/0x00007f42040a1f08 +java.util.function.Predicate$$Lambda$257/0x00007f4204075710 +java.util.stream.Streams$ConcatSpliterator +java.util.stream.Streams$ConcatSpliterator$OfRef +java.util.stream.Streams$2 +org.junit.platform.engine.discovery.NestedClassSelector +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$258/0x00007f42040a23b0 +java.util.stream.AbstractPipeline$$Lambda$259/0x00007f42040760d8 +java.util.stream.StreamSpliterators$AbstractWrappingSpliterator +java.util.stream.StreamSpliterators$WrappingSpliterator +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$260/0x00007f42040a25f8 +java.util.stream.StreamSpliterators +java.util.stream.StreamSpliterators$WrappingSpliterator$$Lambda$261/0x00007f4204076a38 +org.junit.platform.engine.discovery.MethodSelector +org.junit.platform.engine.discovery.MethodSelector$$Lambda$262/0x00007f42040a2a88 +org.junit.platform.commons.util.ClassUtils +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$263/0x00007f42040a2ed0 +org.junit.platform.engine.discovery.IterationSelector +org.junit.platform.engine.discovery.DirectorySelector +org.junit.platform.engine.discovery.FileSelector +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$264/0x00007f42040a37e0 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$265/0x00007f42040a3a08 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$266/0x00007f42040a3c38 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$267/0x00007f42040a6000 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$268/0x00007f42040a6250 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$269/0x00007f42040a6490 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$270/0x00007f42040a66d8 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$271/0x00007f42040a6900 +org.junit.platform.commons.util.ClassUtils$$Lambda$272/0x00007f42040a6b48 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$273/0x00007f42040a6d88 +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall$VoidMethodInterceptorCall +org.junit.jupiter.api.extension.InvocationInterceptor +java.lang.invoke.LambdaForm$DMH/0x00007f42040a4400 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$274/0x00007f42040a73b0 +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall$$Lambda$275/0x00007f42040a77d0 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$276/0x00007f42040a79f8 +org.junit.jupiter.api.DisplayNameGenerator$$Lambda$277/0x00007f42040a7c30 +org.junit.platform.engine.support.descriptor.MethodSource +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$MethodInfo +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$MethodInfo$$Lambda$278/0x00007f42040a5438 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$MethodInfo$$Lambda$279/0x00007f42040a5660 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$MethodInfo$$Lambda$280/0x00007f42040a5888 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$281/0x00007f42040a5ac0 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$282/0x00007f42040a5d00 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$283/0x00007f42040a4800 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$284/0x00007f42040a4a40 +jdk.internal.reflect.GeneratedConstructorAccessor5 +java.util.HashMap$KeySpliterator +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$285/0x00007f42040a4c68 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$286/0x00007f42040ac000 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$287/0x00007f42040ac238 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$288/0x00007f42040ac478 +org.junit.jupiter.api.ClassDescriptor +org.junit.jupiter.engine.discovery.AbstractAnnotatedDescriptorWrapper +org.junit.jupiter.engine.discovery.DefaultClassDescriptor +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$289/0x00007f42040acd28 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$$Lambda$290/0x00007f42040acf68 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$$Lambda$291/0x00007f42040ad1c0 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$$Lambda$292/0x00007f42040ad408 +org.junit.jupiter.api.Order +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$293/0x00007f42040ad840 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$294/0x00007f42040ada78 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$295/0x00007f42040adcb8 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$296/0x00007f42040adef0 +org.junit.platform.engine.TestDescriptor$$Lambda$297/0x00007f42040ae130 +org.junit.jupiter.api.TestClassOrder +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$298/0x00007f42040ae568 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$299/0x00007f42040ae7a8 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$300/0x00007f42040ae9e8 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$301/0x00007f42040aec30 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$302/0x00007f42040aee58 +org.junit.jupiter.api.TestMethodOrder +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$303/0x00007f42040af298 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$304/0x00007f42040af4d8 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$305/0x00007f42040af718 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$306/0x00007f42040af958 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$307/0x00007f42040afb80 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$308/0x00007f42040aa000 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$309/0x00007f42040afdc8 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$310/0x00007f42040aa248 +org.junit.jupiter.api.MethodDescriptor +org.junit.jupiter.engine.discovery.DefaultMethodDescriptor +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$311/0x00007f42040aa8d8 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$312/0x00007f42040aab18 +org.junit.platform.engine.support.hierarchical.Node$ExecutionMode +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$313/0x00007f42040aafa0 +org.junit.jupiter.engine.descriptor.Validatable$$Lambda$314/0x00007f42040ab1d8 +org.junit.jupiter.api.extension.ClassTemplateInvocationLifecycleMethod +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$315/0x00007f42040ab610 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$316/0x00007f42040ab848 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$317/0x00007f42040aba70 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$318/0x00007f42040abc98 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$319/0x00007f42040a9000 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$320/0x00007f42040a9250 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$321/0x00007f42040a9488 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$322/0x00007f42040a96b0 +org.junit.platform.launcher.core.EngineDiscoveryResultValidator$$Lambda$323/0x00007f42040a98d8 +org.junit.platform.engine.TestDescriptor$Type +org.junit.platform.launcher.core.EngineDiscoveryResultValidator$$Lambda$324/0x00007f42040a8800 +org.junit.platform.launcher.EngineDiscoveryResult +org.junit.platform.launcher.EngineDiscoveryResult$Status +org.junit.platform.launcher.listeners.discovery.CompositeLauncherDiscoveryListener$$Lambda$325/0x00007f42040b0000 +java.util.Collections$UnmodifiableList$1 +java.util.ArrayList$ListItr +org.junit.platform.commons.util.ExceptionUtils +java.io.StringWriter +org.junit.platform.launcher.listeners.discovery.AbortOnFailureLauncherDiscoveryListener$$Lambda$326/0x00007f42040b0238 +org.junit.platform.launcher.core.DiscoveryIssueNotifier +org.junit.platform.engine.DiscoveryIssue$Severity +org.junit.platform.launcher.core.LauncherDiscoveryResult$EngineResultInfo +org.junit.platform.launcher.core.EngineFilterer$$Lambda$327/0x00007f42040b0b18 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$328/0x00007f42040b0d58 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$329/0x00007f42040b0fa8 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$330/0x00007f42040b11e8 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$331/0x00007f42040b1428 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$332/0x00007f42040b1680 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$333/0x00007f42040b18a0 +java.lang.invoke.LambdaForm$DMH/0x00007f42040b4000 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$334/0x00007f42040b1af0 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$335/0x00007f42040b1d18 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$336/0x00007f42040b1f50 +java.lang.invoke.LambdaForm$DMH/0x00007f42040b4400 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$337/0x00007f42040b2180 +org.junit.platform.engine.TestDescriptor$$Lambda$338/0x00007f42040b23a0 +org.junit.platform.launcher.core.LauncherDiscoveryResult +org.junit.platform.launcher.listeners.discovery.CompositeLauncherDiscoveryListener$$Lambda$339/0x00007f42040b2848 +org.junit.platform.launcher.core.LauncherPhase$$Lambda$340/0x00007f42040b2a80 +org.junit.platform.engine.ConfigurationParameters$$Lambda$341/0x00007f42040b2cc0 +org.junit.platform.launcher.core.LauncherDiscoveryResult$$Lambda$342/0x00007f42040b2f08 +org.junit.platform.launcher.core.LauncherDiscoveryResult$$Lambda$343/0x00007f42040b3158 +org.junit.platform.launcher.TestPlan$$Lambda$344/0x00007f42040b3398 +org.junit.platform.launcher.TestPlan$$Lambda$345/0x00007f42040b35c0 +org.junit.platform.launcher.TestIdentifier +org.junit.platform.launcher.TestIdentifier$SerializedForm +java.io.ObjectStreamClass +java.io.ObjectStreamClass$Caches +java.io.ClassCache +java.io.ObjectStreamClass$Caches$1 +java.io.ClassCache$1 +java.io.ObjectStreamClass$Caches$2 +java.lang.ClassValue$ClassValueMap +java.io.Externalizable +java.io.ObjectStreamClass$2 +jdk.internal.reflect.UnsafeFieldAccessorFactory +jdk.internal.reflect.UnsafeQualifiedStaticFieldAccessorImpl +jdk.internal.reflect.UnsafeQualifiedStaticLongFieldAccessorImpl +java.util.ComparableTimSort +jdk.internal.reflect.SerializationConstructorAccessorImpl +jdk.internal.reflect.GeneratedSerializationConstructorAccessor1 +java.io.ObjectOutput +java.io.ObjectStreamConstants +java.io.ObjectOutputStream +java.io.ObjectInput +java.io.ObjectInputStream +java.lang.Class$$Lambda$346/0x00007f420407a758 +java.util.stream.Collectors$$Lambda$31/0x800000042 +java.util.stream.Collectors$$Lambda$23/0x80000003a +java.util.stream.Collectors$$Lambda$26/0x80000003d +java.util.stream.Collectors$$Lambda$28/0x80000003f +java.lang.CloneNotSupportedException +java.io.ClassCache$CacheRef +java.io.ObjectStreamClass$FieldReflectorKey +java.io.ObjectStreamClass$FieldReflector +org.junit.platform.launcher.TestIdentifier$$Lambda$351/0x00007f42040b3c20 +org.junit.platform.launcher.TestPlan$$Lambda$352/0x00007f42040b6000 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$353/0x00007f42040b6240 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$354/0x00007f42040b6478 +software.amazon.lambda.powertools.kafka.PowertoolsSerializerTest +org.junit.jupiter.api.extension.ParameterResolver +org.mockito.junit.jupiter.MockitoExtension +software.amazon.lambda.powertools.kafka.PowertoolsSerializerTest$1 +org.apache.avro.generic.GenericContainer +org.apache.avro.generic.IndexedRecord +org.apache.avro.specific.SpecificRecord +software.amazon.lambda.powertools.kafka.PowertoolsSerializerTest$InputType +com.fasterxml.jackson.core.JacksonException +com.fasterxml.jackson.core.JsonProcessingException +software.amazon.lambda.powertools.kafka.serializers.PowertoolsDeserializer +software.amazon.lambda.powertools.kafka.serializers.LambdaDefaultDeserializer +org.junit.jupiter.params.ParameterizedTest +org.junit.jupiter.params.ArgumentCountValidationMode +jdk.proxy2.$Proxy12 +org.junit.jupiter.params.provider.MethodSource +org.junit.jupiter.params.provider.ArgumentsSource +jdk.proxy2.$Proxy13 +jdk.proxy2.$Proxy14 +org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider +org.junit.jupiter.params.ParameterizedInvocationContextProvider +org.junit.jupiter.params.ParameterizedTestExtension +org.junit.jupiter.params.provider.MethodSources +org.junit.jupiter.params.provider.ArgumentsProvider +org.junit.jupiter.params.support.AnnotationConsumer +org.junit.jupiter.params.provider.AnnotationBasedArgumentsProvider +org.junit.jupiter.params.provider.MethodArgumentsProvider +jdk.proxy2.$Proxy15 +org.junit.jupiter.params.provider.ArgumentsSources +jdk.internal.reflect.GeneratedConstructorAccessor6 +jdk.internal.reflect.GeneratedConstructorAccessor7 +org.junit.platform.commons.util.ClassUtils$$Lambda$355/0x00007f42040b9840 +java.util.function.BiPredicate +org.junit.jupiter.engine.descriptor.DynamicDescendantFilter +org.junit.jupiter.engine.descriptor.DynamicDescendantFilter$WithoutIndexFiltering +org.junit.jupiter.engine.descriptor.DynamicDescendantFilter$Mode +software.amazon.lambda.powertools.kafka.DeserializationTest +software.amazon.lambda.powertools.kafka.DeserializationTypeTest +software.amazon.lambda.powertools.kafka.serializers.KafkaJsonDeserializerTest +jdk.proxy2.$Proxy16 +com.google.protobuf.MessageLiteOrBuilder +com.google.protobuf.MessageOrBuilder +software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProductOrBuilder +com.google.protobuf.MessageLite +com.google.protobuf.Message +com.google.protobuf.AbstractMessageLite +com.google.protobuf.AbstractMessage +com.google.protobuf.GeneratedMessage +software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProduct +com.google.protobuf.CheckReturnValue +com.google.protobuf.$Proxy17 +com.google.protobuf.MessageLite$Builder +com.google.protobuf.Internal$ProtobufList +com.google.protobuf.GeneratedMessage$ExtensionDescriptorRetriever +com.google.protobuf.InvalidProtocolBufferException +com.google.protobuf.Reader +com.google.protobuf.Internal$IntList +com.google.protobuf.Internal$LongList +com.google.protobuf.Internal$FloatList +com.google.protobuf.Internal$DoubleList +com.google.protobuf.Internal$BooleanList +com.google.protobuf.MapFieldReflectionAccessor +com.google.protobuf.MutabilityOracle +com.google.protobuf.MapField +com.google.protobuf.Parser +com.google.protobuf.Message$Builder +com.google.protobuf.Descriptors$GenericDescriptor +com.google.protobuf.Descriptors$Descriptor +com.google.protobuf.ByteOutput +com.google.protobuf.CodedOutputStream +com.google.protobuf.ExtensionRegistryLite +com.google.protobuf.CodedInputStream +com.google.protobuf.ByteString +com.google.protobuf.AbstractMessageLite$Builder +com.google.protobuf.AbstractMessage$Builder +com.google.protobuf.GeneratedMessage$Builder +software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProduct$Builder +com.google.protobuf.FieldSet$FieldDescriptorLite +com.google.protobuf.Descriptors$FieldDescriptor +com.google.protobuf.ExtensionLite +com.google.protobuf.Extension +com.google.protobuf.GeneratedMessage$GeneratedExtension +com.google.protobuf.UnknownFieldSet +com.google.protobuf.Descriptors$OneofDescriptor +com.google.protobuf.AbstractMessage$BuilderParent +com.google.protobuf.GeneratedMessage$FieldAccessorTable +java.lang.reflect.Executable$$Lambda$356/0x00007f420407b2b8 +java.lang.reflect.Executable$$Lambda$357/0x00007f420407b4f8 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$358/0x00007f42040c4030 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$359/0x00007f42040c4270 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$360/0x00007f42040c44b0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$361/0x00007f42040c4708 +com.google.protobuf.GeneratedMessage$UnusedPrivateParameter +java.io.ObjectStreamException +com.google.protobuf.UnknownFieldSet$Builder +com.google.protobuf.MapEntry +java.lang.Deprecated +jdk.proxy1.$Proxy18 +com.google.protobuf.UninitializedMessageException +com.google.protobuf.Schema +org.junit.platform.commons.util.ReflectionUtils$$Lambda$362/0x00007f42040c5760 +com.google.protobuf.GeneratedMessage$CachedDescriptorRetriever +com.google.protobuf.GeneratedMessage$ExtendableMessageOrBuilder +com.google.protobuf.GeneratedMessage$ExtendableBuilder +com.google.protobuf.GeneratedMessage$ExtendableMessage +com.google.protobuf.AbstractMessageLite$InternalOneOfEnum +java.lang.invoke.LambdaForm$DMH/0x00007f42040c8000 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$363/0x00007f42040c6f68 +software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProductOuterClass +com.google.protobuf.ExtensionRegistry +com.google.protobuf.Descriptors$FileDescriptor +org.apache.avro.generic.GenericRecord +org.apache.avro.specific.SpecificRecordBase +software.amazon.lambda.powertools.kafka.serializers.test.avro.TestProduct +org.apache.avro.specific.AvroGenerated +jdk.proxy2.$Proxy19 +org.apache.avro.AvroRuntimeException +org.apache.avro.io.Encoder +org.apache.avro.io.BinaryEncoder +org.apache.avro.io.Decoder +org.apache.avro.io.BinaryDecoder +org.apache.avro.generic.GenericData +org.apache.avro.specific.SpecificData +org.apache.avro.message.SchemaStore +org.apache.avro.message.MessageDecoder +org.apache.avro.message.MessageDecoder$BaseDecoder +org.apache.avro.message.BinaryMessageDecoder +org.apache.avro.JsonProperties +org.apache.avro.Schema +org.apache.avro.message.MessageEncoder +org.apache.avro.message.BinaryMessageEncoder +org.apache.avro.data.RecordBuilder +org.apache.avro.data.RecordBuilderBase +org.apache.avro.specific.SpecificRecordBuilderBase +software.amazon.lambda.powertools.kafka.serializers.test.avro.TestProduct$Builder +org.apache.avro.io.parsing.Parser$ActionHandler +org.apache.avro.io.parsing.SkipParser$SkipHandler +org.apache.avro.io.ParsingDecoder +org.apache.avro.io.ValidatingDecoder +org.apache.avro.io.ResolvingDecoder +org.apache.avro.Conversion +software.amazon.lambda.powertools.kafka.serializers.KafkaProtobufDeserializerTest +software.amazon.lambda.powertools.kafka.serializers.KafkaAvroDeserializerTest +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$InputType +jdk.internal.reflect.GeneratedConstructorAccessor8 +jdk.internal.reflect.GeneratedConstructorAccessor9 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializer +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$TestDeserializer +software.amazon.lambda.powertools.kafka.testutils.TestProductPojo +software.amazon.lambda.powertools.kafka.testutils.TestUtils +org.apache.avro.io.DatumWriter +org.apache.maven.surefire.api.util.TestsToRun +org.apache.maven.surefire.api.util.DefaultRunOrderCalculator +java.util.random.RandomGenerator +java.util.Random +org.apache.maven.surefire.api.util.CloseableIterator +org.apache.maven.surefire.api.util.TestsToRun$ClassesIterator +java.util.NoSuchElementException +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$364/0x00007f42040c9738 +org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder$$Lambda$365/0x00007f42040c9970 +org.junit.platform.launcher.core.InterceptingLauncher$$Lambda$366/0x00007f42040c9ba8 +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$367/0x00007f42040c8c00 +org.junit.platform.launcher.core.CompositeTestExecutionListener$EagerTestExecutionListener +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$368/0x00007f42040d0000 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$369/0x00007f42040d0258 +org.junit.platform.engine.reporting.ReportEntry +java.lang.invoke.LambdaForm$DMH/0x00007f42040d4000 +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$370/0x00007f42040d06b0 +org.junit.platform.launcher.core.StreamInterceptingTestExecutionListener +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$371/0x00007f42040d0bc0 +org.junit.platform.engine.EngineExecutionListener$1 +org.junit.platform.launcher.core.IterationOrder +org.junit.platform.launcher.core.IterationOrder$1 +org.junit.platform.launcher.core.IterationOrder$2 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$372/0x00007f42040d1958 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$373/0x00007f42040d1b90 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$374/0x00007f42040d1db8 +org.junit.platform.launcher.core.CompositeEngineExecutionListener +org.junit.platform.launcher.core.ListenerRegistry$$Lambda$375/0x00007f42040d2270 +org.junit.platform.launcher.core.ExecutionListenerAdapter +org.junit.platform.launcher.core.DelegatingEngineExecutionListener +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$376/0x00007f42040d2c30 +org.junit.platform.launcher.core.OutcomeDelayingEngineExecutionListener +org.junit.platform.engine.ExecutionRequest +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$377/0x00007f42040d3330 +org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService +org.junit.jupiter.engine.execution.JupiterEngineExecutionContext +org.junit.jupiter.engine.descriptor.LauncherStoreFacade +org.junit.jupiter.api.extension.ExtensionContext$Store +org.junit.jupiter.engine.descriptor.LauncherStoreFacade$$Lambda$378/0x00007f42040d6000 +org.junit.jupiter.engine.execution.JupiterEngineExecutionContext$State +org.junit.platform.engine.support.hierarchical.ThrowableCollector$Factory +org.junit.platform.engine.support.hierarchical.ThrowableCollector +org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory +org.junit.jupiter.engine.support.OpenTest4JAndJUnit4AwareThrowableCollector +org.junit.jupiter.engine.JupiterTestEngine$$Lambda$379/0x00007f42040d6cb8 +org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor +org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService$TestTask +org.junit.platform.engine.support.hierarchical.NodeTreeWalker +org.junit.platform.engine.support.hierarchical.LockManager +org.junit.platform.engine.support.hierarchical.ResourceLock +java.util.concurrent.locks.ReadWriteLock +org.junit.platform.engine.support.hierarchical.SingleLock +org.junit.platform.engine.support.hierarchical.ExclusiveResource +org.junit.platform.engine.support.hierarchical.ExclusiveResource$LockMode +org.junit.platform.engine.support.hierarchical.ExclusiveResource$$Lambda$380/0x00007f42040d5248 +org.junit.platform.engine.support.hierarchical.ExclusiveResource$$Lambda$381/0x00007f42040d5488 +java.util.Comparator$$Lambda$382/0x00007f420407c258 +java.lang.invoke.LambdaForm$DMH/0x00007f42040d4400 +java.util.Comparator$$Lambda$383/0x00007f420407c4f8 +org.junit.platform.engine.support.hierarchical.ExclusiveResource$$Lambda$384/0x00007f42040d56c8 +org.junit.platform.engine.support.hierarchical.LockManager$$Lambda$385/0x00007f42040d5908 +java.util.concurrent.locks.ReentrantReadWriteLock +java.util.concurrent.locks.ReentrantReadWriteLock$Sync +java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync +java.util.concurrent.locks.ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter +java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock +java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock +org.junit.platform.commons.util.CollectionUtils$$Lambda$386/0x00007f42040d5b48 +org.junit.platform.engine.support.hierarchical.NodeUtils +org.junit.platform.engine.support.hierarchical.NodeUtils$1 +org.junit.platform.engine.support.hierarchical.NodeExecutionAdvisor +org.junit.platform.engine.support.hierarchical.NodeTreeWalker$$Lambda$387/0x00007f42040d4d00 +org.junit.platform.engine.support.hierarchical.NopLock +org.junit.jupiter.api.parallel.ResourceLocksProvider +org.junit.jupiter.engine.descriptor.ClassTestDescriptor$$Lambda$388/0x00007f42040d8490 +org.junit.platform.engine.support.hierarchical.NodeTreeWalker$$Lambda$389/0x00007f42040d86d8 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$390/0x00007f42040d8910 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$391/0x00007f42040d8b38 +org.junit.jupiter.engine.descriptor.ResourceLockAware$1 +java.util.ArrayDeque$DeqSpliterator +org.junit.jupiter.engine.descriptor.ResourceLockAware$$Lambda$392/0x00007f42040d8fc8 +org.junit.jupiter.engine.descriptor.ResourceLockAware$$Lambda$393/0x00007f42040d9208 +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$DefaultExclusiveResourceCollector$$Lambda$394/0x00007f42040d9450 +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$DefaultExclusiveResourceCollector$$Lambda$395/0x00007f42040d96a0 +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$DefaultExclusiveResourceCollector$$Lambda$396/0x00007f42040d98f8 +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$DefaultExclusiveResourceCollector$$Lambda$397/0x00007f42040d9b38 +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$DefaultExclusiveResourceCollector$$Lambda$398/0x00007f42040d9d78 +org.junit.jupiter.engine.descriptor.ResourceLockAware$$Lambda$399/0x00007f42040d9fb8 +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$2 +org.junit.platform.engine.support.hierarchical.NodeTreeWalker$$Lambda$400/0x00007f42040da400 +org.junit.platform.engine.support.hierarchical.NodeTreeWalker$$Lambda$401/0x00007f42040da848 +org.junit.platform.engine.support.hierarchical.NodeTreeWalker$$Lambda$402/0x00007f42040daa80 +org.junit.platform.engine.support.hierarchical.NodeTestTaskContext +org.junit.platform.engine.support.hierarchical.NodeTestTask +org.junit.platform.engine.support.hierarchical.Node$DynamicTestExecutor +java.lang.invoke.LambdaForm$DMH/0x00007f42040dc000 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$403/0x00007f42040db348 +org.opentest4j.IncompleteExecutionException +org.opentest4j.TestAbortedException +org.junit.jupiter.engine.support.OpenTest4JAndJUnit4AwareThrowableCollector$$Lambda$404/0x00007f42040dba28 +org.junit.platform.commons.util.UnrecoverableExceptions +org.junit.jupiter.engine.support.OpenTest4JAndJUnit4AwareThrowableCollector$$Lambda$405/0x00007f42040de000 +org.junit.platform.engine.support.hierarchical.ThrowableCollector$Executable +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$406/0x00007f42040de420 +org.junit.jupiter.engine.extension.MutableExtensionRegistry +org.junit.jupiter.engine.extension.MutableExtensionRegistry$Entry +org.junit.jupiter.api.extension.ExecutionCondition +org.junit.jupiter.engine.extension.DisabledCondition +org.junit.jupiter.api.extension.TestInstancePreDestroyCallback +org.junit.jupiter.engine.extension.AutoCloseExtension +org.junit.jupiter.engine.extension.TimeoutExtension +org.junit.jupiter.api.Timeout +org.junit.jupiter.api.extension.ExtensionContext$Namespace +org.junit.jupiter.engine.extension.RepeatedTestExtension +org.junit.jupiter.api.extension.TestTemplateInvocationContext +org.junit.jupiter.engine.extension.TestInfoParameterResolver +org.junit.jupiter.api.TestInfo +org.junit.jupiter.engine.extension.TestReporterParameterResolver +org.junit.jupiter.api.TestReporter +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$407/0x00007f42040ddac0 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$408/0x00007f42040ddcf8 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$409/0x00007f42040dc800 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$Entry$$Lambda$410/0x00007f42040dca28 +org.junit.jupiter.engine.extension.TempDirectory +org.junit.jupiter.api.extension.AnnotatedElementContext +org.junit.jupiter.engine.extension.TempDirectory$Scope +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$411/0x00007f42040e0248 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$412/0x00007f42040e0490 +org.junit.jupiter.engine.extension.ExtensionContextInternal +org.junit.jupiter.engine.descriptor.AbstractExtensionContext +org.junit.jupiter.engine.descriptor.JupiterEngineExtensionContext +org.junit.jupiter.api.extension.ExecutableInvoker +org.junit.jupiter.engine.execution.DefaultExecutableInvoker +org.junit.jupiter.engine.descriptor.AbstractExtensionContext$$Lambda$413/0x00007f42040e14f8 +org.junit.jupiter.engine.descriptor.AbstractExtensionContext$$Lambda$414/0x00007f42040e1738 +org.junit.jupiter.engine.descriptor.AbstractExtensionContext$$Lambda$415/0x00007f42040e1958 +org.junit.jupiter.engine.execution.NamespaceAwareStore +org.junit.jupiter.api.extension.ExtensionContextException +org.junit.platform.engine.support.store.Namespace +java.lang.invoke.LambdaForm$DMH/0x00007f42040e4000 +org.junit.jupiter.engine.descriptor.AbstractExtensionContext$$Lambda$416/0x00007f42040e22c0 +org.junit.jupiter.engine.execution.JupiterEngineExecutionContext$Builder +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$417/0x00007f42040e2720 +org.junit.platform.engine.support.hierarchical.Node$SkipResult +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$418/0x00007f42040e2b68 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$419/0x00007f42040e2da0 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$420/0x00007f42040e2fc8 +org.junit.platform.launcher.TestPlan$$Lambda$421/0x00007f42040e3200 +org.junit.platform.launcher.TestPlan$$Lambda$422/0x00007f42040e3420 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$423/0x00007f42040e3648 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$424/0x00007f42040e3880 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$425/0x00007f42040e3aa8 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$426/0x00007f42040e3ce0 +org.junit.platform.engine.UniqueIdFormat$$Lambda$427/0x00007f42040e6000 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$428/0x00007f42040e6248 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$429/0x00007f42040e64a0 +org.junit.platform.engine.support.hierarchical.Node$Invocation +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$430/0x00007f42040e68c8 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$431/0x00007f42040e6af0 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$432/0x00007f42040e6d18 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$433/0x00007f42040e6f60 +org.junit.platform.engine.support.hierarchical.NodeTestTask$DefaultDynamicTestExecutor +java.util.concurrent.CancellationException +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$434/0x00007f42040e73d0 +org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService$$Lambda$435/0x00007f42040e7608 +org.junit.jupiter.engine.descriptor.ExtensionUtils +java.util.function.ToIntFunction +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$436/0x00007f42040e7a40 +java.util.Comparator$$Lambda$437/0x00007f420407df18 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$438/0x00007f42040e7c60 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$439/0x00007f42040e5000 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$440/0x00007f42040e5240 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$LateInitEntry +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$441/0x00007f42040e56c8 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$442/0x00007f42040e5900 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$443/0x00007f42040e5b50 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$444/0x00007f42040e4800 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$445/0x00007f42040e4a50 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$446/0x00007f42040e4c70 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$447/0x00007f42040e4400 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$448/0x00007f42040e8000 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$449/0x00007f42040e8258 +java.util.stream.SortedOps +java.util.stream.SortedOps$OfRef +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$450/0x00007f42040e8478 +java.util.stream.SortedOps$AbstractRefSortingSink +java.util.stream.SortedOps$RefSortingSink +java.util.stream.SortedOps$RefSortingSink$$Lambda$451/0x00007f420407ee38 +org.junit.jupiter.api.extension.TestInstanceFactory +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$452/0x00007f42040e86b0 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$453/0x00007f42040e88f0 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$454/0x00007f42040e8b48 +org.junit.jupiter.engine.extension.ExtensionRegistry$$Lambda$455/0x00007f42040e8d90 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$456/0x00007f42040e8fb0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$457/0x00007f42040e9200 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$458/0x00007f42040e9420 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$459/0x00007f42040e9648 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$460/0x00007f42040e9890 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$461/0x00007f42040e9ad0 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$462/0x00007f42040e9d08 +org.junit.jupiter.engine.execution.BeforeEachMethodAdapter +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$463/0x00007f42040ea140 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$464/0x00007f42040ea388 +org.junit.jupiter.engine.execution.AfterEachMethodAdapter +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$465/0x00007f42040ea7c0 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$466/0x00007f42040eaa08 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$467/0x00007f42040eac40 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$468/0x00007f42040eae90 +org.junit.jupiter.engine.descriptor.ClassExtensionContext +org.junit.jupiter.engine.execution.TestInstancesProvider +org.junit.jupiter.api.extension.TestInstances +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$469/0x00007f42040eb9a8 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$470/0x00007f42040ebbe0 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$471/0x00007f42040ebe28 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$FilterType +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$472/0x00007f42040ec4a8 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$473/0x00007f42040ec6f8 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$474/0x00007f42040ec938 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$475/0x00007f42040ecb80 +org.junit.jupiter.engine.execution.ConditionEvaluator$$Lambda$476/0x00007f42040ecdd0 +org.junit.jupiter.engine.execution.ConditionEvaluator$$Lambda$477/0x00007f42040ed018 +org.junit.jupiter.api.Disabled +org.junit.jupiter.engine.extension.DisabledCondition$$Lambda$478/0x00007f42040ed468 +org.junit.jupiter.engine.execution.ConditionEvaluator$$Lambda$479/0x00007f42040ed6b0 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$480/0x00007f42040ed8d8 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$481/0x00007f42040edb30 +org.junit.platform.launcher.TestIdentifier$$Lambda$482/0x00007f42040edd88 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$483/0x00007f42040edfc8 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$484/0x00007f42040ee210 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$485/0x00007f42040ee458 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$486/0x00007f42040ee678 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$487/0x00007f42040ee8c8 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$488/0x00007f42040eeb08 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$489/0x00007f42040eed60 +org.apache.maven.surefire.api.report.SimpleReportEntry +org.apache.maven.surefire.api.util.internal.ClassMethod +org.apache.maven.surefire.report.ClassMethodIndexer$$Lambda$490/0x00007f42040ef4b8 +org.apache.maven.surefire.api.util.internal.ImmutableMap +org.apache.maven.surefire.booter.spi.EventChannelEncoder$StackTrace +java.lang.StrictMath +java.nio.StringCharBuffer +org.junit.jupiter.engine.descriptor.CallbackSupport$CallbackInvoker +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$491/0x00007f42040f0200 +org.junit.jupiter.engine.descriptor.CallbackSupport +org.junit.jupiter.engine.descriptor.CallbackSupport$$Lambda$492/0x00007f42040f0628 +org.junit.jupiter.engine.extension.TimeoutDuration +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$493/0x00007f42040f0a78 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$494/0x00007f42040f0cb8 +org.junit.jupiter.api.Timeout$ThreadMode +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$495/0x00007f42040f1138 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$496/0x00007f42040f1378 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$497/0x00007f42040f15b0 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$498/0x00007f42040f1808 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$499/0x00007f42040f1a40 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$500/0x00007f42040f1c90 +org.junit.jupiter.engine.execution.NamespaceAwareStore$$Lambda$501/0x00007f42040f1ed8 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$CompositeKey +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$StoredValue +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$502/0x00007f42040f2520 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$MemoizingSupplier +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$503/0x00007f42040f2998 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$504/0x00007f42040f2bc0 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$MemoizingSupplier$Failure +org.junit.jupiter.api.io.TempDir +org.junit.platform.commons.util.AnnotationUtils$$Lambda$505/0x00007f42040f3410 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$506/0x00007f42040f3668 +org.junit.jupiter.engine.descriptor.ClassExtensionContext$$Lambda$507/0x00007f42040f38a0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$508/0x00007f42040f3ae0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$509/0x00007f42040f3d20 +java.util.stream.Nodes$ArrayNode +java.util.stream.Nodes$FixedNodeBuilder +org.junit.jupiter.engine.descriptor.MethodExtensionContext +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$510/0x00007f42040f4420 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$511/0x00007f42040f4648 +org.junit.jupiter.engine.execution.ExtensionContextSupplier +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$512/0x00007f42040f4a70 +org.junit.jupiter.engine.execution.ExtensionContextSupplier$ScopeBasedExtensionContextSupplier +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$513/0x00007f42040f50e0 +org.junit.jupiter.engine.descriptor.DefaultTestInstanceFactoryContext +org.junit.jupiter.api.extension.TestInstancePreConstructCallback +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$514/0x00007f42040f5760 +java.lang.invoke.LambdaForm$DMH/0x00007f42040f8000 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$515/0x00007f42040f5998 +org.junit.jupiter.engine.execution.ParameterResolutionUtils +org.junit.jupiter.api.extension.ParameterContext +org.junit.jupiter.api.extension.ParameterResolutionException +org.junit.jupiter.engine.execution.ConstructorInvocation +org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptorCall +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$$Lambda$516/0x00007f42040f66b8 +org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation +org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation +org.junit.jupiter.engine.execution.DefaultTestInstances +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$517/0x00007f42040f6fc8 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$518/0x00007f42040f7210 +org.junit.jupiter.api.extension.TestInstancePostProcessor +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$519/0x00007f42040f7638 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$520/0x00007f42040f7870 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$521/0x00007f42040f7ab8 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$522/0x00007f42040f7d08 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$523/0x00007f42040fc000 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$524/0x00007f42040fc248 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$525/0x00007f42040fc488 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$526/0x00007f42040fc6d8 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$527/0x00007f42040fc918 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$528/0x00007f42040fcb70 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$529/0x00007f42040fcdc0 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$530/0x00007f42040fd000 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$531/0x00007f42040fd250 +org.junit.jupiter.api.extension.ExtensionContext$Store$CloseableResource +org.junit.jupiter.engine.extension.TempDirectory$FailureTracker +org.junit.jupiter.engine.execution.NamespaceAwareStore$$Lambda$532/0x00007f42040fd8b8 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$533/0x00007f42040fdae0 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$534/0x00007f42040fdd08 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$535/0x00007f42040fdf40 +sun.reflect.generics.repository.AbstractRepository +sun.reflect.generics.repository.GenericDeclRepository +sun.reflect.generics.repository.ClassRepository +java.lang.reflect.TypeVariable +sun.reflect.generics.tree.FormalTypeParameter +sun.reflect.generics.tree.Signature +sun.reflect.generics.tree.ClassSignature +org.junitpioneer.jupiter.ClearEnvironmentVariable +org.junitpioneer.jupiter.RestoreEnvironmentVariables +java.lang.reflect.ParameterizedType +sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl +sun.reflect.generics.reflectiveObjects.LazyReflectiveObjectGenerator +sun.reflect.generics.reflectiveObjects.TypeVariableImpl +org.junitpioneer.internal.PioneerAnnotationUtils +java.lang.invoke.LambdaForm$DMH/0x00007f42040f8400 +org.junitpioneer.internal.PioneerAnnotationUtils$$Lambda$536/0x00007f42040fe798 +java.lang.invoke.LambdaForm$DMH/0x00007f42040f8800 +java.util.stream.Collectors$$Lambda$537/0x00007f42040f97c0 +java.util.stream.Collectors$$Lambda$538/0x00007f42040f99e0 +java.util.stream.Collectors$$Lambda$539/0x00007f42040f9c10 +java.util.stream.Collectors$$Lambda$540/0x00007f42040f8c00 +java.util.ImmutableCollections$Access +jdk.internal.access.JavaUtilCollectionAccess +java.util.ImmutableCollections$Access$1 +sun.reflect.annotation.AnnotationSupport +org.junitpioneer.internal.PioneerAnnotationUtils$$Lambda$541/0x00007f42040febd8 +org.junitpioneer.internal.PioneerAnnotationUtils$$Lambda$542/0x00007f42040fee20 +java.util.AbstractList$RandomAccessSpliterator +java.lang.invoke.MethodHandleImpl$BindCaller +java.lang.invoke.MethodHandleImpl$BindCaller$1 +java.lang.invoke.LambdaForm$MH/0x00007f4204140000 +java.lang.invoke.LambdaForm$MH/0x00007f4204140400 +java.lang.invoke.MethodHandleImpl$CasesHolder +java.lang.invoke.MethodHandleImpl$LoopClauses +java.lang.invoke.MethodHandleImpl$ArrayAccess +java.lang.invoke.MethodHandleImpl$2 +java.lang.invoke.MethodHandleImpl$ArrayAccessor +java.lang.invoke.MethodHandleImpl$ArrayAccessor$1 +java.lang.invoke.LambdaForm$DMH/0x00007f4204140800 +java.lang.invoke.LambdaForm$DMH/0x00007f4204140c00 +java.lang.invoke.LambdaForm$DMH/0x00007f4204141000 +java.lang.invoke.LambdaForm$MH/0x00007f4204141400 +java.lang.invoke.LambdaForm$MH/0x00007f4204141800 +org.junitpioneer.internal.PioneerAnnotationUtils$$InjectedInvoker/0x00007f4204141c00 +java.util.Collections$CopiesList +java.lang.invoke.LambdaForm$MH/0x00007f4204142000 +java.lang.invoke.BoundMethodHandle$Species_LLL +java.lang.invoke.LambdaForm$MH/0x00007f4204142400 +java.lang.invoke.LambdaForm$MH/0x00007f4204142800 +java.lang.invoke.LambdaForm$MH/0x00007f4204142c00 +java.lang.invoke.BoundMethodHandle$Species_LLLL +java.lang.invoke.LambdaForm$MH/0x00007f4204143000 +java.lang.invoke.MethodHandleImpl$WrappedMember +org.junitpioneer.internal.PioneerAnnotationUtils$$Lambda$543/0x00007f42040ff060 +org.junitpioneer.internal.PioneerUtils +org.junitpioneer.internal.PioneerUtils$$Lambda$544/0x00007f42040ff4a8 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$545/0x00007f42040ff6e8 +java.lang.invoke.LambdaForm$DMH/0x00007f4204143400 +java.lang.invoke.LambdaForm$DMH/0x00007f4204143800 +java.lang.invoke.LambdaForm$MH/0x00007f4204143c00 +java.lang.invoke.LambdaForm$DMH/0x00007f4204144000 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$546/0x00007f42040ff920 +java.lang.invoke.LambdaForm$DMH/0x00007f4204144400 +java.lang.invoke.LambdaForm$DMH/0x00007f4204144800 +java.lang.invoke.LambdaForm$MH/0x00007f4204144c00 +org.junitpioneer.jupiter.ClearEnvironmentVariable$ClearEnvironmentVariables +org.junitpioneer.jupiter.EnvironmentVariableExtension$$Lambda$547/0x00007f42040ffd58 +org.junitpioneer.internal.PioneerUtils$$Lambda$548/0x00007f4204146000 +org.junitpioneer.internal.PioneerUtils$$Lambda$549/0x00007f4204146220 +org.junitpioneer.internal.PioneerUtils$$Lambda$550/0x00007f4204146450 +org.junitpioneer.jupiter.EnvironmentVariableExtension$$Lambda$551/0x00007f4204146698 +org.junitpioneer.jupiter.EnvironmentVariableExtension$$Lambda$552/0x00007f42041468d8 +java.util.stream.Collectors$$Lambda$553/0x00007f4204100e30 +java.util.stream.Collectors$$Lambda$554/0x00007f4204101050 +java.util.stream.Collectors$$Lambda$555/0x00007f4204101288 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$556/0x00007f4204146b18 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$557/0x00007f4204146d70 +java.time.temporal.TemporalAccessor +java.time.temporal.Temporal +java.time.temporal.TemporalAdjuster +java.time.chrono.ChronoLocalDateTime +java.time.LocalDateTime +java.time.chrono.ChronoLocalDate +java.time.LocalDate +java.time.temporal.TemporalField +java.time.temporal.ChronoField +java.time.temporal.ValueRange +java.time.LocalTime +java.time.InstantSource +java.time.Clock +java.time.Clock$SystemClock +java.time.ZoneId +java.time.ZoneOffset +java.util.TimeZone +sun.util.calendar.ZoneInfo +sun.util.calendar.ZoneInfoFile +sun.util.calendar.ZoneInfoFile$1 +java.io.DataInputStream +sun.util.calendar.ZoneInfoFile$ZoneOffsetTransitionRule +sun.util.calendar.ZoneInfoFile$Checksum +java.time.ZoneRegion +java.time.zone.ZoneRulesProvider +java.time.zone.ZoneRulesProvider$1 +java.time.zone.TzdbZoneRulesProvider +java.time.zone.Ser +java.time.zone.ZoneRules +java.time.zone.ZoneOffsetTransitionRule +java.time.zone.ZoneOffsetTransition +java.time.Instant +org.junit.platform.engine.reporting.ReportEntry$$Lambda$558/0x00007f4204146fb0 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$559/0x00007f42041471e8 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$560/0x00007f4204147420 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$561/0x00007f4204147648 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$562/0x00007f4204147880 +org.apache.maven.surefire.api.report.TestOutputReportEntry +java.lang.invoke.LambdaForm$MH/0x00007f4204145000 +java.lang.invoke.LambdaForm$MH/0x00007f4204145400 +java.lang.invoke.LambdaForm$MH/0x00007f4204145800 +java.lang.invoke.LambdaForm$MH/0x00007f4204145c00 +java.lang.invoke.LambdaForm$MH/0x00007f4204148000 +java.lang.invoke.BoundMethodHandle$Species_LLLLL +java.lang.invoke.LambdaForm$MH/0x00007f4204148400 +java.lang.invoke.BoundMethodHandle$Species_LLLLLL +java.lang.invoke.LambdaForm$MH/0x00007f4204148800 +java.lang.invoke.BoundMethodHandle$Species_LLLLLLL +java.lang.invoke.LambdaForm$MH/0x00007f4204148c00 +java.lang.invoke.MethodHandles$1 +java.lang.invoke.BoundMethodHandle$Species_LJ +java.lang.invoke.LambdaForm$MH/0x00007f4204149000 +java.lang.invoke.BoundMethodHandle$Species_LLLLLLLL +java.lang.invoke.LambdaForm$MH/0x00007f4204149400 +java.lang.invoke.BoundMethodHandle$Species_LLLLLLLLL +java.lang.invoke.LambdaFormEditor$1 +java.util.TreeMap$EntrySet +java.util.TreeMap$EntryIterator +java.lang.invoke.LambdaForm$MH/0x00007f4204149800 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$EntriesBackup +org.junitpioneer.jupiter.AbstractEntryBasedExtension$EntriesBackup$$Lambda$563/0x00007f420414c000 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$564/0x00007f420414c238 +java.lang.invoke.LambdaForm$DMH/0x00007f4204149c00 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$565/0x00007f420414c470 +org.junitpioneer.jupiter.EnvironmentVariableUtils +java.lang.reflect.InaccessibleObjectException +org.junitpioneer.jupiter.EnvironmentVariableUtils$$Lambda$566/0x00007f420414c8b0 +jdk.internal.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl +jdk.internal.reflect.UnsafeQualifiedFieldAccessorImpl +jdk.internal.reflect.UnsafeQualifiedObjectFieldAccessorImpl +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$567/0x00007f420414cae8 +org.junit.jupiter.api.extension.BeforeTestExecutionCallback +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$568/0x00007f420414cf10 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$569/0x00007f420414d130 +org.junit.jupiter.engine.descriptor.MethodExtensionContext$$Lambda$570/0x00007f420414d358 +org.junit.jupiter.engine.execution.ParameterResolutionUtils$$Lambda$571/0x00007f420414d598 +org.junit.jupiter.engine.execution.MethodInvocation +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$$Lambda$572/0x00007f420414da58 +org.junit.jupiter.engine.extension.TimeoutExtension$TimeoutProvider +org.junit.jupiter.engine.extension.TimeoutConfiguration +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$573/0x00007f420414e0d0 +org.junit.jupiter.engine.execution.NamespaceAwareStore$$Lambda$574/0x00007f420414e328 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$575/0x00007f420414e550 +org.junit.jupiter.engine.extension.TimeoutDurationParser +java.time.DateTimeException +java.time.format.DateTimeParseException +java.util.regex.Pattern$$Lambda$576/0x00007f4204104db8 +java.lang.CharacterData00 +java.util.regex.Pattern$$Lambda$577/0x00007f4204105408 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$578/0x00007f420414e9a8 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$579/0x00007f420414ebd0 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$580/0x00007f420414ee18 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$581/0x00007f420414f060 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$582/0x00007f420414f288 +software.amazon.lambda.powertools.kafka.internal.DeserializationUtils +org.slf4j.LoggerFactory +org.slf4j.spi.SLF4JServiceProvider +org.slf4j.event.LoggingEvent +java.lang.InstantiationException +java.util.ServiceConfigurationError +org.slf4j.helpers.SubstituteServiceProvider +org.slf4j.IMarkerFactory +org.slf4j.spi.MDCAdapter +org.slf4j.ILoggerFactory +org.slf4j.helpers.SubstituteLoggerFactory +org.slf4j.Logger +java.util.concurrent.LinkedBlockingQueue +java.util.concurrent.LinkedBlockingQueue$Node +org.slf4j.helpers.BasicMarkerFactory +org.slf4j.Marker +org.slf4j.helpers.BasicMDCAdapter +java.lang.InheritableThreadLocal +org.slf4j.helpers.BasicMDCAdapter$1 +org.slf4j.helpers.ThreadLocalMapOfStacks +org.slf4j.helpers.NOP_FallbackServiceProvider +org.slf4j.helpers.NOPLoggerFactory +org.slf4j.helpers.NOPMDCAdapter +org.slf4j.helpers.Util +org.slf4j.simple.SimpleServiceProvider +org.slf4j.MDC +org.slf4j.simple.SimpleLoggerFactory +org.slf4j.helpers.AbstractLogger +org.slf4j.helpers.LegacyAbstractLogger +org.slf4j.simple.SimpleLogger +org.slf4j.spi.LoggingEventBuilder +org.slf4j.simple.SimpleLoggerConfiguration +java.text.Format +java.text.DateFormat +java.text.SimpleDateFormat +org.slf4j.simple.SimpleLoggerConfiguration$$Lambda$583/0x00007f4204151ec0 +org.slf4j.simple.OutputChoice +org.slf4j.simple.OutputChoice$OutputChoiceType +java.text.AttributedCharacterIterator$Attribute +java.text.Format$Field +java.text.DateFormat$Field +java.util.Calendar +java.util.spi.LocaleServiceProvider +sun.util.spi.CalendarProvider +sun.util.locale.provider.LocaleProviderAdapter +sun.util.locale.provider.LocaleProviderAdapter$Type +sun.util.locale.provider.LocaleProviderAdapter$1 +sun.util.locale.provider.ResourceBundleBasedAdapter +sun.util.locale.provider.JRELocaleProviderAdapter +sun.util.cldr.CLDRLocaleProviderAdapter +sun.util.locale.provider.LocaleDataMetaInfo +sun.util.cldr.CLDRBaseLocaleDataMetaInfo +sun.util.locale.LanguageTag +sun.util.locale.ParseStatus +sun.util.locale.StringTokenIterator +sun.util.locale.InternalLocaleBuilder +sun.util.locale.InternalLocaleBuilder$CaseInsensitiveChar +sun.util.locale.BaseLocale$Key +sun.util.locale.LocaleObjectCache +sun.util.locale.BaseLocale$Cache +sun.util.locale.LocaleObjectCache$CacheEntry +java.util.Locale$Cache +sun.util.cldr.CLDRLocaleProviderAdapter$$Lambda$57/0x80000005e +jdk.internal.module.ModulePatcher$PatchedModuleReader +sun.net.www.protocol.jrt.Handler +sun.util.resources.cldr.provider.CLDRLocaleDataMetaInfo +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$59/0x800000060 +sun.util.locale.provider.AvailableLanguageTags +sun.util.locale.provider.CalendarProviderImpl +java.util.Calendar$Builder +java.util.GregorianCalendar +sun.util.calendar.CalendarSystem +sun.util.calendar.CalendarSystem$GregorianHolder +sun.util.calendar.AbstractCalendar +sun.util.calendar.BaseCalendar +sun.util.calendar.Gregorian +sun.util.locale.provider.CalendarDataUtility +java.util.Locale$Builder +java.util.spi.CalendarDataProvider +sun.util.locale.provider.LocaleServiceProviderPool +java.text.spi.BreakIteratorProvider +java.text.spi.CollatorProvider +java.text.spi.DateFormatProvider +java.text.spi.DateFormatSymbolsProvider +java.text.spi.DecimalFormatSymbolsProvider +java.text.spi.NumberFormatProvider +java.util.spi.CurrencyNameProvider +java.util.spi.LocaleNameProvider +java.util.spi.TimeZoneNameProvider +sun.util.locale.provider.LocaleServiceProviderPool$LocalizedObjectGetter +sun.util.locale.provider.CalendarDataUtility$CalendarWeekParameterGetter +java.util.ResourceBundle$Control +java.util.ResourceBundle +java.util.ResourceBundle$Control$CandidateListCache +java.util.ResourceBundle$SingleFormatControl +java.util.ResourceBundle$NoFallbackControl +sun.util.cldr.CLDRLocaleProviderAdapter$$Lambda$58/0x80000005f +sun.util.locale.provider.CalendarDataProviderImpl +sun.util.cldr.CLDRCalendarDataProviderImpl +sun.util.locale.provider.LocaleResources +sun.util.resources.LocaleData +sun.util.resources.LocaleData$1 +sun.util.resources.Bundles$Strategy +sun.util.resources.LocaleData$LocaleDataStrategy +sun.util.resources.Bundles +sun.util.resources.Bundles$1 +jdk.internal.access.JavaUtilResourceBundleAccess +java.util.ResourceBundle$1 +java.util.ResourceBundle$2 +sun.util.resources.Bundles$CacheKey +java.util.ListResourceBundle +sun.util.resources.cldr.CalendarData +java.util.ResourceBundle$ResourceBundleProviderHelper +java.util.ResourceBundle$ResourceBundleProviderHelper$$Lambda$11/0x80000000f +sun.util.resources.Bundles$CacheKeyReference +sun.util.resources.Bundles$BundleReference +sun.util.locale.provider.LocaleResources$ResourceReference +sun.util.calendar.CalendarDate +sun.util.calendar.BaseCalendar$Date +sun.util.calendar.Gregorian$Date +sun.util.calendar.CalendarUtils +java.text.DateFormatSymbols +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$61/0x800000062 +sun.util.locale.provider.DateFormatSymbolsProviderImpl +sun.text.resources.cldr.FormatData +sun.text.resources.cldr.FormatData_en +java.text.NumberFormat +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$63/0x800000064 +sun.util.locale.provider.NumberFormatProviderImpl +java.text.DecimalFormatSymbols +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$62/0x800000063 +sun.util.locale.provider.DecimalFormatSymbolsProviderImpl +java.lang.StringLatin1$CharsSpliterator +java.util.stream.IntStream +java.util.stream.IntPipeline +java.util.stream.IntPipeline$Head +java.util.function.IntPredicate +java.text.DecimalFormatSymbols$$Lambda$7/0x80000000b +java.util.stream.IntPipeline$StatelessOp +java.util.stream.IntPipeline$10 +java.util.function.IntConsumer +java.util.stream.Sink$OfInt +java.util.stream.FindOps$FindSink$OfInt +java.util.OptionalInt +java.util.stream.FindOps$FindSink$OfInt$$Lambda$36/0x800000047 +java.util.stream.FindOps$FindSink$OfInt$$Lambda$34/0x800000045 +java.util.stream.FindOps$FindSink$OfInt$$Lambda$35/0x800000046 +java.util.stream.FindOps$FindSink$OfInt$$Lambda$33/0x800000044 +java.util.stream.Sink$ChainedInt +java.util.stream.IntPipeline$10$1 +java.lang.StringUTF16$CharsSpliterator +java.text.DecimalFormat +java.text.FieldPosition +java.text.DigitList +java.math.RoundingMode +java.util.Date +org.slf4j.helpers.Reporter +org.slf4j.helpers.Reporter$TargetChoice +org.slf4j.helpers.Reporter$Level +org.slf4j.simple.SimpleLoggerFactory$$Lambda$596/0x00007f42041531c0 +software.amazon.lambda.powertools.kafka.internal.DeserializationUtils$HandlerInfo +com.amazonaws.services.lambda.runtime.RequestHandler +software.amazon.lambda.powertools.kafka.testutils.AvroHandler +org.apache.kafka.clients.consumer.ConsumerRecords +com.amazonaws.services.lambda.runtime.Context +software.amazon.lambda.powertools.kafka.Deserialization +software.amazon.lambda.powertools.kafka.DeserializationType +jdk.proxy2.$Proxy20 +org.slf4j.event.Level +java.text.DontCareFieldPosition +java.text.Format$FieldDelegate +java.text.DontCareFieldPosition$1 +java.text.NumberFormat$Field +org.slf4j.helpers.MessageFormatter +org.slf4j.helpers.FormattingTuple +org.assertj.core.api.InstanceOfAssertFactories +org.assertj.core.api.Assertions +org.assertj.core.api.NumberAssert +org.assertj.core.api.ComparableAssert +org.assertj.core.api.Descriptable +org.assertj.core.api.ExtensionPoints +org.assertj.core.api.Assert +org.assertj.core.api.AbstractAssert +org.assertj.core.api.AbstractObjectAssert +org.assertj.core.api.AbstractComparableAssert +org.assertj.core.api.AbstractBigIntegerAssert +org.assertj.core.api.BigIntegerAssert +org.assertj.core.data.TemporalOffset +org.assertj.core.data.TemporalUnitOffset +org.assertj.core.data.TemporalUnitWithinOffset +org.assertj.core.data.TemporalUnitLessThanOffset +org.assertj.core.configuration.ConfigurationProvider +org.assertj.core.api.AssertionsForClassTypes +org.assertj.core.api.AssertionsForInterfaceTypes +org.assertj.core.api.EnumerableAssert +org.assertj.core.api.AbstractCharSequenceAssert +org.assertj.core.api.CharSequenceAssert +org.assertj.core.api.AbstractStringAssert +org.assertj.core.api.StringAssert +org.assertj.core.api.AbstractDateAssert +org.assertj.core.api.DateAssert +org.assertj.core.api.AbstractTemporalAssert +org.assertj.core.api.AbstractZonedDateTimeAssert +org.assertj.core.api.ZonedDateTimeAssert +org.assertj.core.api.AbstractShortAssert +org.assertj.core.api.ShortAssert +org.assertj.core.api.ArraySortedAssert +org.assertj.core.api.AbstractEnumerableAssert +org.assertj.core.api.AbstractArrayAssert +org.assertj.core.api.AbstractShortArrayAssert +org.assertj.core.api.ShortArrayAssert +org.assertj.core.api.AbstractYearMonthAssert +org.assertj.core.api.YearMonthAssert +org.assertj.core.api.AbstractInstantAssert +org.assertj.core.api.InstantAssert +org.assertj.core.api.AbstractDurationAssert +org.assertj.core.api.DurationAssert +org.assertj.core.api.AbstractPeriodAssert +org.assertj.core.api.PeriodAssert +org.assertj.core.api.AbstractThrowableAssert +org.assertj.core.api.ThrowableAssert +org.assertj.core.api.AbstractLocalDateTimeAssert +org.assertj.core.api.LocalDateTimeAssert +org.assertj.core.api.AbstractOffsetDateTimeAssert +org.assertj.core.api.OffsetDateTimeAssert +org.assertj.core.api.AbstractOffsetTimeAssert +org.assertj.core.api.OffsetTimeAssert +org.assertj.core.api.AbstractLocalTimeAssert +org.assertj.core.api.LocalTimeAssert +org.assertj.core.api.AbstractLocalDateAssert +org.assertj.core.api.LocalDateAssert +org.assertj.core.api.AbstractByteArrayAssert +org.assertj.core.api.ByteArrayAssert +org.assertj.core.api.AbstractByteAssert +org.assertj.core.api.ByteAssert +org.assertj.core.api.AbstractCharacterAssert +org.assertj.core.api.CharacterAssert +org.assertj.core.api.AbstractCharArrayAssert +org.assertj.core.api.CharArrayAssert +org.assertj.core.api.AbstractBooleanAssert +org.assertj.core.api.BooleanAssert +org.assertj.core.api.AbstractBigDecimalAssert +org.assertj.core.api.BigDecimalAssert +org.assertj.core.api.AbstractUriAssert +org.assertj.core.api.UriAssert +org.assertj.core.api.AbstractUrlAssert +org.assertj.core.api.UrlAssert +org.assertj.core.api.AbstractBooleanArrayAssert +org.assertj.core.api.BooleanArrayAssert +org.assertj.core.api.AbstractIntArrayAssert +org.assertj.core.api.IntArrayAssert +org.assertj.core.api.AbstractIntegerAssert +org.assertj.core.api.IntegerAssert +org.assertj.core.api.AbstractFloatArrayAssert +org.assertj.core.api.FloatArrayAssert +org.assertj.core.api.FloatingPointNumberAssert +org.assertj.core.api.AbstractFloatAssert +org.assertj.core.api.FloatAssert +org.assertj.core.api.AbstractLongArrayAssert +org.assertj.core.api.LongArrayAssert +org.assertj.core.api.AbstractLongAssert +org.assertj.core.api.LongAssert +org.assertj.core.api.AbstractDoubleArrayAssert +org.assertj.core.api.DoubleArrayAssert +org.assertj.core.api.AbstractDoubleAssert +org.assertj.core.api.DoubleAssert +org.assertj.core.api.AbstractFileAssert +org.assertj.core.api.FileAssert +org.assertj.core.api.AbstractInputStreamAssert +org.assertj.core.api.InputStreamAssert +org.assertj.core.api.GenericComparableAssert +org.assertj.core.api.AbstractUniversalComparableAssert +org.assertj.core.api.UniversalComparableAssert +org.assertj.core.description.Description +org.assertj.core.description.LazyTextDescription +org.assertj.core.description.TextDescription +org.assertj.core.api.AssertionInfo +org.assertj.core.internal.ComparisonStrategy +org.assertj.core.api.ObjectEnumerableAssert +org.assertj.core.api.IndexedObjectEnumerableAssert +org.assertj.core.api.AbstractIterableAssert +org.assertj.core.api.AbstractCollectionAssert +org.assertj.core.api.AbstractListAssert +org.assertj.core.api.FactoryBasedNavigableListAssert +org.assertj.core.api.ListAssert +org.assertj.core.api.ObjectAssert +org.assertj.core.internal.Objects +org.assertj.core.error.ErrorMessageFactory +org.assertj.core.util.introspection.IntrospectionError +org.assertj.core.internal.AbstractComparisonStrategy +org.assertj.core.internal.StandardComparisonStrategy +org.assertj.core.util.introspection.PropertySupport +org.assertj.core.internal.Failures +org.assertj.core.error.AssertionErrorCreator +org.assertj.core.util.Arrays +org.assertj.core.error.ConstructorInvoker +org.assertj.core.util.introspection.FieldSupport +org.assertj.core.error.GroupTypeDescription +org.assertj.core.internal.Conditions +org.assertj.core.api.WritableAssertionInfo +org.assertj.core.presentation.Representation +org.assertj.core.configuration.Configuration +org.assertj.core.configuration.PreferredAssumptionException +org.assertj.core.configuration.PreferredAssumptionException$1 +org.assertj.core.configuration.Services +org.assertj.core.util.Lists +org.assertj.core.util.Streams +org.assertj.core.util.Lists$$Lambda$597/0x00007f42041958b0 +org.assertj.core.presentation.CompositeRepresentation +java.lang.invoke.LambdaForm$DMH/0x00007f4204154800 +org.assertj.core.presentation.CompositeRepresentation$$Lambda$598/0x00007f4204195d28 +java.util.stream.SortedOps$SizedRefSortingSink +org.assertj.core.presentation.StandardRepresentation +java.time.chrono.ChronoZonedDateTime +java.time.ZonedDateTime +java.time.OffsetDateTime +java.nio.file.DirectoryStream +org.assertj.core.internal.Comparables +org.junit.jupiter.api.extension.AfterTestExecutionCallback +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$599/0x00007f42041967b8 +org.junit.jupiter.engine.descriptor.CallbackSupport$$Lambda$600/0x00007f42041969d8 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$601/0x00007f4204196c10 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$602/0x00007f4204196e38 +org.junit.jupiter.engine.descriptor.CallbackSupport$$Lambda$603/0x00007f4204197058 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$604/0x00007f4204197280 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$EntriesBackup$$Lambda$605/0x00007f42041974b8 +org.junitpioneer.jupiter.EnvironmentVariableUtils$$Lambda$606/0x00007f42041976f0 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$EntriesBackup$$Lambda$607/0x00007f4204197928 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$608/0x00007f4204197b60 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$609/0x00007f4204197d88 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$610/0x00007f4204154c00 +org.junit.jupiter.engine.descriptor.MethodExtensionContext$$Lambda$611/0x00007f4204198000 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$612/0x00007f4204198240 +org.junit.jupiter.engine.extension.AutoCloseExtension$$Lambda$613/0x00007f4204198460 +org.junit.jupiter.engine.extension.AutoCloseExtension$$Lambda$614/0x00007f42041986b0 +org.junit.jupiter.api.extension.TestInstancePreDestroyCallback$$Lambda$615/0x00007f42041988e8 +org.junit.jupiter.api.extension.TestInstancePreDestroyCallback$$Lambda$616/0x00007f4204198b28 +org.junit.jupiter.engine.extension.AutoCloseExtension$$Lambda$617/0x00007f4204198d60 +org.junit.jupiter.api.AutoClose +org.junit.jupiter.engine.extension.AutoCloseExtension$$Lambda$618/0x00007f42041991b0 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$619/0x00007f42041993e8 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$620/0x00007f4204199610 +java.util.concurrent.ConcurrentHashMap$EntrySpliterator +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$EvaluatedValue +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$621/0x00007f4204199a70 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$622/0x00007f4204199cb0 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$EvaluatedValue$$Lambda$623/0x00007f4204199f00 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$624/0x00007f420419a140 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$625/0x00007f420419a378 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$626/0x00007f420419a5a0 +org.junit.platform.engine.TestExecutionResult +org.junit.platform.engine.TestExecutionResult$Status +org.junit.jupiter.api.extension.TestWatcher +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$627/0x00007f420419b048 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$628/0x00007f420419b280 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$629/0x00007f420419b4b8 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$630/0x00007f420419b6f8 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$631/0x00007f420419b948 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$632/0x00007f420419bb88 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$633/0x00007f420419bdc8 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$634/0x00007f420419c018 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$635/0x00007f420419c250 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$636/0x00007f420419c478 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$637/0x00007f420419c6b0 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$638/0x00007f420419c8d8 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$639/0x00007f420419cb10 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$640/0x00007f420419cd38 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$1 +software.amazon.lambda.powertools.kafka.testutils.ProtobufHandler +software.amazon.lambda.powertools.kafka.testutils.JsonHandler +java.lang.Throwable$PrintStreamOrWriter +java.lang.Throwable$WrappedPrintStream +java.lang.StackTraceElement$HashedModules +java.lang.invoke.LambdaForm$DMH/0x00007f42041a0000 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$641/0x00007f420419d5f8 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$642/0x00007f420419d830 +org.junit.jupiter.engine.extension.AutoCloseExtension$$Lambda$643/0x00007f420419da50 +org.junit.jupiter.engine.extension.AutoCloseExtension$$Lambda$644/0x00007f420419dca0 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$645/0x00007f420419def0 +org.apache.maven.surefire.api.util.internal.ObjectUtils +org.apache.maven.surefire.api.util.internal.ImmutableMap$Node +org.mockito.session.MockitoSessionLogger +org.mockito.quality.Strictness +org.mockito.junit.jupiter.resolver.CompositeParameterResolver +org.mockito.junit.jupiter.resolver.MockParameterResolver +org.mockito.junit.jupiter.resolver.CaptorParameterResolver +com.fasterxml.jackson.core.Versioned +com.fasterxml.jackson.core.TreeCodec +com.fasterxml.jackson.core.ObjectCodec +com.fasterxml.jackson.databind.ObjectMapper +org.junit.jupiter.api.extension.RegisterExtension +org.mockito.Mock +org.mockito.stubbing.Answer +org.mockito.Answers +org.mockito.Mock$Strictness +org.mockito.invocation.InvocationOnMock +org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer +org.mockito.internal.stubbing.defaultanswers.ReturnsSmartNulls +org.mockito.internal.stubbing.defaultanswers.RetrieveGenericsForDefaultAnswers$AnswerCallback +org.mockito.internal.stubbing.defaultanswers.ReturnsMoreEmptyValues +org.mockito.internal.stubbing.defaultanswers.ReturnsEmptyValues +org.mockito.internal.stubbing.defaultanswers.ReturnsMocks +org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs +org.mockito.invocation.DescribedInvocation +org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs$ReturnsDeepStubsSerializationFallback +org.mockito.stubbing.ValidableAnswer +org.mockito.internal.stubbing.answers.CallsRealMethods +org.mockito.exceptions.base.MockitoException +org.mockito.internal.stubbing.defaultanswers.TriesToReturnSelf +jdk.proxy2.$Proxy21 +com.fasterxml.jackson.core.TokenStreamFactory +com.fasterxml.jackson.core.JsonFactory +com.fasterxml.jackson.databind.MappingJsonFactory +com.fasterxml.jackson.databind.jsontype.SubtypeResolver +com.fasterxml.jackson.databind.jsontype.impl.StdSubtypeResolver +com.fasterxml.jackson.databind.DatabindContext +com.fasterxml.jackson.databind.SerializerProvider +com.fasterxml.jackson.databind.ser.DefaultSerializerProvider +com.fasterxml.jackson.databind.ser.DefaultSerializerProvider$Impl +com.fasterxml.jackson.databind.deser.DeserializerFactory +com.fasterxml.jackson.databind.deser.BasicDeserializerFactory +com.fasterxml.jackson.databind.deser.BeanDeserializerFactory +com.fasterxml.jackson.databind.DeserializationContext +com.fasterxml.jackson.databind.deser.DefaultDeserializationContext +com.fasterxml.jackson.databind.deser.DefaultDeserializationContext$Impl +com.fasterxml.jackson.databind.ser.SerializerFactory +com.fasterxml.jackson.databind.ser.BasicSerializerFactory +com.fasterxml.jackson.databind.ser.BeanSerializerFactory +com.fasterxml.jackson.databind.AnnotationIntrospector +com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector +com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy$Provider +com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy$Provider +com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator +com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator$Base +com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator +com.fasterxml.jackson.databind.util.StdDateFormat +com.fasterxml.jackson.databind.DatabindException +com.fasterxml.jackson.databind.JsonMappingException +com.fasterxml.jackson.core.util.BufferRecycler$Gettable +com.fasterxml.jackson.core.io.SegmentedStringWriter +com.fasterxml.jackson.core.util.ByteArrayBuilder +com.fasterxml.jackson.core.TreeNode +com.fasterxml.jackson.databind.JsonSerializable +com.fasterxml.jackson.databind.JsonSerializable$Base +com.fasterxml.jackson.databind.JsonNode +com.fasterxml.jackson.databind.node.BaseJsonNode +com.fasterxml.jackson.databind.node.ValueNode +com.fasterxml.jackson.databind.node.NullNode +com.fasterxml.jackson.databind.introspect.ClassIntrospector +com.fasterxml.jackson.databind.introspect.BasicClassIntrospector +com.fasterxml.jackson.databind.Module$SetupContext +com.fasterxml.jackson.databind.introspect.VisibilityChecker +com.fasterxml.jackson.core.JsonParser +com.fasterxml.jackson.core.base.ParserMinimalBase +com.fasterxml.jackson.databind.node.TreeTraversingParser +com.fasterxml.jackson.core.JsonGenerator +com.fasterxml.jackson.databind.util.TokenBuffer +com.fasterxml.jackson.core.type.ResolvedType +com.fasterxml.jackson.databind.JavaType +com.fasterxml.jackson.databind.type.TypeBase +com.fasterxml.jackson.databind.type.ArrayType +com.fasterxml.jackson.databind.type.CollectionLikeType +com.fasterxml.jackson.databind.type.CollectionType +com.fasterxml.jackson.databind.type.MapLikeType +com.fasterxml.jackson.databind.type.MapType +com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder +com.fasterxml.jackson.databind.exc.MismatchedInputException +com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair +com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector +com.fasterxml.jackson.databind.introspect.Annotated +com.fasterxml.jackson.databind.introspect.TypeResolutionContext +com.fasterxml.jackson.databind.introspect.AnnotatedClass +com.fasterxml.jackson.databind.introspect.AnnotatedMember +com.fasterxml.jackson.databind.introspect.VirtualAnnotatedMember +com.fasterxml.jackson.databind.util.Named +com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition +com.fasterxml.jackson.databind.util.SimpleBeanPropertyDefinition +com.fasterxml.jackson.databind.BeanProperty +com.fasterxml.jackson.databind.introspect.ConcreteBeanPropertyBase +com.fasterxml.jackson.databind.ser.PropertyWriter +com.fasterxml.jackson.databind.ser.BeanPropertyWriter +com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter +com.fasterxml.jackson.databind.ser.impl.AttributePropertyWriter +com.fasterxml.jackson.databind.introspect.AnnotatedWithParams +com.fasterxml.jackson.databind.introspect.AnnotatedMethod +com.fasterxml.jackson.databind.annotation.JsonSerialize +com.fasterxml.jackson.annotation.JsonView +com.fasterxml.jackson.annotation.JsonFormat +com.fasterxml.jackson.annotation.JsonTypeInfo +com.fasterxml.jackson.annotation.JsonRawValue +com.fasterxml.jackson.annotation.JsonUnwrapped +com.fasterxml.jackson.annotation.JsonBackReference +com.fasterxml.jackson.annotation.JsonManagedReference +com.fasterxml.jackson.databind.annotation.JsonDeserialize +com.fasterxml.jackson.annotation.JsonMerge +com.fasterxml.jackson.databind.ext.Java7Support +java.lang.IllegalAccessError +com.fasterxml.jackson.databind.ext.Java7SupportImpl +com.fasterxml.jackson.databind.util.ClassUtil +com.fasterxml.jackson.databind.util.ClassUtil$Ctor +java.lang.reflect.AnnotatedType +java.beans.Transient +java.beans.ConstructorProperties +com.fasterxml.jackson.databind.util.LookupCache +com.fasterxml.jackson.databind.util.LRUMap +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$Builder +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap +java.io.InvalidObjectException +com.fasterxml.jackson.databind.util.internal.Linked +com.fasterxml.jackson.databind.util.internal.LinkedDeque +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$1 +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$2 +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$3 +java.util.concurrent.atomic.AtomicLongArray +java.lang.invoke.VarHandleLongs$Array +java.util.concurrent.atomic.AtomicReferenceArray +java.lang.invoke.VarHandleReferences$Array +com.fasterxml.jackson.databind.cfg.BaseSettings +com.fasterxml.jackson.databind.type.TypeFactory +com.fasterxml.jackson.databind.type.SimpleType +com.fasterxml.jackson.databind.type.IdentityEqualityType +com.fasterxml.jackson.databind.type.PlaceholderForType +com.fasterxml.jackson.databind.type.ReferenceType +com.fasterxml.jackson.databind.type.IterationType +com.fasterxml.jackson.databind.type.ResolvedRecursiveType +com.fasterxml.jackson.databind.type.TypeParser +com.fasterxml.jackson.databind.type.TypeBindings +java.text.ParseException +com.fasterxml.jackson.core.Base64Variants +com.fasterxml.jackson.core.Base64Variant +com.fasterxml.jackson.core.Base64Variant$PaddingReadBehaviour +com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy +com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy +com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy$RecordNaming +com.fasterxml.jackson.databind.cfg.CacheProvider +com.fasterxml.jackson.databind.cfg.DefaultCacheProvider +com.fasterxml.jackson.core.io.DataOutputAsStream +com.fasterxml.jackson.core.SerializableString +com.fasterxml.jackson.core.TSFBuilder +com.fasterxml.jackson.core.JsonFactoryBuilder +java.io.CharArrayReader +com.fasterxml.jackson.core.async.NonBlockingInputFeeder +com.fasterxml.jackson.core.async.ByteBufferFeeder +com.fasterxml.jackson.core.base.ParserBase +com.fasterxml.jackson.core.json.JsonParserBase +com.fasterxml.jackson.core.json.async.NonBlockingJsonParserBase +com.fasterxml.jackson.core.json.async.NonBlockingUtf8JsonParserBase +com.fasterxml.jackson.core.json.async.NonBlockingByteBufferJsonParser +com.fasterxml.jackson.core.json.ReaderBasedJsonParser +com.fasterxml.jackson.core.json.UTF8DataInputJsonParser +com.fasterxml.jackson.core.base.GeneratorBase +com.fasterxml.jackson.core.json.JsonGeneratorImpl +com.fasterxml.jackson.core.json.WriterBasedJsonGenerator +com.fasterxml.jackson.core.json.UTF8JsonGenerator +com.fasterxml.jackson.core.io.UTF8Writer +com.fasterxml.jackson.core.async.ByteArrayFeeder +com.fasterxml.jackson.core.json.async.NonBlockingJsonParser +com.fasterxml.jackson.core.util.JacksonFeature +com.fasterxml.jackson.core.JsonFactory$Feature +com.fasterxml.jackson.core.JsonParser$Feature +com.fasterxml.jackson.core.JsonGenerator$Feature +com.fasterxml.jackson.core.io.SerializedString +com.fasterxml.jackson.core.io.JsonStringEncoder +com.fasterxml.jackson.core.io.CharTypes +com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer +com.fasterxml.jackson.core.exc.StreamConstraintsException +com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer$TableInfo +com.fasterxml.jackson.core.util.JsonRecyclerPools +com.fasterxml.jackson.core.util.RecyclerPool +com.fasterxml.jackson.core.util.RecyclerPool$ThreadLocalPoolBase +com.fasterxml.jackson.core.util.JsonRecyclerPools$ThreadLocalPool +com.fasterxml.jackson.core.util.RecyclerPool$WithPool +com.fasterxml.jackson.core.StreamReadConstraints +com.fasterxml.jackson.core.StreamWriteConstraints +com.fasterxml.jackson.core.ErrorReportConfiguration +com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer +com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer$TableInfo +com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer$Bucket +com.fasterxml.jackson.databind.util.RootNameLookup +com.fasterxml.jackson.databind.introspect.ClassIntrospector$MixInResolver +com.fasterxml.jackson.databind.introspect.SimpleMixInResolver +com.fasterxml.jackson.databind.BeanDescription +com.fasterxml.jackson.databind.introspect.BasicBeanDescription +com.fasterxml.jackson.databind.cfg.MapperConfig +com.fasterxml.jackson.databind.cfg.MapperConfigBase +com.fasterxml.jackson.databind.SerializationConfig +com.fasterxml.jackson.databind.DeserializationConfig +com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver +com.fasterxml.jackson.databind.introspect.AnnotationCollector +com.fasterxml.jackson.databind.util.Annotations +com.fasterxml.jackson.databind.introspect.AnnotationCollector$EmptyCollector +com.fasterxml.jackson.databind.introspect.AnnotationCollector$NoAnnotations +com.fasterxml.jackson.databind.introspect.AnnotatedClass$Creators +com.fasterxml.jackson.databind.introspect.AnnotatedConstructor +com.fasterxml.jackson.databind.cfg.ConfigOverrides +com.fasterxml.jackson.annotation.JacksonAnnotationValue +com.fasterxml.jackson.annotation.JsonInclude$Value +com.fasterxml.jackson.annotation.JsonInclude$Include +com.fasterxml.jackson.annotation.JsonSetter$Value +com.fasterxml.jackson.annotation.Nulls +com.fasterxml.jackson.databind.introspect.VisibilityChecker$Std +com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility +com.fasterxml.jackson.databind.cfg.CoercionConfigs +com.fasterxml.jackson.databind.type.LogicalType +com.fasterxml.jackson.databind.cfg.CoercionAction +com.fasterxml.jackson.databind.cfg.CoercionConfig +com.fasterxml.jackson.databind.cfg.MutableCoercionConfig +com.fasterxml.jackson.databind.cfg.CoercionInputShape +com.fasterxml.jackson.databind.jsontype.DefaultBaseTypeLimitingValidator +com.fasterxml.jackson.core.PrettyPrinter +com.fasterxml.jackson.annotation.JsonFormat$Value +com.fasterxml.jackson.annotation.JsonFormat$Shape +com.fasterxml.jackson.annotation.JsonFormat$Features +com.fasterxml.jackson.databind.cfg.ConfigOverride +com.fasterxml.jackson.databind.cfg.ConfigOverride$Empty +com.fasterxml.jackson.databind.cfg.ConfigFeature +com.fasterxml.jackson.databind.MapperFeature +com.fasterxml.jackson.core.util.Instantiatable +com.fasterxml.jackson.core.util.DefaultPrettyPrinter +com.fasterxml.jackson.core.util.DefaultPrettyPrinter$Indenter +com.fasterxml.jackson.core.util.Separators +com.fasterxml.jackson.core.util.Separators$Spacing +com.fasterxml.jackson.core.util.DefaultPrettyPrinter$NopIndenter +com.fasterxml.jackson.core.util.DefaultPrettyPrinter$FixedSpaceIndenter +com.fasterxml.jackson.core.util.DefaultIndenter +com.fasterxml.jackson.databind.SerializationFeature +com.fasterxml.jackson.databind.cfg.DatatypeFeatures +com.fasterxml.jackson.databind.cfg.DatatypeFeatures$DefaultHolder +com.fasterxml.jackson.databind.cfg.DatatypeFeature +com.fasterxml.jackson.databind.cfg.EnumFeature +com.fasterxml.jackson.databind.cfg.JsonNodeFeature +com.fasterxml.jackson.databind.cfg.ContextAttributes +com.fasterxml.jackson.databind.cfg.ContextAttributes$Impl +com.fasterxml.jackson.databind.DeserializationFeature +com.fasterxml.jackson.databind.node.JsonNodeCreator +com.fasterxml.jackson.databind.node.JsonNodeFactory +com.fasterxml.jackson.databind.node.MissingNode +com.fasterxml.jackson.databind.node.BooleanNode +com.fasterxml.jackson.databind.node.NumericNode +com.fasterxml.jackson.databind.node.DoubleNode +com.fasterxml.jackson.databind.node.DecimalNode +com.fasterxml.jackson.databind.node.IntNode +com.fasterxml.jackson.databind.node.ShortNode +com.fasterxml.jackson.databind.node.BigIntegerNode +com.fasterxml.jackson.databind.node.LongNode +com.fasterxml.jackson.databind.node.FloatNode +com.fasterxml.jackson.databind.node.TextNode +com.fasterxml.jackson.databind.node.BinaryNode +com.fasterxml.jackson.databind.node.POJONode +com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable +com.fasterxml.jackson.databind.JsonSerializer +com.fasterxml.jackson.databind.jsonschema.SchemaAware +com.fasterxml.jackson.databind.ser.std.StdSerializer +com.fasterxml.jackson.databind.ser.std.NullSerializer +com.fasterxml.jackson.databind.ser.impl.FailingSerializer +com.fasterxml.jackson.databind.ser.std.ToEmptyObjectSerializer +com.fasterxml.jackson.databind.ser.impl.UnknownSerializer +com.fasterxml.jackson.databind.ser.ContextualSerializer +com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer +com.fasterxml.jackson.databind.exc.InvalidDefinitionException +com.fasterxml.jackson.databind.exc.InvalidTypeIdException +com.fasterxml.jackson.databind.node.ContainerNode +com.fasterxml.jackson.databind.node.ObjectNode +com.fasterxml.jackson.databind.ser.ResolvableSerializer +com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer +com.fasterxml.jackson.databind.ser.SerializerCache +com.fasterxml.jackson.databind.deser.NullValueProvider +com.fasterxml.jackson.databind.JsonDeserializer +com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer +com.fasterxml.jackson.databind.exc.PropertyBindingException +com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException +com.fasterxml.jackson.databind.exc.InvalidFormatException +com.fasterxml.jackson.databind.exc.ValueInstantiationException +com.fasterxml.jackson.databind.exc.MissingInjectableValueExcepion +com.fasterxml.jackson.databind.deser.UnresolvedForwardReference +com.fasterxml.jackson.databind.deser.ContextualDeserializer +com.fasterxml.jackson.databind.deser.ResolvableDeserializer +com.fasterxml.jackson.databind.deser.ValueInstantiator$Gettable +com.fasterxml.jackson.databind.deser.std.StdDeserializer +com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase +com.fasterxml.jackson.databind.deser.std.EnumMapDeserializer +com.fasterxml.jackson.databind.deser.std.MapDeserializer +com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer +com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer +com.fasterxml.jackson.databind.deser.std.StringDeserializer +com.fasterxml.jackson.databind.deser.std.MapEntryDeserializer +com.fasterxml.jackson.databind.deser.std.TokenBufferDeserializer +com.fasterxml.jackson.databind.introspect.AnnotatedParameter +com.fasterxml.jackson.databind.deser.SettableBeanProperty +com.fasterxml.jackson.databind.deser.CreatorProperty +com.fasterxml.jackson.databind.deser.AbstractDeserializer +com.fasterxml.jackson.databind.deser.std.EnumDeserializer +com.fasterxml.jackson.databind.deser.std.ReferenceTypeDeserializer +com.fasterxml.jackson.databind.deser.std.AtomicReferenceDeserializer +com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer +com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer +com.fasterxml.jackson.databind.deser.std.EnumSetDeserializer +com.fasterxml.jackson.databind.deser.std.CollectionDeserializer +com.fasterxml.jackson.databind.deser.std.ArrayBlockingQueueDeserializer +com.fasterxml.jackson.databind.deser.std.StringCollectionDeserializer +com.fasterxml.jackson.databind.deser.impl.MethodProperty +com.fasterxml.jackson.databind.deser.impl.FieldProperty +com.fasterxml.jackson.databind.deser.impl.SetterlessProperty +com.fasterxml.jackson.databind.deser.impl.UnsupportedTypeDeserializer +com.fasterxml.jackson.databind.deser.impl.ErrorThrowingDeserializer +com.fasterxml.jackson.annotation.ObjectIdGenerator +com.fasterxml.jackson.annotation.ObjectIdGenerators$Base +com.fasterxml.jackson.annotation.ObjectIdGenerators$PropertyGenerator +com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator +com.fasterxml.jackson.databind.deser.BeanDeserializerBase +com.fasterxml.jackson.databind.deser.BeanDeserializer +com.fasterxml.jackson.databind.deser.std.ThrowableDeserializer +com.fasterxml.jackson.databind.deser.Deserializers +com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig +com.fasterxml.jackson.databind.deser.BeanDeserializerModifier +com.fasterxml.jackson.databind.AbstractTypeResolver +com.fasterxml.jackson.databind.deser.ValueInstantiators +com.fasterxml.jackson.databind.deser.KeyDeserializers +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializers +com.fasterxml.jackson.databind.KeyDeserializer +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$DelegatingKD +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$EnumKD +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringCtorKeyDeserializer +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringFactoryKeyDeserializer +com.fasterxml.jackson.databind.deser.DeserializerCache +com.fasterxml.jackson.databind.deser.std.StdDelegatingDeserializer +com.fasterxml.jackson.databind.ser.std.JsonValueSerializer +com.fasterxml.jackson.databind.ser.std.SerializableSerializer +com.fasterxml.jackson.databind.ser.std.StdScalarSerializer +com.fasterxml.jackson.databind.ser.std.DateTimeSerializerBase +com.fasterxml.jackson.databind.ser.std.CalendarSerializer +com.fasterxml.jackson.databind.ser.std.DateSerializer +com.fasterxml.jackson.databind.ser.std.ByteBufferSerializer +com.fasterxml.jackson.databind.ser.std.InetAddressSerializer +com.fasterxml.jackson.databind.ser.std.InetSocketAddressSerializer +com.fasterxml.jackson.databind.ser.std.TimeZoneSerializer +com.fasterxml.jackson.databind.ser.std.ToStringSerializerBase +com.fasterxml.jackson.databind.ser.std.ToStringSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializer +com.fasterxml.jackson.databind.ser.ContainerSerializer +com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase +com.fasterxml.jackson.databind.ser.std.CollectionSerializer +com.fasterxml.jackson.databind.ser.std.StaticListSerializerBase +com.fasterxml.jackson.databind.ser.impl.IndexedStringListSerializer +com.fasterxml.jackson.databind.ser.impl.StringCollectionSerializer +com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer +com.fasterxml.jackson.databind.ser.std.EnumSetSerializer +com.fasterxml.jackson.databind.ser.std.MapSerializer +com.fasterxml.jackson.databind.ser.impl.MapEntrySerializer +com.fasterxml.jackson.databind.ser.std.ArraySerializerBase +com.fasterxml.jackson.databind.ser.impl.StringArraySerializer +com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer +com.fasterxml.jackson.databind.ser.std.ReferenceTypeSerializer +com.fasterxml.jackson.databind.ser.impl.IteratorSerializer +com.fasterxml.jackson.databind.ser.std.IterableSerializer +com.fasterxml.jackson.databind.ser.std.EnumSerializer +com.fasterxml.jackson.databind.ser.impl.MapEntryAsPOJOSerializer +com.fasterxml.jackson.databind.ser.std.BeanSerializerBase +com.fasterxml.jackson.databind.ser.BeanSerializer +com.fasterxml.jackson.databind.ser.impl.PropertyBasedObjectIdGenerator +com.fasterxml.jackson.databind.introspect.AnnotatedField +com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer +com.fasterxml.jackson.databind.ser.std.StringSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers +com.fasterxml.jackson.databind.ser.std.NumberSerializers$Base +com.fasterxml.jackson.databind.ser.std.NumberSerializers$IntegerSerializer +com.fasterxml.jackson.core.JsonParser$NumberType +com.fasterxml.jackson.databind.ser.std.NumberSerializers$LongSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$IntLikeSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$ShortSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$DoubleSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$FloatSerializer +com.fasterxml.jackson.databind.ser.std.BooleanSerializer +com.fasterxml.jackson.databind.ser.std.BooleanSerializer$AsNumber +com.fasterxml.jackson.databind.ser.std.NumberSerializer$BigDecimalAsStringSerializer +com.fasterxml.jackson.databind.ser.std.StdJdkSerializers +java.util.Currency +java.util.UUID +com.fasterxml.jackson.databind.ser.std.UUIDSerializer +com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicBooleanSerializer +com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicIntegerSerializer +com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicLongSerializer +com.fasterxml.jackson.databind.ser.std.FileSerializer +com.fasterxml.jackson.databind.ser.std.ClassSerializer +com.fasterxml.jackson.databind.ser.std.TokenBufferSerializer +com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig +com.fasterxml.jackson.databind.ser.Serializers +com.fasterxml.jackson.databind.ser.BeanSerializerModifier +org.mockito.junit.jupiter.MockitoSettings +org.mockito.junit.jupiter.MockitoExtension$$Lambda$646/0x00007f42041f8000 +org.mockito.ArgumentMatchers +org.mockito.Mockito +org.mockito.ArgumentMatcher +org.mockito.verification.VerificationMode +org.mockito.verification.VerificationAfterDelay +org.mockito.verification.VerificationWithTimeout +org.mockito.session.MockitoSessionBuilder +org.mockito.MockitoFramework +org.mockito.internal.MockitoCore +org.mockito.stubbing.BaseStubber +org.mockito.stubbing.LenientStubber +org.mockito.MockingDetails +org.mockito.ScopedMock +org.mockito.MockedStatic +org.mockito.MockedConstruction +org.mockito.exceptions.misusing.NotAMockException +org.mockito.internal.verification.api.VerificationData +org.mockito.stubbing.Stubber +org.mockito.InOrder +org.mockito.exceptions.misusing.DoNotMockException +org.mockito.internal.verification.api.VerificationDataInOrder +org.mockito.internal.configuration.plugins.Plugins +org.mockito.plugins.MockitoPlugins +org.mockito.internal.configuration.plugins.PluginRegistry +org.mockito.plugins.PluginSwitch +org.mockito.internal.configuration.plugins.PluginLoader +org.mockito.internal.configuration.plugins.DefaultPluginSwitch +org.mockito.internal.configuration.plugins.DefaultMockitoPlugins +org.mockito.plugins.MockMaker +org.mockito.plugins.StackTraceCleanerProvider +org.mockito.plugins.InstantiatorProvider2 +org.mockito.plugins.AnnotationEngine +org.mockito.plugins.MockitoLogger +org.mockito.plugins.MemberAccessor +org.mockito.plugins.DoNotMockEnforcerWithType +org.mockito.internal.configuration.plugins.PluginInitializer +org.mockito.internal.configuration.plugins.PluginFinder +org.mockito.internal.util.collections.Iterables +org.mockito.internal.creation.bytebuddy.ClassCreatingMockMaker +org.mockito.plugins.InlineMockMaker +org.mockito.creation.instance.Instantiator +org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker +org.mockito.exceptions.base.MockitoInitializationException +org.mockito.internal.creation.bytebuddy.BytecodeGenerator +org.mockito.creation.instance.InstantiationException +org.mockito.exceptions.misusing.MockitoConfigurationException +org.mockito.plugins.MockMaker$TypeMockability +org.mockito.plugins.MockMaker$StaticMockControl +org.mockito.plugins.MockMaker$ConstructionMockControl +org.mockito.internal.PremainAttachAccess +org.mockito.internal.PremainAttach +java.lang.instrument.Instrumentation +net.bytebuddy.agent.Installer +java.io.Console +net.bytebuddy.ClassFileVersion +net.bytebuddy.ClassFileVersion$VersionLocator$Resolver +net.bytebuddy.ClassFileVersion$VersionLocator +net.bytebuddy.ClassFileVersion$VersionLocator$Resolved +java.lang.Process +net.bytebuddy.agent.ByteBuddyAgent +net.bytebuddy.agent.ByteBuddyAgent$AgentProvider +net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider +net.bytebuddy.agent.ByteBuddyAgent$AttachmentTypeEvaluator$InstallationAction +net.bytebuddy.agent.ByteBuddyAgent$AttachmentTypeEvaluator +java.security.PrivilegedActionException +com.sun.proxy.jdk.proxy1.$Proxy22 +java.security.DomainCombiner +net.bytebuddy.agent.ByteBuddyAgent$AttachmentTypeEvaluator$ForJava9CapableVm +java.lang.ProcessHandle +java.lang.ProcessHandle$Info +java.util.concurrent.CompletionStage +java.util.concurrent.CompletableFuture +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$Compound +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$Accessor +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$ForModularizedVm +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$ForJ9Vm +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$ForStandardToolsJarVm +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$ForUserDefinedToolsJar +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$ForEmulatedAttachment +net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm +net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm$ForJava9CapableVm +java.lang.ProcessHandleImpl +java.lang.ProcessHandleImpl$$Lambda$647/0x00007f420410ced8 +java.util.concurrent.ThreadLocalRandom +jdk.internal.util.random.RandomSupport +java.lang.invoke.LambdaForm$DMH/0x00007f4204204000 +java.lang.invoke.LambdaForm$DMH/0x00007f4204204400 +java.lang.ProcessHandleImpl$$Lambda$648/0x00007f420410d0f8 +java.lang.invoke.LambdaForm$DMH/0x00007f4204204800 +java.lang.invoke.LambdaForm$MH/0x00007f4204204c00 +java.util.concurrent.SynchronousQueue +java.util.concurrent.SynchronousQueue$Transferer +java.util.concurrent.SynchronousQueue$TransferStack +java.util.concurrent.SynchronousQueue$TransferStack$SNode +net.bytebuddy.agent.ByteBuddyAgent$AgentProvider$ForByteBuddyAgent +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$Accessor$Simple +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$Accessor$Simple$WithExternalAttachment +com.sun.tools.attach.VirtualMachine +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$Accessor$ExternalAttachment +java.util.zip.ZipInputStream +java.util.jar.JarInputStream +java.io.PushbackInputStream +sun.security.util.ManifestEntryVerifier +net.bytebuddy.agent.Attacher +java.lang.ProcessBuilder +java.lang.ProcessImpl +java.lang.ProcessImpl$Platform +java.lang.ProcessImpl$LaunchMechanism +java.lang.ProcessImpl$Platform$$Lambda$649/0x00007f420410f8c8 +java.lang.ProcessImpl$$Lambda$650/0x00007f420410faf0 +java.lang.ProcessImpl$1 +java.lang.ProcessImpl$ProcessPipeOutputStream +java.lang.ProcessImpl$ProcessPipeInputStream +java.lang.Process$PipeInputStream +java.lang.ProcessHandleImpl$ExitCompletion +java.util.concurrent.CompletableFuture$AltResult +java.util.concurrent.ForkJoinPool +java.lang.invoke.VarHandleLongs$FieldInstanceReadOnly +java.lang.invoke.VarHandleLongs$FieldInstanceReadWrite +java.lang.invoke.VarHandleInts$FieldStaticReadOnly +java.lang.invoke.VarHandleInts$FieldStaticReadWrite +java.util.concurrent.ForkJoinPool$ForkJoinWorkerThreadFactory +java.util.concurrent.ForkJoinPool$DefaultForkJoinWorkerThreadFactory +java.util.concurrent.ForkJoinPool$1 +java.util.concurrent.ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory +java.util.concurrent.ForkJoinPool$WorkQueue +java.util.concurrent.CompletableFuture$AsynchronousCompletionTask +java.util.concurrent.ForkJoinTask +java.util.concurrent.CompletableFuture$Completion +java.lang.ProcessHandleImpl$1 +java.lang.ProcessImpl$$Lambda$651/0x00007f4204111920 +java.util.concurrent.CompletableFuture$UniCompletion +java.util.concurrent.CompletableFuture$UniHandle +java.util.concurrent.ForkJoinTask$Aux +jdk.internal.event.Event +jdk.internal.event.ProcessStartEvent +sun.instrument.InstrumentationImpl +sun.instrument.TransformerManager +sun.instrument.TransformerManager$TransformerInfo +java.lang.ProcessBuilder$NullInputStream +java.io.FileOutputStream$1 +java.lang.ProcessBuilder$NullOutputStream +java.io.File$TempDirectory +java.security.SecureRandom +sun.security.jca.Providers +sun.security.jca.ProviderList +sun.security.jca.ProviderConfig +java.security.Provider +sun.security.jca.ProviderList$3 +sun.security.jca.ProviderList$1 +java.security.Provider$ServiceKey +java.security.Provider$EngineDescription +java.security.SecureRandomParameters +java.security.cert.CertStoreParameters +java.security.Policy$Parameters +javax.security.auth.login.Configuration$Parameters +sun.security.jca.ProviderList$2 +sun.security.provider.Sun +sun.security.util.SecurityConstants +java.net.NetPermission +java.security.SecurityPermission +java.net.SocketPermission +sun.security.provider.SunEntries +sun.security.provider.SunEntries$1 +java.security.SecureRandomSpi +sun.security.provider.NativePRNG +sun.security.provider.NativePRNG$Variant +sun.security.provider.NativePRNG$1 +sun.security.provider.NativePRNG$2 +sun.security.provider.NativePRNG$RandomIO +sun.security.provider.FileInputStreamPool +sun.security.provider.FileInputStreamPool$UnclosableInputStream +sun.security.provider.FileInputStreamPool$StreamRef +java.security.Provider$Service +java.security.Provider$UString +sun.security.provider.NativePRNG$Blocking +sun.security.provider.NativePRNG$NonBlocking +sun.security.util.SecurityProviderConstants +sun.security.util.KnownOIDs +sun.security.util.KnownOIDs$1 +sun.security.util.KnownOIDs$2 +sun.security.util.KnownOIDs$3 +sun.security.util.KnownOIDs$4 +sun.security.util.KnownOIDs$5 +sun.security.util.KnownOIDs$6 +sun.security.util.KnownOIDs$7 +sun.security.util.KnownOIDs$8 +sun.security.util.KnownOIDs$9 +sun.security.util.KnownOIDs$10 +jdk.internal.event.SecurityProviderServiceEvent +sun.security.provider.SecureRandom +java.security.MessageDigestSpi +java.security.MessageDigest +sun.security.jca.GetInstance +sun.security.provider.DigestBase +sun.security.provider.SHA +sun.security.jca.GetInstance$Instance +sun.security.util.MessageDigestSpi2 +java.security.MessageDigest$Delegate +java.security.MessageDigest$Delegate$CloneableDelegate +sun.security.provider.ByteArrayAccess +sun.security.provider.ByteArrayAccess$BE +java.lang.invoke.VarHandleByteArrayAsInts$ByteArrayViewVarHandle +java.lang.invoke.VarHandleByteArrayAsInts$ArrayHandle +java.lang.ArrayIndexOutOfBoundsException +java.lang.invoke.VarHandleByteArrayBase +java.lang.invoke.VarHandleByteArrayAsInts +java.lang.invoke.VarHandleByteArrayAsInts$ArrayHandle$$Lambda$652/0x00007f420411ea70 +java.lang.invoke.VarHandleByteArrayAsLongs$ByteArrayViewVarHandle +java.lang.invoke.VarHandleByteArrayAsLongs$ArrayHandle +java.lang.invoke.VarHandleByteArrayAsLongs +java.lang.invoke.VarHandleByteArrayAsLongs$ArrayHandle$$Lambda$653/0x00007f420411f3d8 +java.lang.invoke.VarHandle$TypesAndInvokers +java.lang.invoke.VarHandle$2 +java.lang.invoke.VarHandle$VarHandleDesc$Kind +java.lang.constant.ConstantDescs +java.lang.constant.ClassDesc +java.lang.constant.ConstantUtils +java.lang.constant.ReferenceClassDescImpl +java.lang.constant.DirectMethodHandleDesc$Kind +java.lang.constant.MethodTypeDesc +java.lang.constant.MethodTypeDescImpl +java.lang.constant.MethodHandleDesc +java.lang.constant.MethodHandleDesc$1 +java.lang.constant.DirectMethodHandleDesc +java.lang.constant.DirectMethodHandleDescImpl +java.lang.constant.DirectMethodHandleDescImpl$1 +java.lang.constant.DirectMethodHandleDesc$1 +java.lang.constant.DynamicConstantDesc +java.lang.constant.PrimitiveClassDescImpl +java.lang.constant.DynamicConstantDesc$AnonymousDynamicConstantDesc +java.io.DeleteOnExitHook +java.io.DeleteOnExitHook$1 +java.util.zip.DeflaterOutputStream +java.util.zip.ZipOutputStream +java.util.jar.JarOutputStream +java.util.zip.Deflater +java.util.zip.Deflater$DeflaterZStreamRef +java.util.zip.ZipOutputStream$XEntry +java.util.Vector$Itr +opened:/tmp/mockitoboot10528763096709983050.jar +org.mockito.internal.creation.bytebuddy.inject.MockMethodDispatcher +org.mockito.internal.util.concurrent.WeakConcurrentMap +org.mockito.internal.util.concurrent.DetachedThreadLocal +org.mockito.internal.util.concurrent.DetachedThreadLocal$1 +org.mockito.internal.util.concurrent.WeakConcurrentMap$WithInlinedExpunction +org.mockito.internal.util.concurrent.DetachedThreadLocal$2 +org.mockito.internal.util.concurrent.DetachedThreadLocal$Cleaner +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker$$Lambda$654/0x00007f42042076e0 +java.lang.ThreadLocal$SuppliedThreadLocal +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker$$Lambda$655/0x00007f4204207900 +org.mockito.internal.creation.bytebuddy.StackWalkerChecker +java.lang.StackWalker$Option +java.lang.invoke.LambdaForm$DMH/0x00007f4204205000 +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker$$Lambda$656/0x00007f4204207d88 +org.mockito.internal.creation.bytebuddy.ConstructionCallback +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker$$Lambda$657/0x00007f4204205a00 +org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator +net.bytebuddy.matcher.ElementMatcher +net.bytebuddy.TypeCache +net.bytebuddy.TypeCache$WithInlineExpunction +java.lang.instrument.ClassFileTransformer +org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator +net.bytebuddy.implementation.Implementation$Context$Factory +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler +net.bytebuddy.dynamic.scaffold.InstrumentedType$Prepareable +net.bytebuddy.implementation.Implementation +net.bytebuddy.asm.AsmVisitorWrapper +org.mockito.internal.creation.bytebuddy.MockMethodAdvice +java.lang.instrument.UnmodifiableClassException +net.bytebuddy.ByteBuddy +net.bytebuddy.NamingStrategy +net.bytebuddy.implementation.auxiliary.AuxiliaryType$NamingStrategy +net.bytebuddy.matcher.LatentMatcher +net.bytebuddy.utility.AsmClassWriter$Factory +net.bytebuddy.utility.AsmClassReader$Factory +net.bytebuddy.dynamic.VisibilityBridgeStrategy +net.bytebuddy.dynamic.scaffold.InstrumentedType$Factory +net.bytebuddy.implementation.attribute.AnnotationValueFilter$Factory +net.bytebuddy.NamingStrategy$Suffixing$BaseNameResolver +net.bytebuddy.dynamic.DynamicType$Builder +net.bytebuddy.description.NamedElement +net.bytebuddy.description.ModifierReviewable +net.bytebuddy.description.ModifierReviewable$OfByteCodeElement +net.bytebuddy.description.ModifierReviewable$OfAbstraction +net.bytebuddy.description.ModifierReviewable$OfEnumeration +net.bytebuddy.description.ModifierReviewable$ForTypeDefinition +net.bytebuddy.description.type.TypeDefinition +net.bytebuddy.matcher.FilterableList +net.bytebuddy.description.type.TypeList$Generic +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy +net.bytebuddy.description.NamedElement$WithRuntimeName +net.bytebuddy.description.annotation.AnnotationSource +net.bytebuddy.description.type.PackageDescription +net.bytebuddy.description.NamedElement$WithDescriptor +net.bytebuddy.description.DeclaredByType +net.bytebuddy.description.ByteCodeElement +net.bytebuddy.description.TypeVariableSource +net.bytebuddy.description.type.TypeDescription +net.bytebuddy.utility.privilege.GetSystemPropertyAction +net.bytebuddy.dynamic.scaffold.TypeValidation +net.bytebuddy.utility.GraalImageCode +net.bytebuddy.NamingStrategy$AbstractBase +net.bytebuddy.NamingStrategy$Suffixing +net.bytebuddy.NamingStrategy$SuffixingRandom +net.bytebuddy.NamingStrategy$Suffixing$BaseNameResolver$ForUnnamedType +net.bytebuddy.utility.RandomString +net.bytebuddy.implementation.auxiliary.AuxiliaryType$NamingStrategy$SuffixingRandom +net.bytebuddy.implementation.attribute.AnnotationValueFilter +net.bytebuddy.implementation.attribute.AnnotationValueFilter$Default +net.bytebuddy.implementation.attribute.AnnotationValueFilter$Default$1 +net.bytebuddy.implementation.attribute.AnnotationValueFilter$Default$2 +net.bytebuddy.implementation.attribute.AnnotationRetention +net.bytebuddy.implementation.Implementation$Context$Default$Factory +net.bytebuddy.implementation.MethodAccessorFactory +net.bytebuddy.implementation.Implementation$Context +net.bytebuddy.implementation.Implementation$Context$ExtractableView +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$AbstractBase +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default +net.bytebuddy.dynamic.scaffold.MethodGraph +net.bytebuddy.dynamic.scaffold.MethodGraph$Linked +net.bytebuddy.description.type.TypeDescription$Generic$Visitor +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Merger +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Harmonizer +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Harmonizer$ForJavaMethod +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Merger$Directional +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Reifying +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Reifying$1 +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Reifying$2 +net.bytebuddy.description.type.TypeDescription$Generic +net.bytebuddy.matcher.ElementMatchers +net.bytebuddy.matcher.ElementMatcher$Junction +net.bytebuddy.description.ModifierReviewable$ForMethodDescription +net.bytebuddy.description.DeclaredByType$WithMandatoryDeclaration +net.bytebuddy.description.NamedElement$WithGenericName +net.bytebuddy.description.ByteCodeElement$Member +net.bytebuddy.description.ByteCodeElement$TypeDependant +net.bytebuddy.description.method.MethodDescription +net.bytebuddy.description.method.MethodDescription$InDefinedShape +net.bytebuddy.description.ModifierReviewable$ForFieldDescription +net.bytebuddy.description.field.FieldDescription +net.bytebuddy.description.field.FieldDescription$InDefinedShape +net.bytebuddy.matcher.ElementMatcher$Junction$AbstractBase +net.bytebuddy.matcher.BooleanMatcher +net.bytebuddy.dynamic.scaffold.InstrumentedType$Factory$Default +net.bytebuddy.dynamic.scaffold.InstrumentedType$Factory$Default$1 +net.bytebuddy.dynamic.scaffold.InstrumentedType$Factory$Default$2 +net.bytebuddy.implementation.LoadedTypeInitializer +net.bytebuddy.implementation.bytecode.ByteCodeAppender +net.bytebuddy.dynamic.scaffold.TypeInitializer +net.bytebuddy.dynamic.scaffold.InstrumentedType +net.bytebuddy.dynamic.scaffold.InstrumentedType$WithFlexibleName +net.bytebuddy.dynamic.VisibilityBridgeStrategy$Default +net.bytebuddy.dynamic.VisibilityBridgeStrategy$Default$1 +net.bytebuddy.dynamic.VisibilityBridgeStrategy$Default$2 +net.bytebuddy.dynamic.VisibilityBridgeStrategy$Default$3 +net.bytebuddy.utility.AsmClassReader$Factory$Default +net.bytebuddy.utility.AsmClassReader$Factory$Default$1 +net.bytebuddy.utility.AsmClassReader$Factory$Default$2 +net.bytebuddy.utility.AsmClassReader$Factory$Default$3 +net.bytebuddy.utility.AsmClassReader$Factory$Default$4 +net.bytebuddy.utility.AsmClassReader$Factory$Default$5 +net.bytebuddy.utility.AsmClassReader +net.bytebuddy.utility.AsmClassWriter$Factory$Default +net.bytebuddy.utility.AsmClassWriter$Factory$Default$1 +net.bytebuddy.utility.AsmClassWriter$Factory$Default$2 +net.bytebuddy.utility.AsmClassWriter$Factory$Default$3 +net.bytebuddy.utility.AsmClassWriter$Factory$Default$4 +net.bytebuddy.utility.AsmClassWriter$Factory$Default$5 +net.bytebuddy.pool.TypePool +net.bytebuddy.jar.asm.ClassVisitor +net.bytebuddy.jar.asm.ClassWriter +net.bytebuddy.utility.AsmClassWriter$FrameComputingClassWriter +net.bytebuddy.utility.AsmClassWriter +net.bytebuddy.matcher.LatentMatcher$Resolved +net.bytebuddy.matcher.ModifierMatcher$Mode +net.bytebuddy.matcher.ElementMatcher$Junction$ForNonNullValues +net.bytebuddy.matcher.ModifierMatcher +net.bytebuddy.matcher.NameMatcher +net.bytebuddy.matcher.StringMatcher +net.bytebuddy.matcher.StringMatcher$Mode +net.bytebuddy.matcher.StringMatcher$Mode$1 +net.bytebuddy.matcher.StringMatcher$Mode$2 +net.bytebuddy.matcher.StringMatcher$Mode$3 +net.bytebuddy.matcher.StringMatcher$Mode$4 +net.bytebuddy.matcher.StringMatcher$Mode$5 +net.bytebuddy.matcher.StringMatcher$Mode$6 +net.bytebuddy.matcher.StringMatcher$Mode$7 +net.bytebuddy.matcher.StringMatcher$Mode$8 +net.bytebuddy.matcher.StringMatcher$Mode$9 +net.bytebuddy.matcher.MethodParametersMatcher +net.bytebuddy.matcher.CollectionSizeMatcher +net.bytebuddy.matcher.ElementMatcher$Junction$Conjunction +net.bytebuddy.description.ModifierReviewable$ForParameterDescription +net.bytebuddy.description.ModifierReviewable$AbstractBase +net.bytebuddy.description.TypeVariableSource$AbstractBase +net.bytebuddy.description.type.TypeDescription$AbstractBase +net.bytebuddy.description.type.TypeDescription$ForLoadedType +java.lang.ClassFormatError +java.lang.reflect.GenericSignatureFormatError +net.bytebuddy.description.annotation.AnnotationList +net.bytebuddy.description.field.FieldList +net.bytebuddy.description.type.RecordComponentList +net.bytebuddy.matcher.FilterableList$Empty +net.bytebuddy.description.type.RecordComponentList$Empty +net.bytebuddy.matcher.FilterableList$AbstractBase +net.bytebuddy.description.type.RecordComponentList$AbstractBase +net.bytebuddy.description.type.RecordComponentList$ForLoadedRecordComponents +net.bytebuddy.description.method.MethodList +net.bytebuddy.description.type.TypeList +net.bytebuddy.description.type.TypeList$Empty +net.bytebuddy.description.type.TypeList$AbstractBase +net.bytebuddy.description.type.TypeList$ForLoadedTypes +net.bytebuddy.description.type.TypeDescription$ForLoadedType$Dispatcher +net.bytebuddy.utility.dispatcher.JavaDispatcher +net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher +net.bytebuddy.utility.dispatcher.JavaDispatcher$DynamicClassLoader$Resolver$CreationAction +net.bytebuddy.utility.dispatcher.JavaDispatcher$DynamicClassLoader$Resolver +net.bytebuddy.utility.dispatcher.JavaDispatcher$DynamicClassLoader$Resolver$ForModuleSystem +net.bytebuddy.utility.dispatcher.JavaDispatcher$InvokerCreationAction +net.bytebuddy.utility.dispatcher.JavaDispatcher$DynamicClassLoader +net.bytebuddy.utility.Invoker +net.bytebuddy.jar.asm.ClassTooLargeException +net.bytebuddy.jar.asm.FieldVisitor +net.bytebuddy.jar.asm.FieldWriter +net.bytebuddy.jar.asm.AnnotationVisitor +net.bytebuddy.jar.asm.AnnotationWriter +net.bytebuddy.jar.asm.MethodVisitor +net.bytebuddy.jar.asm.MethodWriter +net.bytebuddy.jar.asm.ModuleVisitor +net.bytebuddy.jar.asm.ModuleWriter +net.bytebuddy.jar.asm.RecordComponentVisitor +net.bytebuddy.jar.asm.RecordComponentWriter +java.lang.TypeNotPresentException +net.bytebuddy.jar.asm.SymbolTable +net.bytebuddy.jar.asm.Symbol +net.bytebuddy.jar.asm.SymbolTable$Entry +net.bytebuddy.jar.asm.ByteVector +net.bytebuddy.jar.asm.Type +net.bytebuddy.utility.MethodComparator +net.bytebuddy.jar.asm.Frame +net.bytebuddy.jar.asm.CurrentFrame +net.bytebuddy.jar.asm.MethodTooLargeException +net.bytebuddy.jar.asm.Handler +net.bytebuddy.jar.asm.Attribute +net.bytebuddy.utility.Invoker$Dispatcher +net.bytebuddy.utility.dispatcher.JavaDispatcher$Proxied +net.bytebuddy.utility.dispatcher.JavaDispatcher$Defaults +jdk.proxy2.$Proxy23 +jdk.proxy2.$Proxy24 +net.bytebuddy.utility.dispatcher.JavaDispatcher$Instance +net.bytebuddy.utility.dispatcher.JavaDispatcher$Container +net.bytebuddy.utility.dispatcher.JavaDispatcher$IsStatic +net.bytebuddy.utility.dispatcher.JavaDispatcher$IsConstructor +net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher$ForNonStaticMethod +net.bytebuddy.utility.nullability.MaybeNull +jdk.proxy2.$Proxy25 +net.bytebuddy.utility.dispatcher.JavaDispatcher$ProxiedInvocationHandler +net.bytebuddy.description.type.$Proxy26 +net.bytebuddy.dynamic.TargetType +net.bytebuddy.matcher.EqualityMatcher +net.bytebuddy.matcher.ErasureMatcher +net.bytebuddy.matcher.MethodReturnTypeMatcher +net.bytebuddy.matcher.DeclaringTypeMatcher +net.bytebuddy.matcher.ElementMatcher$Junction$Disjunction +net.bytebuddy.implementation.Implementation$Context$Disabled$Factory +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$ForDeclaredMethods +net.bytebuddy.matcher.MethodSortMatcher$Sort +net.bytebuddy.matcher.MethodSortMatcher$Sort$1 +net.bytebuddy.matcher.MethodSortMatcher$Sort$2 +net.bytebuddy.matcher.MethodSortMatcher$Sort$3 +net.bytebuddy.matcher.MethodSortMatcher$Sort$4 +net.bytebuddy.matcher.MethodSortMatcher$Sort$5 +net.bytebuddy.matcher.MethodSortMatcher +net.bytebuddy.matcher.NegatingMatcher +org.mockito.internal.util.concurrent.WeakConcurrentSet +org.mockito.internal.util.concurrent.WeakConcurrentSet$Cleaner +org.mockito.internal.creation.bytebuddy.SubclassBytecodeGenerator +net.bytebuddy.implementation.attribute.MethodAttributeAppender$Factory +org.mockito.internal.creation.bytebuddy.ModuleHandler +org.mockito.internal.creation.bytebuddy.ModuleHandler$ModuleSystemFound +org.mockito.internal.creation.bytebuddy.ModuleHandler$1 +org.mockito.internal.creation.bytebuddy.ModuleHandler$NoModuleSystemFound +org.mockito.internal.creation.bytebuddy.ModuleHandler$2 +org.mockito.internal.creation.bytebuddy.ModuleHandler$3 +java.lang.instrument.ClassDefinition +org.mockito.internal.creation.bytebuddy.ModuleHandler$MockitoMockClassLoader +jdk.internal.vm.annotation.ForceInline +com.sun.proxy.jdk.proxy1.$Proxy27 +net.bytebuddy.implementation.Implementation$Composable +net.bytebuddy.implementation.MethodDelegation +net.bytebuddy.implementation.bind.MethodDelegationBinder$TerminationHandler +net.bytebuddy.implementation.bind.MethodDelegationBinder$Record +net.bytebuddy.implementation.bind.MethodDelegationBinder$AmbiguityResolver +net.bytebuddy.implementation.MethodDelegation$WithCustomProperties +net.bytebuddy.implementation.bind.MethodDelegationBinder$BindingResolver +net.bytebuddy.implementation.MethodDelegation$ImplementationDelegate +net.bytebuddy.dynamic.scaffold.FieldLocator$Factory +net.bytebuddy.implementation.bind.MethodDelegationBinder$AmbiguityResolver$Compound +net.bytebuddy.implementation.bind.annotation.BindingPriority$Resolver +net.bytebuddy.implementation.bind.annotation.BindingPriority +net.bytebuddy.description.method.MethodList$AbstractBase +net.bytebuddy.description.method.MethodList$ForLoadedMethods +net.bytebuddy.description.method.MethodDescription$AbstractBase +net.bytebuddy.description.method.MethodDescription$InDefinedShape$AbstractBase +net.bytebuddy.description.method.MethodDescription$InDefinedShape$AbstractBase$ForLoadedExecutable +net.bytebuddy.description.method.ParameterDescription$ForLoadedParameter$ParameterAnnotationSource +net.bytebuddy.description.method.MethodDescription$ForLoadedConstructor +net.bytebuddy.description.method.MethodDescription$ForLoadedMethod +net.bytebuddy.utility.ConstructorComparator +net.bytebuddy.description.ByteCodeElement$Token +net.bytebuddy.description.method.MethodDescription$InDefinedShape$AbstractBase$Executable +net.bytebuddy.description.method.$Proxy28 +net.bytebuddy.implementation.bind.DeclaringTypeResolver +net.bytebuddy.implementation.bind.ArgumentTypeResolver +net.bytebuddy.implementation.bind.MethodNameEqualityResolver +net.bytebuddy.implementation.bind.ParameterLengthResolver +net.bytebuddy.implementation.bind.MethodDelegationBinder$AmbiguityResolver$NoOp +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$ParameterBinder +net.bytebuddy.implementation.bind.annotation.Argument$Binder +net.bytebuddy.implementation.bytecode.StackManipulation +net.bytebuddy.implementation.bind.MethodDelegationBinder$ParameterBinding +net.bytebuddy.implementation.bind.annotation.Argument +net.bytebuddy.implementation.bind.annotation.Argument$BindingMechanic +net.bytebuddy.description.method.MethodList$Explicit +net.bytebuddy.implementation.bind.annotation.AllArguments$Binder +net.bytebuddy.implementation.bind.annotation.AllArguments +net.bytebuddy.implementation.bind.annotation.AllArguments$Assignment +net.bytebuddy.implementation.bind.annotation.Origin$Binder +net.bytebuddy.implementation.bind.annotation.Origin +net.bytebuddy.implementation.bind.annotation.This$Binder +net.bytebuddy.implementation.bind.annotation.This +net.bytebuddy.implementation.bind.annotation.Super$Binder +net.bytebuddy.implementation.bind.annotation.Super +net.bytebuddy.implementation.bind.annotation.Super$Instantiation +net.bytebuddy.implementation.bind.annotation.Default$Binder +net.bytebuddy.implementation.bind.annotation.Default +net.bytebuddy.implementation.bind.annotation.SuperCall$Binder +net.bytebuddy.implementation.bind.annotation.SuperCall +net.bytebuddy.implementation.bind.annotation.SuperCallHandle$Binder +net.bytebuddy.implementation.bind.annotation.SuperCallHandle +net.bytebuddy.implementation.bind.annotation.DefaultCall$Binder +net.bytebuddy.implementation.bind.annotation.DefaultCall$Binder$DefaultMethodLocator +net.bytebuddy.implementation.bind.annotation.DefaultCall +net.bytebuddy.implementation.bind.annotation.DefaultCallHandle$Binder +net.bytebuddy.implementation.bind.annotation.DefaultCallHandle$Binder$DefaultMethodLocator +net.bytebuddy.implementation.bind.annotation.DefaultCallHandle +net.bytebuddy.implementation.bind.annotation.SuperMethod$Binder +net.bytebuddy.implementation.bind.annotation.SuperMethod +net.bytebuddy.implementation.bind.annotation.SuperMethodHandle$Binder +net.bytebuddy.implementation.bind.annotation.SuperMethodHandle +net.bytebuddy.implementation.bind.annotation.Handle$Binder +net.bytebuddy.utility.ConstantValue +net.bytebuddy.utility.JavaConstant +net.bytebuddy.implementation.bind.annotation.Handle +net.bytebuddy.utility.JavaConstant$MethodHandle$HandleType +net.bytebuddy.implementation.bind.annotation.DynamicConstant$Binder +net.bytebuddy.implementation.bind.annotation.DynamicConstant +net.bytebuddy.implementation.bind.annotation.DefaultMethod$Binder +net.bytebuddy.implementation.bind.annotation.DefaultMethod$Binder$MethodLocator +net.bytebuddy.implementation.bind.annotation.DefaultMethod +net.bytebuddy.implementation.bind.annotation.DefaultMethodHandle$Binder +net.bytebuddy.implementation.bind.annotation.DefaultMethodHandle$Binder$MethodLocator +net.bytebuddy.implementation.bind.annotation.DefaultMethodHandle +net.bytebuddy.implementation.bind.annotation.FieldValue$Binder +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$ParameterBinder$ForFieldBinding +net.bytebuddy.implementation.bind.annotation.FieldValue$Binder$Delegate +net.bytebuddy.dynamic.scaffold.FieldLocator +net.bytebuddy.dynamic.scaffold.FieldLocator$AbstractBase +net.bytebuddy.dynamic.scaffold.FieldLocator$ForClassHierarchy +net.bytebuddy.dynamic.scaffold.FieldLocator$ForExactType +net.bytebuddy.implementation.bind.annotation.FieldValue +net.bytebuddy.implementation.bind.annotation.FieldGetterHandle$Binder +net.bytebuddy.implementation.bind.annotation.FieldGetterHandle$Binder$Delegate +net.bytebuddy.implementation.bind.annotation.FieldGetterHandle +net.bytebuddy.implementation.bind.annotation.FieldSetterHandle$Binder +net.bytebuddy.implementation.bind.annotation.FieldSetterHandle$Binder$Delegate +net.bytebuddy.implementation.bind.annotation.FieldSetterHandle +net.bytebuddy.implementation.bind.annotation.StubValue$Binder +net.bytebuddy.implementation.bind.annotation.Empty$Binder +net.bytebuddy.implementation.bind.MethodDelegationBinder$BindingResolver$Default +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$Identifier +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$ParameterBinder$ForFixedValue +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$ParameterBinder$ForFixedValue$OfConstant +net.bytebuddy.utility.CompoundList +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$ForReadObject +org.mockito.internal.creation.bytebuddy.access.MockAccess +net.bytebuddy.implementation.bind.MethodDelegationBinder +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$DelegationProcessor +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$DelegationProcessor$Handler +net.bytebuddy.implementation.bind.annotation.StubValue +net.bytebuddy.implementation.bind.annotation.Empty +net.bytebuddy.implementation.MethodDelegation$ImplementationDelegate$ForStaticMethod +net.bytebuddy.implementation.MethodDelegation$ImplementationDelegate$Compiled +net.bytebuddy.implementation.bind.annotation.IgnoreForBinding$Verifier +net.bytebuddy.description.annotation.AnnotationList$AbstractBase +net.bytebuddy.description.annotation.AnnotationList$ForLoadedAnnotations +net.bytebuddy.description.annotation.AnnotationDescription +net.bytebuddy.implementation.bind.annotation.IgnoreForBinding +net.bytebuddy.description.method.ParameterList +net.bytebuddy.description.method.ParameterList$AbstractBase +net.bytebuddy.description.method.ParameterList$ForLoadedExecutable +net.bytebuddy.description.method.ParameterList$ForLoadedExecutable$OfMethod +net.bytebuddy.description.method.ParameterList$ForLoadedExecutable$OfLegacyVmMethod +net.bytebuddy.description.method.ParameterList$ForLoadedExecutable$OfConstructor +net.bytebuddy.description.method.ParameterList$ForLoadedExecutable$OfLegacyVmConstructor +net.bytebuddy.description.method.ParameterList$ForLoadedExecutable$Executable +jdk.proxy2.$Proxy29 +net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher$ForInstanceCheck +jdk.internal.reflect.GeneratedConstructorAccessor10 +net.bytebuddy.description.method.$Proxy30 +net.bytebuddy.description.NamedElement$WithOptionalName +net.bytebuddy.description.method.ParameterDescription +net.bytebuddy.description.method.ParameterDescription$InDefinedShape +net.bytebuddy.description.method.ParameterDescription$AbstractBase +net.bytebuddy.description.method.ParameterDescription$InDefinedShape$AbstractBase +net.bytebuddy.description.method.ParameterDescription$ForLoadedParameter +net.bytebuddy.description.method.ParameterDescription$ForLoadedParameter$OfMethod +net.bytebuddy.description.method.ParameterDescription$ForLoadedParameter$Parameter +net.bytebuddy.description.method.$Proxy31 +net.bytebuddy.implementation.bind.annotation.RuntimeType$Verifier +org.mockito.internal.creation.bytebuddy.$Proxy32 +jdk.proxy2.$Proxy33 +net.bytebuddy.implementation.bind.annotation.Argument$BindingMechanic$1 +net.bytebuddy.implementation.bind.annotation.Argument$BindingMechanic$2 +jdk.proxy2.$Proxy34 +net.bytebuddy.implementation.bind.annotation.RuntimeType +net.bytebuddy.description.annotation.AnnotationDescription$Loadable +net.bytebuddy.description.annotation.AnnotationDescription$AbstractBase +net.bytebuddy.description.annotation.AnnotationDescription$ForLoadedAnnotation +net.bytebuddy.description.annotation.AnnotationValue +net.bytebuddy.description.enumeration.EnumerationDescription +net.bytebuddy.description.type.TypeDefinition$Sort +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader +net.bytebuddy.description.type.TypeDefinition$Sort$AnnotatedType +net.bytebuddy.description.type.$Proxy35 +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$NoOp +net.bytebuddy.description.type.TypeDescription$Generic$AbstractBase +net.bytebuddy.description.type.TypeDescription$Generic$OfNonGenericType +net.bytebuddy.description.type.TypeDescription$Generic$OfNonGenericType$ForLoadedType +net.bytebuddy.implementation.bytecode.assign.Assigner$Typing +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$DelegationProcessor$Handler$Unbound +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$DelegationProcessor$Handler$Bound +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$Record +net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodBinding +net.bytebuddy.implementation.bind.MethodDelegationBinder$TerminationHandler$Default +net.bytebuddy.implementation.bind.MethodDelegationBinder$TerminationHandler$Default$1 +net.bytebuddy.implementation.bind.MethodDelegationBinder$TerminationHandler$Default$2 +net.bytebuddy.implementation.bytecode.assign.Assigner +net.bytebuddy.implementation.bytecode.assign.primitive.VoidAwareAssigner +net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveTypeAwareAssigner +net.bytebuddy.implementation.bytecode.assign.reference.ReferenceTypeAwareAssigner +net.bytebuddy.implementation.bytecode.StackManipulation$Trivial +net.bytebuddy.implementation.bytecode.StackManipulation$Illegal +net.bytebuddy.implementation.bytecode.assign.reference.GenericTypeAwareAssigner +org.mockito.internal.creation.bytebuddy.access.MockMethodInterceptor$DispatcherDefaultingToRealMethod +org.mockito.internal.invocation.RealMethod +org.mockito.internal.creation.bytebuddy.access.MockMethodInterceptor +jdk.proxy2.$Proxy36 +jdk.proxy2.$Proxy37 +jdk.proxy2.$Proxy38 +jdk.proxy2.$Proxy39 +jdk.proxy2.$Proxy40 +jdk.proxy2.$Proxy41 +jdk.proxy2.$Proxy42 +jdk.internal.reflect.GeneratedMethodAccessor1 +org.mockito.internal.creation.bytebuddy.access.MockMethodInterceptor$ForHashCode +org.mockito.internal.creation.bytebuddy.access.MockMethodInterceptor$ForEquals +org.mockito.internal.creation.bytebuddy.access.MockMethodInterceptor$ForWriteReplace +net.bytebuddy.TypeCache$Sort +net.bytebuddy.TypeCache$Sort$1 +net.bytebuddy.TypeCache$Sort$2 +net.bytebuddy.TypeCache$Sort$3 +org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator$TypeCachingLock +net.bytebuddy.asm.AsmVisitorWrapper$ForDeclaredMethods +net.bytebuddy.asm.AsmVisitorWrapper$ForDeclaredMethods$DispatchingVisitor +net.bytebuddy.matcher.CollectionOneToOneMatcher +net.bytebuddy.matcher.CollectionErasureMatcher +net.bytebuddy.matcher.MethodParameterTypesMatcher +net.bytebuddy.matcher.AnnotationTypeMatcher +net.bytebuddy.matcher.DeclaringAnnotationMatcher +net.bytebuddy.matcher.CollectionItemMatcher +net.bytebuddy.asm.AsmVisitorWrapper$ForDeclaredMethods$MethodVisitorWrapper +net.bytebuddy.asm.Advice +net.bytebuddy.asm.Advice$ExceptionHandler +net.bytebuddy.asm.Advice$Dispatcher +net.bytebuddy.asm.Advice$Dispatcher$Unresolved +net.bytebuddy.dynamic.ClassFileLocator +net.bytebuddy.asm.Advice$Delegator$Factory +net.bytebuddy.asm.Advice$PostProcessor$Factory +net.bytebuddy.utility.visitor.ExceptionTableSensitiveMethodVisitor +net.bytebuddy.utility.visitor.LineNumberPrependingMethodVisitor +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$Relocation +net.bytebuddy.asm.Advice$AdviceVisitor +net.bytebuddy.asm.Advice$AdviceVisitor$WithoutExitAdvice +net.bytebuddy.asm.Advice$AdviceVisitor$WithExitAdvice +net.bytebuddy.asm.Advice$AdviceVisitor$WithExitAdvice$WithoutExceptionHandling +net.bytebuddy.asm.Advice$AdviceVisitor$WithExitAdvice$WithExceptionHandling +net.bytebuddy.asm.Advice$OnMethodEnter +net.bytebuddy.asm.Advice$OnMethodExit +net.bytebuddy.asm.Advice$WithCustomMapping +net.bytebuddy.asm.Advice$OffsetMapping$Factory +net.bytebuddy.asm.Advice$BootstrapArgumentResolver$Factory +net.bytebuddy.asm.Advice$PostProcessor +net.bytebuddy.asm.Advice$PostProcessor$NoOp +net.bytebuddy.asm.Advice$Delegator$ForRegularInvocation$Factory +net.bytebuddy.asm.Advice$Delegator +net.bytebuddy.asm.Advice$OffsetMapping$ForStackManipulation$Factory +net.bytebuddy.asm.Advice$OffsetMapping +net.bytebuddy.utility.ConstantValue$Simple +net.bytebuddy.utility.JavaConstant$Simple +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher +jdk.proxy2.$Proxy43 +net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher$ForContainerCreation +net.bytebuddy.utility.$Proxy44 +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher$OfClassDesc +jdk.proxy2.$Proxy45 +net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher$ForStaticMethod +jdk.proxy2.$Proxy46 +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher$OfMethodTypeDesc +jdk.proxy2.$Proxy47 +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher$OfMethodHandleDesc +jdk.proxy2.$Proxy48 +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher$OfDirectMethodHandleDesc +jdk.proxy2.$Proxy49 +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher$OfDirectMethodHandleDesc$ForKind +jdk.proxy2.$Proxy50 +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher$OfDynamicConstantDesc +jdk.proxy2.$Proxy51 +net.bytebuddy.utility.JavaConstant$Simple$OfTrivialValue +net.bytebuddy.utility.JavaConstant$Simple$OfTrivialValue$ForString +net.bytebuddy.implementation.bytecode.StackManipulation$AbstractBase +net.bytebuddy.implementation.bytecode.constant.TextConstant +net.bytebuddy.dynamic.ClassFileLocator$ForClassLoader +net.bytebuddy.dynamic.ClassFileLocator$Resolution +net.bytebuddy.dynamic.ClassFileLocator$ForClassLoader$BootLoaderProxyCreationAction +net.bytebuddy.dynamic.loading.ClassLoadingStrategy +net.bytebuddy.asm.Advice$Dispatcher$Resolved +net.bytebuddy.asm.Advice$Dispatcher$Resolved$ForMethodEnter +net.bytebuddy.asm.Advice$Dispatcher$Resolved$ForMethodExit +net.bytebuddy.asm.Advice$Dispatcher$Bound +net.bytebuddy.asm.Advice$Dispatcher$Inactive +net.bytebuddy.asm.Advice$NoExceptionHandler +jdk.proxy2.$Proxy52 +net.bytebuddy.description.annotation.AnnotationValue$AbstractBase +net.bytebuddy.description.annotation.AnnotationValue$ForConstant +net.bytebuddy.description.annotation.AnnotationValue$Loaded +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$1 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$2 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$3 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$4 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$5 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$6 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$7 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$8 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$9 +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithEagerNavigation +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithEagerNavigation$OfAnnotatedElement +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$ForLoadedReturnType +net.bytebuddy.asm.Advice$Dispatcher$Inlining +net.bytebuddy.asm.Advice$Return +jdk.proxy2.$Proxy53 +net.bytebuddy.asm.Advice$Enter +jdk.proxy2.$Proxy54 +net.bytebuddy.asm.Advice$Local +net.bytebuddy.asm.Advice$OnNonDefaultValue +jdk.proxy2.$Proxy55 +net.bytebuddy.asm.Advice$This +jdk.proxy2.$Proxy56 +net.bytebuddy.asm.Advice$Origin +jdk.proxy2.$Proxy57 +net.bytebuddy.asm.Advice$AllArguments +jdk.proxy2.$Proxy58 +net.bytebuddy.dynamic.ClassFileLocator$Resolution$Explicit +net.bytebuddy.utility.StreamDrainer +net.bytebuddy.utility.OpenedClassReader +net.bytebuddy.utility.AsmClassReader$ForAsm +net.bytebuddy.jar.asm.ClassReader +net.bytebuddy.asm.Advice$Dispatcher$Resolved$AbstractBase +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$ForMethodEnter +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$ForMethodEnter$WithRetainedEnterType +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$ForMethodEnter$WithDiscardedEnterType +net.bytebuddy.asm.Advice$ArgumentHandler +net.bytebuddy.asm.Advice$Dispatcher$Inlining$CodeTranslationVisitor +net.bytebuddy.asm.Advice$OffsetMapping$ForArgument$Unresolved$Factory +net.bytebuddy.asm.Advice$Argument +net.bytebuddy.asm.Advice$OffsetMapping$ForAllArguments$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForThisReference$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForField$Unresolved$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForField +net.bytebuddy.asm.Advice$OffsetMapping$ForField$Unresolved +net.bytebuddy.asm.Advice$OffsetMapping$ForField$Unresolved$WithImplicitType +net.bytebuddy.asm.Advice$OffsetMapping$ForField$Unresolved$WithExplicitType +net.bytebuddy.asm.Advice$OffsetMapping$ForFieldHandle$Unresolved$ReaderFactory +net.bytebuddy.asm.Advice$OffsetMapping$ForFieldHandle +net.bytebuddy.asm.Advice$OffsetMapping$ForFieldHandle$Unresolved +net.bytebuddy.asm.Advice$OffsetMapping$ForFieldHandle$Unresolved$WithImplicitType +net.bytebuddy.asm.Advice$OffsetMapping$ForFieldHandle$Unresolved$WithExplicitType +net.bytebuddy.asm.Advice$FieldGetterHandle +net.bytebuddy.asm.Advice$OffsetMapping$ForFieldHandle$Unresolved$WriterFactory +net.bytebuddy.asm.Advice$FieldSetterHandle +net.bytebuddy.asm.Advice$OffsetMapping$ForOrigin$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForSelfCallHandle$Factory +net.bytebuddy.asm.Advice$SelfCallHandle +net.bytebuddy.asm.Advice$OffsetMapping$ForHandle$Factory +net.bytebuddy.asm.Advice$Handle +net.bytebuddy.asm.Advice$OffsetMapping$ForDynamicConstant$Factory +net.bytebuddy.asm.Advice$DynamicConstant +net.bytebuddy.asm.Advice$OffsetMapping$ForUnusedValue$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForStubValue +net.bytebuddy.asm.Advice$OffsetMapping$Target +net.bytebuddy.asm.Advice$OffsetMapping$ForThrowable$Factory +net.bytebuddy.asm.Advice$Thrown +net.bytebuddy.asm.Advice$OffsetMapping$ForExitValue$Factory +net.bytebuddy.asm.Advice$Exit +net.bytebuddy.asm.Advice$OffsetMapping$Factory$Illegal +net.bytebuddy.asm.Advice$OffsetMapping$ForLocalValue$Factory +net.bytebuddy.description.annotation.AnnotationValue$ForTypeDescription +net.bytebuddy.description.annotation.AnnotationValue$ForMismatchedType +net.bytebuddy.asm.Advice$OffsetMapping$Factory$AdviceType +net.bytebuddy.asm.Advice$FieldValue +net.bytebuddy.asm.Advice$Unused +net.bytebuddy.asm.Advice$StubValue +net.bytebuddy.asm.Advice$OffsetMapping$ForStackManipulation +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$OfMethodParameter +net.bytebuddy.description.type.TypeList$Generic$AbstractBase +net.bytebuddy.description.type.TypeList$Generic$Explicit +net.bytebuddy.description.type.TypeList$Explicit +net.bytebuddy.implementation.bytecode.StackSize +net.bytebuddy.asm.Advice$OffsetMapping$ForThisReference +net.bytebuddy.asm.Advice$OffsetMapping$Target$ForDefaultValue +net.bytebuddy.asm.Advice$OffsetMapping$Target$ForDefaultValue$ReadOnly +net.bytebuddy.asm.Advice$OffsetMapping$Target$ForDefaultValue$ReadWrite +net.bytebuddy.description.enumeration.EnumerationDescription$AbstractBase +net.bytebuddy.description.enumeration.EnumerationDescription$ForLoadedEnumeration +net.bytebuddy.description.annotation.AnnotationValue$ForEnumerationDescription +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedMethod +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedMethod$1 +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedMethod$2 +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedMethod$3 +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedMethod$4 +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedMethod$5 +sun.reflect.generics.scope.MethodScope +sun.reflect.generics.repository.ConstructorRepository +sun.reflect.generics.repository.MethodRepository +sun.reflect.generics.tree.ArrayTypeSignature +sun.reflect.generics.tree.BottomSignature +sun.reflect.generics.tree.Wildcard +sun.reflect.generics.tree.MethodTypeSignature +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedExecutableParameterType +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedExecutableParameterType$Dispatcher +jdk.internal.reflect.GeneratedMethodAccessor2 +net.bytebuddy.description.type.$Proxy59 +net.bytebuddy.asm.Advice$OffsetMapping$ForAllArguments +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$Chained +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$ForComponentType +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$ForComponentType$AnnotatedParameterizedType +java.lang.reflect.AnnotatedArrayType +net.bytebuddy.description.type.$Proxy60 +net.bytebuddy.asm.Advice$Dispatcher$SuppressionHandler +net.bytebuddy.asm.Advice$Dispatcher$SuppressionHandler$Suppressing +net.bytebuddy.asm.Advice$Dispatcher$SuppressionHandler$Bound +net.bytebuddy.asm.Advice$Dispatcher$SuppressionHandler$NoOp +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForType +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$Bound +net.bytebuddy.asm.Advice$OnDefaultValue +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$1 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$2 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$3 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$4 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$5 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$6 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$7 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$8 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$9 +java.lang.reflect.WildcardType +sun.reflect.generics.reflectiveObjects.WildcardTypeImpl +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedMethodReturnType +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedMethodReturnType$Dispatcher +net.bytebuddy.description.type.$Proxy61 +net.bytebuddy.description.type.TypeDescription$Generic$OfParameterizedType +net.bytebuddy.description.type.TypeDescription$Generic$OfParameterizedType$ForLoadedType +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$OfNonDefault +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$ForMethodExit +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$ForMethodExit$WithoutExceptionHandler +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$ForMethodExit$WithExceptionHandler +net.bytebuddy.asm.Advice$OffsetMapping$ForEnterValue$Factory +sun.reflect.generics.tree.VoidDescriptor +net.bytebuddy.asm.Advice$OffsetMapping$ForReturnValue$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForReturnValue +net.bytebuddy.asm.Advice$OffsetMapping$ForEnterValue +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$Disabled +net.bytebuddy.asm.Advice$ExceptionHandler$Default +net.bytebuddy.asm.Advice$ExceptionHandler$Default$1 +net.bytebuddy.asm.Advice$ExceptionHandler$Default$2 +net.bytebuddy.asm.Advice$ExceptionHandler$Default$3 +net.bytebuddy.implementation.SuperMethodCall +net.bytebuddy.asm.AsmVisitorWrapper$ForDeclaredMethods$Entry +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$ForStatic +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedType +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$ConstructorShortcut +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$ConstructorShortcut$1 +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$ForHashCode +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$ForEquals +jdk.proxy2.$Proxy62 +net.bytebuddy.asm.Advice$OffsetMapping$ForArgument +net.bytebuddy.asm.Advice$OffsetMapping$ForArgument$Unresolved +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$SelfCallInfo +org.mockito.internal.util.reflection.ModuleMemberAccessor +org.mockito.internal.util.reflection.InstrumentationMemberAccessor +net.bytebuddy.dynamic.loading.InjectionClassLoader +net.bytebuddy.dynamic.loading.ByteArrayClassLoader +net.bytebuddy.implementation.MethodCall +net.bytebuddy.implementation.MethodCall$WithoutSpecifiedTarget +net.bytebuddy.dynamic.loading.ClassFilePostProcessor +net.bytebuddy.dynamic.loading.PackageDefinitionStrategy +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$PackageLookupStrategy$CreationAction +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$PackageLookupStrategy +net.bytebuddy.utility.JavaModule +net.bytebuddy.utility.JavaModule$Resolver +net.bytebuddy.utility.$Proxy63 +net.bytebuddy.utility.JavaModule$Module +net.bytebuddy.utility.$Proxy64 +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$PackageLookupStrategy$ForJava9CapableVm +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$SynchronizationStrategy$CreationAction +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$SynchronizationStrategy$Initializable +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$SynchronizationStrategy +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$SynchronizationStrategy$ForJava8CapableVm +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$PersistenceHandler +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$PersistenceHandler$1 +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$PersistenceHandler$2 +net.bytebuddy.dynamic.loading.PackageDefinitionStrategy$Trivial +net.bytebuddy.dynamic.loading.PackageDefinitionStrategy$Definition +net.bytebuddy.dynamic.loading.ClassFilePostProcessor$NoOp +org.mockito.internal.util.reflection.InstrumentationMemberAccessor$Dispatcher +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy$Default +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy$Default$1 +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy$Default$2 +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy$Default$3 +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy$Default$4 +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy$Default$5 +net.bytebuddy.dynamic.scaffold.MethodRegistry$Handler +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$UsingTypeWriter +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter +net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder +net.bytebuddy.dynamic.TypeResolutionStrategy +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ImplementationDefinition +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$TypeVariableDefinition +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ExceptionDefinition +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition$Optional +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition$Valuable +net.bytebuddy.implementation.attribute.TypeAttributeAppender +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Delegator +net.bytebuddy.dynamic.DynamicType$Builder$InnerTypeDefinition +net.bytebuddy.dynamic.DynamicType$Builder$InnerTypeDefinition$ForType +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$InnerTypeDefinitionForTypeAdapter +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$InnerTypeDefinitionForMethodAdapter +net.bytebuddy.dynamic.DynamicType$Builder$RecordComponentDefinition +net.bytebuddy.dynamic.DynamicType$Builder$RecordComponentDefinition$Optional +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ImplementationDefinition$Optional +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Simple +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Initial +net.bytebuddy.dynamic.DynamicType$Builder$TypeVariableDefinition +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition$Optional$Valuable +net.bytebuddy.dynamic.scaffold.RecordComponentRegistry +net.bytebuddy.dynamic.scaffold.MethodRegistry +net.bytebuddy.dynamic.scaffold.FieldRegistry +net.bytebuddy.implementation.Implementation$Target$Factory +net.bytebuddy.dynamic.scaffold.TypeWriter$RecordComponentPool +net.bytebuddy.dynamic.scaffold.TypeWriter$FieldPool +net.bytebuddy.description.modifier.ModifierContributor +net.bytebuddy.description.modifier.ModifierContributor$ForType +net.bytebuddy.description.modifier.ModifierContributor$ForMethod +net.bytebuddy.description.modifier.ModifierContributor$ForField +net.bytebuddy.description.modifier.Visibility +net.bytebuddy.description.modifier.TypeManifestation +net.bytebuddy.description.modifier.ModifierContributor$Resolver +net.bytebuddy.description.type.TypeDescription$AbstractBase$OfSimpleType +net.bytebuddy.dynamic.scaffold.InstrumentedType$Default +net.bytebuddy.dynamic.scaffold.TypeInitializer$None +net.bytebuddy.implementation.LoadedTypeInitializer$NoOp +net.bytebuddy.description.type.TypeDescription$LazyProxy +net.bytebuddy.description.modifier.Ownership +net.bytebuddy.description.modifier.ModifierContributor$ForParameter +net.bytebuddy.description.modifier.SyntheticState +net.bytebuddy.description.modifier.EnumerationState +net.bytebuddy.description.TypeVariableSource$Visitor +java.util.LinkedList$ListItr +jdk.proxy2.$Proxy65 +net.bytebuddy.description.type.TypeList$Generic$ForLoadedTypes +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Substitutor +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Substitutor$ForDetachment +net.bytebuddy.dynamic.scaffold.FieldRegistry$Default +net.bytebuddy.dynamic.scaffold.FieldRegistry$Compiled +net.bytebuddy.dynamic.scaffold.MethodRegistry$Default +net.bytebuddy.dynamic.scaffold.MethodRegistry$Prepared +net.bytebuddy.dynamic.scaffold.RecordComponentRegistry$Default +net.bytebuddy.dynamic.scaffold.RecordComponentRegistry$Compiled +net.bytebuddy.implementation.attribute.TypeAttributeAppender$ForInstrumentedType +net.bytebuddy.implementation.attribute.AnnotationAppender$Target +net.bytebuddy.implementation.attribute.AnnotationAppender +net.bytebuddy.asm.AsmVisitorWrapper$NoOp +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ImplementationDefinition$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$MethodMatchAdapter +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ReceiverTypeDefinition +net.bytebuddy.implementation.MethodCall$MethodLocator$Factory +net.bytebuddy.implementation.MethodCall$TerminationHandler$Factory +net.bytebuddy.implementation.MethodCall$MethodInvoker$Factory +net.bytebuddy.implementation.MethodCall$TargetHandler$Factory +net.bytebuddy.implementation.MethodCall$ArgumentLoader$Factory +net.bytebuddy.implementation.MethodCall$MethodLocator +net.bytebuddy.implementation.MethodCall$MethodLocator$ForExplicitMethod +net.bytebuddy.implementation.MethodCall$TargetHandler$ForField$Location +net.bytebuddy.implementation.MethodCall$TargetHandler$ForSelfOrStaticInvocation$Factory +net.bytebuddy.implementation.MethodCall$TargetHandler +net.bytebuddy.implementation.MethodCall$MethodInvoker$ForContextualInvocation$Factory +net.bytebuddy.implementation.MethodCall$MethodInvoker +net.bytebuddy.implementation.MethodCall$TerminationHandler +net.bytebuddy.implementation.MethodCall$TerminationHandler$Simple +net.bytebuddy.implementation.MethodCall$TerminationHandler$Simple$1 +net.bytebuddy.implementation.MethodCall$TerminationHandler$Simple$2 +net.bytebuddy.implementation.MethodCall$TerminationHandler$Simple$3 +net.bytebuddy.dynamic.scaffold.MethodRegistry$Handler$ForImplementation +net.bytebuddy.dynamic.scaffold.MethodRegistry$Handler$Compiled +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ReceiverTypeDefinition$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$AbstractBase$Adapter +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$MethodMatchAdapter$AnnotationAdapter +net.bytebuddy.dynamic.Transformer +net.bytebuddy.implementation.attribute.MethodAttributeAppender +net.bytebuddy.implementation.attribute.MethodAttributeAppender$NoOp +net.bytebuddy.dynamic.Transformer$NoOp +net.bytebuddy.dynamic.scaffold.MethodRegistry$Default$Entry +net.bytebuddy.implementation.MethodCall$TargetHandler$ForMethodCall$Factory +net.bytebuddy.implementation.MethodCall$MethodInvoker$ForVirtualInvocation$WithImplicitType +net.bytebuddy.implementation.MethodCall$TargetHandler$ForMethodParameter +net.bytebuddy.implementation.MethodCall$TargetHandler$Resolved +net.bytebuddy.implementation.MethodCall$ArgumentLoader$ArgumentProvider +net.bytebuddy.implementation.MethodCall$ArgumentLoader$ForMethodParameter$Factory +net.bytebuddy.dynamic.TypeResolutionStrategy$Resolved +net.bytebuddy.dynamic.TypeResolutionStrategy$Passive +net.bytebuddy.pool.TypePool$AbstractBase +net.bytebuddy.pool.TypePool$AbstractBase$Hierarchical +net.bytebuddy.pool.TypePool$ClassLoading +net.bytebuddy.pool.TypePool$Resolution +net.bytebuddy.pool.TypePool$CacheProvider +net.bytebuddy.pool.TypePool$Empty +net.bytebuddy.pool.TypePool$CacheProvider$Simple +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithResolvedErasure +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Substitutor$ForAttachment +net.bytebuddy.description.method.MethodList$TypeSubstituting +net.bytebuddy.description.method.MethodDescription$InGenericShape +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$ForRawType +net.bytebuddy.matcher.VisibilityMatcher +net.bytebuddy.description.method.MethodDescription$TypeSubstituting +net.bytebuddy.description.type.TypeDescription$Generic$OfParameterizedType$ForGenerifiedErasure +net.bytebuddy.description.type.TypeDescription$Generic$OfNonGenericType$ForErasure +net.bytebuddy.description.type.TypeList$Generic$ForLoadedTypes$OfTypeVariables +net.bytebuddy.description.method.MethodDescription$Token +net.bytebuddy.matcher.TypeSortMatcher +net.bytebuddy.description.ByteCodeElement$Token$TokenList +net.bytebuddy.description.method.ParameterList$TypeSubstituting +net.bytebuddy.description.method.ParameterDescription$InGenericShape +net.bytebuddy.description.type.TypeList$Generic$ForDetachedTypes +net.bytebuddy.description.type.TypeList$Generic$OfConstructorExceptionTypes +jdk.internal.vm.annotation.IntrinsicCandidate +com.sun.proxy.jdk.proxy1.$Proxy66 +net.bytebuddy.description.annotation.AnnotationList$Explicit +net.bytebuddy.description.type.TypeDescription$Generic$LazyProxy +jdk.proxy2.$Proxy67 +net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder$InstrumentableMatcher +net.bytebuddy.description.method.MethodList$ForTokens +net.bytebuddy.description.method.MethodDescription$Latent +net.bytebuddy.description.method.ParameterList$ForTokens +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithLazyNavigation +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithLazyNavigation$OfAnnotatedElement +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$ForLoadedSuperClass +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Store +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Store$Entry +net.bytebuddy.description.type.TypeList$Generic$ForDetachedTypes$WithResolvedErasure +net.bytebuddy.description.type.TypeList$Generic$OfLoadedInterfaceTypes +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Harmonized +net.bytebuddy.description.method.MethodDescription$TypeToken +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Harmonizer$ForJavaMethod$Token +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Store$Entry$Initial +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Store$Entry$Resolved +net.bytebuddy.dynamic.scaffold.MethodGraph$Node +java.util.AbstractMap$SimpleImmutableEntry +net.bytebuddy.description.method.ParameterDescription$TypeSubstituting +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Store$Entry$Resolved$Node +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Detached +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Store$Graph +net.bytebuddy.dynamic.scaffold.MethodGraph$Linked$Delegation +net.bytebuddy.matcher.MethodParameterTypeMatcher +net.bytebuddy.matcher.FailSafeMatcher +net.bytebuddy.dynamic.scaffold.MethodGraph$NodeList +net.bytebuddy.dynamic.scaffold.MethodGraph$Node$Sort +net.bytebuddy.dynamic.scaffold.MethodRegistry$Default$Prepared$Entry +net.bytebuddy.description.method.MethodDescription$Latent$TypeInitializer +net.bytebuddy.dynamic.scaffold.MethodRegistry$Default$Prepared +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool +net.bytebuddy.dynamic.scaffold.MethodRegistry$Compiled +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator$1 +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator$2 +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator$3 +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator$ForTypeAnnotations +net.bytebuddy.description.annotation.AnnotationList$Empty +net.bytebuddy.description.type.TypeList$Generic$ForDetachedTypes$OfTypeVariables +net.bytebuddy.description.type.PackageDescription$AbstractBase +net.bytebuddy.description.type.PackageDescription$Simple +net.bytebuddy.description.field.FieldList$AbstractBase +net.bytebuddy.description.field.FieldList$ForTokens +net.bytebuddy.description.method.MethodDescription$SignatureToken +net.bytebuddy.description.annotation.AnnotationValue$ForDescriptionArray +net.bytebuddy.description.annotation.AnnotationValue$Sort +net.bytebuddy.description.annotation.AnnotationValue$State +net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget$Factory +net.bytebuddy.implementation.Implementation$Target +net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget$OriginTypeResolver +net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget$OriginTypeResolver$1 +net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget$OriginTypeResolver$2 +net.bytebuddy.implementation.Implementation$Target$AbstractBase +net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget +net.bytebuddy.implementation.Implementation$SpecialMethodInvocation +net.bytebuddy.implementation.Implementation$Target$AbstractBase$DefaultMethodInvocation +net.bytebuddy.implementation.Implementation$Target$AbstractBase$DefaultMethodInvocation$1 +net.bytebuddy.implementation.Implementation$Target$AbstractBase$DefaultMethodInvocation$2 +net.bytebuddy.dynamic.scaffold.MethodRegistry$Handler$ForImplementation$Compiled +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record +net.bytebuddy.implementation.MethodCall$Appender +net.bytebuddy.implementation.MethodCall$TargetHandler$ForMethodCall +net.bytebuddy.implementation.MethodCall$MethodInvoker$ForContextualInvocation +net.bytebuddy.implementation.MethodCall$TargetHandler$ForSelfOrStaticInvocation +net.bytebuddy.dynamic.scaffold.MethodRegistry$Default$Compiled$Entry +net.bytebuddy.implementation.SuperMethodCall$Appender +net.bytebuddy.implementation.SuperMethodCall$Appender$TerminationHandler +net.bytebuddy.implementation.SuperMethodCall$Appender$TerminationHandler$1 +net.bytebuddy.implementation.SuperMethodCall$Appender$TerminationHandler$2 +net.bytebuddy.dynamic.scaffold.MethodRegistry$Default$Compiled +net.bytebuddy.dynamic.scaffold.FieldRegistry$Default$Compiled +net.bytebuddy.dynamic.scaffold.TypeWriter$FieldPool$Record +net.bytebuddy.dynamic.scaffold.RecordComponentRegistry$Default$Compiled +net.bytebuddy.dynamic.scaffold.TypeWriter$RecordComponentPool$Record +net.bytebuddy.pool.TypePool$Explicit +net.bytebuddy.pool.TypePool$CacheProvider$NoOp +net.bytebuddy.dynamic.scaffold.TypeWriter +net.bytebuddy.dynamic.scaffold.TypeWriter$Default +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ClassDumpAction$Dispatcher +net.bytebuddy.dynamic.scaffold.inline.MethodRebaseResolver +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForCreation +net.bytebuddy.utility.visitor.MetadataAwareClassVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForCreation$CreationClassVisitor +net.bytebuddy.utility.visitor.ContextClassVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForCreation$ImplementationContextClassVisitor +net.bytebuddy.dynamic.scaffold.TypeInitializer$Drain +net.bytebuddy.description.type.RecordComponentList$ForTokens +net.bytebuddy.description.type.RecordComponentDescription +net.bytebuddy.description.type.RecordComponentDescription$InDefinedShape +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ClassDumpAction$Dispatcher$Disabled +net.bytebuddy.utility.AsmClassWriter$Factory$Default$EmptyAsmClassReader +net.bytebuddy.utility.AsmClassWriter$ForAsm +net.bytebuddy.implementation.Implementation$Context$FrameGeneration +net.bytebuddy.implementation.Implementation$Context$FrameGeneration$1 +net.bytebuddy.implementation.Implementation$Context$FrameGeneration$2 +net.bytebuddy.implementation.Implementation$Context$FrameGeneration$3 +net.bytebuddy.implementation.Implementation$Context$ExtractableView$AbstractBase +net.bytebuddy.implementation.Implementation$Context$Default +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod +net.bytebuddy.implementation.Implementation$Context$Default$DelegationRecord +net.bytebuddy.implementation.Implementation$Context$Default$AccessorMethodDelegation +net.bytebuddy.implementation.Implementation$Context$Default$FieldGetterDelegation +net.bytebuddy.implementation.Implementation$Context$Default$FieldSetterDelegation +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor$Constraint +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor$ValidatingFieldVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor$ValidatingMethodVisitor +net.bytebuddy.jar.asm.signature.SignatureVisitor +net.bytebuddy.jar.asm.signature.SignatureWriter +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$ForSignatureVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor$Constraint$ForClassFileVersion +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor$Constraint$ForClass +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor$Constraint$Compound +net.bytebuddy.implementation.attribute.AnnotationAppender$Default +net.bytebuddy.implementation.attribute.AnnotationAppender$Target$OnType +net.bytebuddy.implementation.attribute.AnnotationAppender$ForTypeAnnotations +java.util.AbstractList$SubList +net.bytebuddy.jar.asm.TypeReference +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$AccessBridgeWrapper +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$Sort +net.bytebuddy.description.modifier.Visibility$1 +net.bytebuddy.description.type.TypeList$Generic$OfMethodExceptionTypes +net.bytebuddy.implementation.MethodCall$TargetHandler$ForSelfOrStaticInvocation$Resolved +net.bytebuddy.implementation.bytecode.Duplication +net.bytebuddy.implementation.MethodCall$TargetHandler$ForMethodCall$Resolved +net.bytebuddy.implementation.bytecode.ByteCodeAppender$Size +net.bytebuddy.implementation.bytecode.StackManipulation$Compound +net.bytebuddy.implementation.bytecode.member.MethodVariableAccess +net.bytebuddy.implementation.bytecode.member.MethodVariableAccess$MethodLoading$TypeCastingHandler +net.bytebuddy.implementation.bytecode.member.MethodVariableAccess$OffsetLoading +net.bytebuddy.implementation.bytecode.member.MethodInvocation +net.bytebuddy.implementation.bytecode.member.MethodInvocation$WithImplicitInvocationTargetType +net.bytebuddy.implementation.bytecode.member.MethodInvocation$Invocation +net.bytebuddy.implementation.bytecode.member.MethodReturn +net.bytebuddy.implementation.bytecode.StackManipulation$Size +net.bytebuddy.implementation.MethodCall$TargetHandler$ForMethodParameter$Resolved +net.bytebuddy.implementation.MethodCall$ArgumentLoader +net.bytebuddy.implementation.MethodCall$ArgumentLoader$ForMethodParameter +net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveWideningDelegate +net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveWideningDelegate$WideningStackManipulation +net.bytebuddy.description.type.TypeList$Generic$OfMethodExceptionTypes$TypeProjection +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedExecutableExceptionType +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedExecutableExceptionType$Dispatcher +net.bytebuddy.description.type.$Proxy68 +net.bytebuddy.matcher.SignatureTokenMatcher +net.bytebuddy.implementation.Implementation$SpecialMethodInvocation$AbstractBase +net.bytebuddy.implementation.Implementation$SpecialMethodInvocation$Simple +net.bytebuddy.implementation.bytecode.member.MethodVariableAccess$MethodLoading +net.bytebuddy.implementation.bytecode.member.MethodVariableAccess$MethodLoading$TypeCastingHandler$NoOp +net.bytebuddy.dynamic.scaffold.TypeInitializer$Drain$Default +net.bytebuddy.description.method.ParameterList$Empty +net.bytebuddy.description.type.TypeList$Generic$Empty +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForNonImplementedMethod +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$UnresolvedType +net.bytebuddy.dynamic.DynamicType +net.bytebuddy.dynamic.DynamicType$Unloaded +net.bytebuddy.dynamic.DynamicType$AbstractBase +net.bytebuddy.dynamic.DynamicType$Default +net.bytebuddy.dynamic.DynamicType$Default$Unloaded +net.bytebuddy.dynamic.DynamicType$Loaded +net.bytebuddy.dynamic.loading.InjectionClassLoader$Strategy +net.bytebuddy.dynamic.DynamicType$Default$Loaded +java.lang.invoke.LambdaForm$MH/0x00007f42042d0000 +java.lang.invoke.LambdaForm$MH/0x00007f42042d0400 +java.lang.invoke.LambdaForm$MH/0x00007f42042d0800 +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$ClassDefinitionAction +net.bytebuddy.dynamic.loading.PackageDefinitionStrategy$Definition$Trivial +org.mockito.internal.util.reflection.InstrumentationMemberAccessor$Dispatcher$ByteBuddy$41mTxNg6 +java.lang.invoke.LambdaForm$DMH/0x00007f42042d1000 +org.mockito.internal.exceptions.stacktrace.DefaultStackTraceCleanerProvider +org.mockito.internal.configuration.InjectingAnnotationEngine +org.mockito.internal.configuration.IndependentAnnotationEngine +org.mockito.internal.configuration.FieldAnnotationProcessor +org.mockito.internal.configuration.MockAnnotationProcessor +org.mockito.Captor +org.mockito.internal.configuration.CaptorAnnotationProcessor +org.mockito.internal.configuration.SpyAnnotationEngine +org.mockito.internal.util.ConsoleMockitoLogger +org.mockito.plugins.MockResolver +org.mockito.plugins.DoNotMockEnforcer +org.mockito.internal.configuration.DefaultDoNotMockEnforcer +org.mockito.internal.creation.instance.DefaultInstantiatorProvider +org.mockito.internal.creation.instance.ObjenesisInstantiator +org.objenesis.Objenesis +org.objenesis.ObjenesisBase +org.objenesis.ObjenesisStd +org.objenesis.strategy.InstantiatorStrategy +org.mockito.configuration.IMockitoConfiguration +org.mockito.internal.configuration.GlobalConfiguration +org.mockito.configuration.DefaultMockitoConfiguration +org.mockito.internal.configuration.ClassPathLoader +org.objenesis.strategy.BaseInstantiatorStrategy +org.objenesis.strategy.StdInstantiatorStrategy +org.objenesis.instantiator.ObjectInstantiator +org.mockito.internal.session.DefaultMockitoSessionBuilder +org.mockito.MockitoSession +org.mockito.internal.session.MockitoSessionLoggerAdapter +org.mockito.internal.session.MockitoLoggerAdapter +org.mockito.internal.framework.DefaultMockitoSession +org.mockito.exceptions.misusing.RedundantListenerException +org.mockito.listeners.MockitoListener +org.mockito.internal.junit.TestFinishedEvent +org.mockito.listeners.MockCreationListener +org.mockito.internal.junit.MockitoTestListener +org.mockito.internal.listeners.AutoCleanableListener +org.mockito.internal.junit.UniversalTestListener +org.mockito.listeners.StubbingLookupListener +org.mockito.internal.junit.DefaultStubbingLookupListener +org.mockito.internal.framework.DefaultMockitoFramework +org.mockito.invocation.InvocationFactory +org.mockito.internal.util.Checks +org.mockito.internal.progress.ThreadSafeMockingProgress +org.mockito.internal.progress.ThreadSafeMockingProgress$1 +org.mockito.internal.progress.MockingProgress +org.mockito.internal.progress.MockingProgressImpl +org.mockito.internal.progress.ArgumentMatcherStorage +org.mockito.verification.VerificationStrategy +org.mockito.internal.progress.ArgumentMatcherStorageImpl +java.util.Stack +org.mockito.internal.progress.MockingProgressImpl$1 +org.mockito.MockitoAnnotations +org.mockito.internal.util.Supplier +org.mockito.internal.configuration.MockAnnotationProcessor$$Lambda$658/0x00007f42042d86c0 +org.mockito.MockSettings +org.mockito.mock.MockCreationSettings +org.mockito.internal.creation.settings.CreationSettings +org.mockito.internal.creation.MockSettingsImpl +org.mockito.mock.MockName +org.mockito.mock.SerializableMode +org.mockito.internal.util.MockCreationValidator +org.mockito.internal.util.MockUtil +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker$1 +org.mockito.mock.MockType +org.mockito.internal.util.MockNameImpl +org.mockito.plugins.DoNotMockEnforcer$Cache +org.mockito.internal.handler.MockHandlerFactory +org.mockito.invocation.MockHandler +org.mockito.internal.handler.MockHandlerImpl +org.mockito.invocation.MatchableInvocation +org.mockito.stubbing.OngoingStubbing +org.mockito.stubbing.Stubbing +org.mockito.invocation.InvocationContainer +org.mockito.internal.invocation.MatchersBinder +org.mockito.internal.stubbing.InvocationContainerImpl +org.mockito.invocation.StubInfo +org.mockito.internal.verification.RegisteredInvocations +org.mockito.internal.verification.DefaultRegisteredInvocations +org.mockito.internal.stubbing.DoAnswerStyleStubbing +org.mockito.internal.handler.NullResultGuardian +org.mockito.internal.handler.InvocationNotifierHandler +org.mockito.listeners.MethodInvocationReport +org.mockito.internal.creation.bytebuddy.MockFeatures +net.bytebuddy.TypeCache$SimpleKey +org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator$MockitoMockKey +org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator$$Lambda$659/0x00007f42042dd328 +net.bytebuddy.TypeCache$LookupKey +org.mockito.internal.creation.bytebuddy.TypeSupport +java.lang.invoke.LambdaForm$DMH/0x00007f42042e0000 +org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$$Lambda$660/0x00007f42042dd978 +org.mockito.internal.util.concurrent.WeakConcurrentMap$WeakKey +org.mockito.internal.util.concurrent.WeakConcurrentMap$LatentKey +net.bytebuddy.dynamic.ClassFileLocator$Simple +net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder +net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder +net.bytebuddy.description.field.FieldList$ForLoadedFields +net.bytebuddy.utility.FieldComparator +sun.reflect.annotation.TypeAnnotation$TypeAnnotationTarget +sun.reflect.annotation.TypeAnnotationParser +sun.reflect.annotation.TypeAnnotation +sun.reflect.annotation.TypeAnnotation$LocationInfo +sun.reflect.annotation.TypeAnnotation$LocationInfo$Location +sun.reflect.annotation.AnnotatedTypeFactory +sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$Simple +sun.reflect.generics.tree.TypeVariableSignature +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedTypeVariable +net.bytebuddy.description.type.TypeDescription$Generic$OfTypeVariable +net.bytebuddy.description.type.TypeDescription$Generic$OfTypeVariable$ForLoadedType +net.bytebuddy.description.type.TypeVariableToken +net.bytebuddy.description.type.TypeDescription$Generic$OfTypeVariable$ForLoadedType$TypeVariableBoundList +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$ForTypeVariableBoundType$OfFormalTypeVariable +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$ForTypeVariableBoundType$OfFormalTypeVariable$FormalTypeVariable +net.bytebuddy.description.type.$Proxy69 +sun.reflect.misc.ReflectUtil +net.bytebuddy.description.type.TypeDescription$Generic$OfTypeVariable$Symbolic +net.bytebuddy.description.method.ParameterDescription$Token +net.bytebuddy.description.type.TypeDescription$Generic$OfNonGenericType$Latent +java.lang.Class$EnclosingMethodInfo +net.bytebuddy.description.type.RecordComponentDescription$Token +net.bytebuddy.implementation.attribute.TypeAttributeAppender$ForInstrumentedType$Differentiating +net.bytebuddy.asm.AsmVisitorWrapper$AbstractBase +org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$ParameterWritingVisitorWrapper +org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$ParameterWritingVisitorWrapper$ParameterAddingClassVisitor +net.bytebuddy.asm.AsmVisitorWrapper$Compound +net.bytebuddy.pool.TypePool$Default +net.bytebuddy.pool.TypePool$Default$TypeExtractor +net.bytebuddy.pool.TypePool$Default$ReaderMode +net.bytebuddy.dynamic.scaffold.inline.InliningImplementationMatcher +net.bytebuddy.description.type.TypeDescription$Generic$OfTypeVariable$WithAnnotationOverlay +net.bytebuddy.description.type.TypeList$Generic$ForDetachedTypes$OfTypeVariables$AttachedTypeVariable +net.bytebuddy.description.method.ParameterDescription$Latent +net.bytebuddy.dynamic.scaffold.MethodGraph$Node$Simple +net.bytebuddy.dynamic.scaffold.MethodGraph$Simple +net.bytebuddy.dynamic.scaffold.MethodGraph$Empty +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$RegistryContextClassVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor +net.bytebuddy.jar.asm.commons.Remapper +net.bytebuddy.jar.asm.commons.SimpleRemapper +net.bytebuddy.jar.asm.commons.ClassRemapper +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$OpenedClassRemapper +net.bytebuddy.dynamic.scaffold.inline.MethodRebaseResolver$Disabled +net.bytebuddy.dynamic.scaffold.inline.MethodRebaseResolver$Resolution +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$ContextRegistry +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$InitializationHandler +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor$AttributeObtainingRecordComponentVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor$AttributeObtainingFieldVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor$AttributeObtainingMethodVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor$CodePreservingMethodVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor$DeduplicatingClassVisitor +net.bytebuddy.jar.asm.Context +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$InitializationHandler$Creating +net.bytebuddy.implementation.Implementation$Context$Disabled +org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$ParameterWritingVisitorWrapper$MethodParameterStrippingMethodVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$SignatureKey +net.bytebuddy.matcher.DescriptorMatcher +jdk.internal.reflect.GeneratedMethodAccessor3 +software.amazon.lambda.powertools.kafka.serializers.PowertoolsDeserializer +net.bytebuddy.dynamic.loading.MultipleParentClassLoader$Builder +net.bytebuddy.dynamic.loading.MultipleParentClassLoader +net.bytebuddy.matcher.LatentMatcher$Disjunction +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$OptionalMethodMatchAdapter +net.bytebuddy.description.modifier.SynchronizationState +net.bytebuddy.dynamic.Transformer$ForMethod +net.bytebuddy.dynamic.Transformer$ForMethod$MethodModifierTransformer +net.bytebuddy.dynamic.Transformer$Compound +net.bytebuddy.implementation.attribute.MethodAttributeAppender$ForInstrumentedMethod +net.bytebuddy.implementation.attribute.MethodAttributeAppender$ForInstrumentedMethod$1 +net.bytebuddy.implementation.attribute.MethodAttributeAppender$ForInstrumentedMethod$2 +net.bytebuddy.implementation.attribute.MethodAttributeAppender$Factory$Compound +net.bytebuddy.description.modifier.FieldManifestation +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition$Optional$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition$Optional$Valuable$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition$Optional$Valuable$AbstractBase$Adapter +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$FieldDefinitionAdapter +net.bytebuddy.implementation.attribute.FieldAttributeAppender$Factory +net.bytebuddy.description.field.FieldDescription$Token +net.bytebuddy.implementation.attribute.FieldAttributeAppender +net.bytebuddy.implementation.attribute.FieldAttributeAppender$ForInstrumentedField +net.bytebuddy.matcher.LatentMatcher$ForFieldToken +net.bytebuddy.dynamic.scaffold.FieldRegistry$Default$Entry +net.bytebuddy.implementation.FieldAccessor +net.bytebuddy.implementation.FieldAccessor$FieldLocation +net.bytebuddy.implementation.FieldAccessor$PropertyConfigurable +net.bytebuddy.implementation.FieldAccessor$AssignerConfigurable +net.bytebuddy.implementation.FieldAccessor$OwnerTypeLocatable +net.bytebuddy.implementation.FieldAccessor$FieldNameExtractor +net.bytebuddy.implementation.FieldAccessor$FieldNameExtractor$ForBeanProperty +net.bytebuddy.implementation.FieldAccessor$FieldNameExtractor$ForBeanProperty$1 +net.bytebuddy.implementation.FieldAccessor$FieldNameExtractor$ForBeanProperty$2 +net.bytebuddy.implementation.FieldAccessor$ForImplicitProperty +net.bytebuddy.implementation.FieldAccessor$FieldLocation$Relative +net.bytebuddy.implementation.FieldAccessor$FieldLocation$Prepared +net.bytebuddy.dynamic.scaffold.FieldLocator$ForClassHierarchy$Factory +net.bytebuddy.matcher.SuperTypeMatcher +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$TypeVariableDefinition$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ExceptionDefinition$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Initial$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$MethodDefinitionAdapter +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$TypeVariableDefinition$Annotatable +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Annotatable +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Simple$Annotatable +net.bytebuddy.description.method.ParameterDescription$Token$TypeList +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Simple$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Simple$Annotatable$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Simple$Annotatable$AbstractBase$Adapter +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$MethodDefinitionAdapter$SimpleParameterAnnotationAdapter +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$MethodDefinitionAdapter$AnnotationAdapter +jdk.internal.reflect.GeneratedMethodAccessor4 +net.bytebuddy.dynamic.loading.ClassLoadingStrategy$UsingLookup +net.bytebuddy.dynamic.loading.ClassInjector +net.bytebuddy.dynamic.loading.ClassInjector$AbstractBase +net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup +net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup$MethodHandles +net.bytebuddy.dynamic.loading.$Proxy70 +net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup$MethodHandles$Lookup +jdk.proxy2.$Proxy71 +net.bytebuddy.utility.JavaType +net.bytebuddy.description.type.TypeDescription$Latent +net.bytebuddy.utility.JavaType$LatentTypeWithSimpleName +net.bytebuddy.matcher.LatentMatcher$ForMethodToken +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$ForTypeVariableBoundType +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$ForTypeVariableBoundType$AnnotatedTypeVariable +java.lang.reflect.AnnotatedTypeVariable +net.bytebuddy.description.type.$Proxy72 +net.bytebuddy.matcher.LatentMatcher$ForMethodToken$ResolvedMatcher +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Reducing +net.bytebuddy.dynamic.Transformer$ForMethod$TransformedMethod +jdk.internal.reflect.GeneratedMethodAccessor5 +net.bytebuddy.implementation.MethodDelegation$ImplementationDelegate$Compiled$ForStaticCall +net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodInvoker +net.bytebuddy.implementation.MethodDelegation$Appender +net.bytebuddy.implementation.bind.MethodDelegationBinder$Processor +net.bytebuddy.implementation.attribute.MethodAttributeAppender$Compound +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Substitutor$WithoutTypeSubstitution +net.bytebuddy.dynamic.Transformer$ForMethod$TransformedMethod$AttachmentVisitor +net.bytebuddy.dynamic.Transformer$ForMethod$TransformedMethod$TransformedParameterList +net.bytebuddy.dynamic.Transformer$ForMethod$TransformedMethod$TransformedParameter +net.bytebuddy.implementation.FieldAccessor$ForImplicitProperty$Appender +net.bytebuddy.implementation.FieldAccessor$FieldLocation$Relative$Prepared +net.bytebuddy.dynamic.scaffold.FieldLocator$Resolution +net.bytebuddy.dynamic.scaffold.FieldRegistry$Default$Compiled$Entry +net.bytebuddy.matcher.LatentMatcher$ForFieldToken$ResolvedMatcher +net.bytebuddy.description.field.FieldDescription$SignatureToken +net.bytebuddy.description.field.FieldDescription$AbstractBase +net.bytebuddy.description.field.FieldDescription$InDefinedShape$AbstractBase +net.bytebuddy.description.field.FieldDescription$Latent +net.bytebuddy.dynamic.scaffold.TypeWriter$FieldPool$Record$ForExplicitField +net.bytebuddy.implementation.attribute.AnnotationAppender$Target$OnField +net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodInvoker$Simple +net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodBinding$Builder +net.bytebuddy.implementation.bind.MethodDelegationBinder$ParameterBinding$Anonymous +net.bytebuddy.description.annotation.AnnotationValue$Loaded$AbstractBase +net.bytebuddy.description.annotation.AnnotationValue$ForEnumerationDescription$Loaded +net.bytebuddy.implementation.bind.ArgumentTypeResolver$ParameterIndexToken +net.bytebuddy.implementation.bind.MethodDelegationBinder$ParameterBinding$Unique +net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodBinding$Builder$Build +net.bytebuddy.implementation.attribute.AnnotationAppender$Target$OnMethod +net.bytebuddy.implementation.bytecode.assign.TypeCasting +net.bytebuddy.description.type.TypeDefinition$SuperClassIterator +net.bytebuddy.description.field.FieldList$Explicit +net.bytebuddy.dynamic.scaffold.FieldLocator$Resolution$Simple +net.bytebuddy.implementation.bytecode.member.FieldAccess +net.bytebuddy.implementation.bytecode.member.FieldAccess$Defined +net.bytebuddy.implementation.bytecode.member.FieldAccess$AccessDispatcher +net.bytebuddy.implementation.bytecode.member.FieldAccess$AccessDispatcher$AbstractFieldInstruction +net.bytebuddy.implementation.bytecode.member.FieldAccess$AccessDispatcher$FieldGetInstruction +net.bytebuddy.implementation.bytecode.constant.MethodConstant +net.bytebuddy.implementation.bytecode.constant.MethodConstant$CanCache +net.bytebuddy.implementation.bytecode.constant.MethodConstant$ForMethod +net.bytebuddy.implementation.bytecode.constant.MethodConstant$CachedMethod +net.bytebuddy.implementation.bytecode.collection.CollectionFactory +net.bytebuddy.implementation.bytecode.collection.ArrayFactory +net.bytebuddy.implementation.bytecode.collection.ArrayFactory$ArrayCreator +net.bytebuddy.implementation.bytecode.collection.ArrayFactory$ArrayCreator$ForReferenceType +net.bytebuddy.implementation.bytecode.collection.ArrayFactory$ArrayStackManipulation +net.bytebuddy.implementation.auxiliary.MethodCallProxy$AssignableSignatureCall +net.bytebuddy.implementation.auxiliary.AuxiliaryType +net.bytebuddy.implementation.bytecode.constant.DefaultValue +net.bytebuddy.implementation.bytecode.constant.IntegerConstant +net.bytebuddy.implementation.bytecode.constant.LongConstant +net.bytebuddy.implementation.bytecode.constant.FloatConstant +net.bytebuddy.implementation.bytecode.constant.DoubleConstant +net.bytebuddy.implementation.bytecode.constant.NullConstant +net.bytebuddy.implementation.bind.MethodDelegationBinder$1 +net.bytebuddy.implementation.bind.MethodDelegationBinder$AmbiguityResolver$Resolution +net.bytebuddy.implementation.Implementation$Context$Default$FieldCacheEntry +net.bytebuddy.implementation.Implementation$Context$Default$CacheValueField +net.bytebuddy.implementation.auxiliary.MethodCallProxy +net.bytebuddy.implementation.MethodAccessorFactory$AccessType +net.bytebuddy.implementation.Implementation$Context$Default$AbstractPropertyAccessorMethod +net.bytebuddy.implementation.Implementation$Context$Default$AccessorMethod +net.bytebuddy.description.method.ParameterList$Explicit$ForTypes +net.bytebuddy.implementation.auxiliary.MethodCallProxy$PrecomputedMethodGraph +net.bytebuddy.implementation.auxiliary.MethodCallProxy$MethodCall +net.bytebuddy.implementation.auxiliary.MethodCallProxy$ConstructorCall +net.bytebuddy.implementation.auxiliary.MethodCallProxy$MethodCall$Appender +net.bytebuddy.implementation.auxiliary.MethodCallProxy$ConstructorCall$Appender +net.bytebuddy.implementation.bytecode.Removal +net.bytebuddy.implementation.bytecode.Removal$1 +net.bytebuddy.implementation.bytecode.Removal$2 +net.bytebuddy.implementation.attribute.AnnotationAppender$Target$OnMethodParameter +net.bytebuddy.implementation.bytecode.member.FieldAccess$AccessDispatcher$FieldPutInstruction +net.bytebuddy.implementation.bytecode.TypeCreation +net.bytebuddy.implementation.bytecode.Duplication$1 +net.bytebuddy.implementation.bytecode.Duplication$2 +net.bytebuddy.implementation.bytecode.Duplication$3 +sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeVariableImpl +net.bytebuddy.dynamic.scaffold.MethodGraph$Node$Unresolved +net.bytebuddy.implementation.Implementation$SpecialMethodInvocation$Illegal +net.bytebuddy.implementation.bind.MethodDelegationBinder$ParameterBinding$Illegal +net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodBinding$Illegal +jdk.internal.reflect.GeneratedMethodAccessor6 +jdk.internal.reflect.GeneratedMethodAccessor7 +net.bytebuddy.dynamic.scaffold.FieldLocator$Resolution$Illegal +net.bytebuddy.implementation.bytecode.ByteCodeAppender$Simple +net.bytebuddy.dynamic.scaffold.TypeInitializer$Simple +net.bytebuddy.implementation.bytecode.ByteCodeAppender$Compound +net.bytebuddy.implementation.bytecode.constant.ClassConstant +net.bytebuddy.implementation.bytecode.constant.ClassConstant$ForReferenceType +net.bytebuddy.description.type.PackageDescription$ForLoadedPackage +software.amazon.lambda.powertools.kafka.serializers.PowertoolsDeserializer$MockitoMock$84ecoPEA +software.amazon.lambda.powertools.kafka.serializers.PowertoolsDeserializer$MockitoMock$84ecoPEA$auxiliary$0oONJkzb +software.amazon.lambda.powertools.kafka.serializers.PowertoolsDeserializer$MockitoMock$84ecoPEA$auxiliary$buK1SnlY +net.bytebuddy.TypeCache$StorageKey +org.mockito.plugins.MemberAccessor$OnConstruction +org.mockito.plugins.MemberAccessor$ConstructionDispatcher +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker$$Lambda$661/0x00007f4204315f70 +java.lang.invoke.LambdaForm$MH/0x00007f4204310800 +java.lang.invoke.LambdaForm$MH/0x00007f4204310c00 +java.lang.invoke.LambdaForm$MH/0x00007f4204311000 +java.lang.invoke.LambdaForm$MH/0x00007f4204311400 +sun.invoke.util.ValueConversions$WrapperCache +java.lang.invoke.LambdaForm$MH/0x00007f4204311800 +java.lang.invoke.LambdaForm$MH/0x00007f4204311c00 +java.lang.invoke.LambdaForm$MH/0x00007f4204312000 +java.lang.invoke.LambdaForm$MH/0x00007f4204312400 +java.lang.invoke.LambdaForm$MH/0x00007f4204312800 +org.mockito.internal.util.reflection.InstrumentationMemberAccessor$$Lambda$662/0x00007f4204316198 +org.mockito.invocation.Invocation +org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport +org.mockito.exceptions.base.MockitoSerializationIssue +java.lang.invoke.LambdaForm$MH/0x00007f4204312c00 +java.lang.invoke.LambdaForm$MH/0x00007f4204313000 +java.lang.invoke.LambdaForm$MH/0x00007f4204313400 +java.lang.invoke.LambdaForm$DMH/0x00007f4204313800 +org.mockito.internal.configuration.IndependentAnnotationEngine$$Lambda$663/0x00007f4204316a40 +org.mockito.Spy +org.mockito.plugins.AnnotationEngine$NoAction +org.mockito.internal.util.collections.Sets +org.mockito.internal.util.collections.HashCodeAndEqualsSafeSet +org.mockito.internal.configuration.injection.scanner.InjectMocksScanner +org.mockito.InjectMocks +org.mockito.internal.configuration.injection.scanner.MockScanner +org.mockito.internal.util.reflection.FieldReader +org.mockito.internal.util.collections.HashCodeAndEqualsMockWrapper +java.lang.invoke.DirectMethodHandle$StaticAccessor +java.lang.invoke.LambdaForm$MH/0x00007f4204318000 +org.mockito.internal.util.collections.HashCodeAndEqualsSafeSet$1 +org.mockito.internal.configuration.DefaultInjectionEngine +org.mockito.internal.configuration.injection.MockInjection +org.mockito.internal.configuration.injection.MockInjection$OngoingMockInjection +org.mockito.internal.configuration.injection.MockInjectionStrategy +org.mockito.internal.configuration.injection.ConstructorInjection +org.mockito.internal.configuration.injection.PropertyAndSetterInjection +org.mockito.internal.configuration.injection.SpyOnInjectedFieldsHandler +org.mockito.internal.configuration.injection.MockInjectionStrategy$1 +org.mockito.internal.util.reflection.FieldInitializer$ConstructorArgumentResolver +org.mockito.internal.configuration.injection.filter.MockCandidateFilter +org.mockito.internal.configuration.injection.filter.TypeBasedCandidateFilter +org.mockito.internal.configuration.injection.filter.NameBasedCandidateFilter +org.mockito.internal.configuration.injection.filter.TerminalMockCandidateFilter +org.mockito.internal.configuration.InjectingAnnotationEngine$$Lambda$664/0x00007f420431de08 +org.mockito.internal.configuration.InjectingAnnotationEngine$$Lambda$665/0x00007f420431e030 +com.amazonaws.services.lambda.runtime.CustomPojoSerializer +org.crac.Resource +software.amazon.lambda.powertools.kafka.PowertoolsSerializer +software.amazon.lambda.powertools.kafka.serializers.KafkaJsonDeserializer +org.apache.kafka.common.header.Headers +software.amazon.lambda.powertools.kafka.serializers.KafkaAvroDeserializer +org.apache.avro.io.DatumReader +software.amazon.lambda.powertools.kafka.serializers.KafkaProtobufDeserializer +software.amazon.lambda.powertools.kafka.testutils.InputStreamHandler +org.crac.Core +org.crac.Context +org.crac.GlobalContextWrapper +org.crac.Core$Compat +org.crac.CheckpointException +org.crac.RestoreException +org.assertj.core.internal.Strings +org.junit.jupiter.engine.execution.NamespaceAwareStore$$Lambda$666/0x00007f420431a8a0 +org.mockito.junit.jupiter.MockitoExtension$$Lambda$667/0x00007f420431aac8 +org.mockito.junit.jupiter.MockitoExtension$$Lambda$668/0x00007f420431acf8 +org.mockito.junit.jupiter.MockitoExtension$$Lambda$669/0x00007f420431af28 +org.mockito.internal.framework.DefaultMockitoSession$1 +org.mockito.internal.junit.UniversalTestListener$1 +org.mockito.internal.junit.UnusedStubbingsFinder +org.mockito.internal.junit.UnusedStubbings +org.mockito.internal.invocation.finder.AllInvocationsFinder +org.mockito.internal.stubbing.StubbingComparator +org.mockito.internal.invocation.InvocationComparator +java.util.IdentityHashMap$IdentityHashMapIterator +java.util.IdentityHashMap$KeyIterator +org.mockito.internal.util.DefaultMockingDetails +java.util.TreeMap$TreeMapSpliterator +java.util.TreeMap$KeySpliterator +org.mockito.internal.stubbing.UnusedStubbingReporting +org.mockito.internal.junit.UnusedStubbingsFinder$$Lambda$670/0x00007f4204319750 +org.assertj.core.api.ThrowableAssert$ThrowingCallable +software.amazon.lambda.powertools.kafka.PowertoolsSerializerTest$$Lambda$671/0x00007f4204319ba0 +org.assertj.core.internal.Throwables +org.assertj.core.internal.CommonValidations +org.junit.jupiter.api.extension.TemplateInvocationValidationException +org.junit.jupiter.params.ParameterizedDeclarationContext +org.junit.jupiter.engine.descriptor.TestTemplateExtensionContext +org.junit.jupiter.engine.descriptor.TemplateExecutor +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor$TestTemplateExecutor +org.junit.jupiter.engine.descriptor.TemplateExecutor$$Lambda$672/0x00007f42043209d0 +org.junit.jupiter.api.RepeatedTest +org.junit.jupiter.params.ParameterizedTestContext +org.junit.jupiter.params.ResolverFacade +org.junit.jupiter.params.support.ParameterDeclaration +org.junit.jupiter.params.support.ParameterDeclarations +org.junit.jupiter.params.ResolverFacade$ResolvableParameterDeclaration +org.junit.jupiter.params.support.FieldContext +org.junit.jupiter.params.ResolverFacade$FieldParameterDeclaration +org.junit.jupiter.params.ResolverFacade$Resolver +org.junit.jupiter.params.ResolverFacade$ExecutableParameterDeclaration +org.junit.jupiter.params.aggregator.ArgumentsAccessor +org.junit.jupiter.params.aggregator.AggregateWith +java.util.TreeMap$Values +java.util.TreeMap$ValueIterator +org.junit.jupiter.params.ResolverFacade$DefaultParameterDeclarations +org.junit.jupiter.engine.descriptor.TemplateExecutor$$Lambda$673/0x00007f4204322990 +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$674/0x00007f4204322bb8 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$PartialFormatter +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$675/0x00007f42043231f8 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$676/0x00007f4204323420 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$CachingByArgumentsLengthPartialFormatter +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$677/0x00007f4204323890 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$ArgumentSetNameFormatter +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$PartialFormatters +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$ArgumentsContext +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$PartialFormatter$$Lambda$678/0x00007f4204324140 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$679/0x00007f4204324360 +java.lang.invoke.LambdaForm$DMH/0x00007f4204318400 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$680/0x00007f4204324588 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$681/0x00007f42043247c8 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$PlaceholderPosition +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$682/0x00007f4204324c00 +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$683/0x00007f4204325020 +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$684/0x00007f4204325260 +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$685/0x00007f42043254a8 +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$686/0x00007f42043256f0 +org.junit.jupiter.params.provider.Arguments +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$687/0x00007f4204325b38 +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$688/0x00007f4204325d80 +org.junit.jupiter.engine.descriptor.TemplateExecutor$$Lambda$689/0x00007f4204325fa8 +org.junit.jupiter.params.ParameterizedTestSpiInstantiator +org.junit.jupiter.params.ParameterizedTestSpiInstantiator$$Lambda$690/0x00007f42043263e8 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$691/0x00007f4204326610 +org.junit.jupiter.params.support.AnnotationConsumerInitializer +org.junit.jupiter.params.support.AnnotationConsumerInitializer$AnnotationConsumingMethodSignature +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$692/0x00007f4204326e80 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$693/0x00007f42043270c0 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$694/0x00007f4204327310 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$695/0x00007f4204327558 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$696/0x00007f42043277b0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$697/0x00007f4204327a00 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$698/0x00007f4204327c20 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$699/0x00007f4204328000 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$700/0x00007f4204328220 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$701/0x00007f4204328470 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$702/0x00007f42043286c8 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$703/0x00007f4204328908 +org.junit.jupiter.params.provider.AnnotationBasedArgumentsProvider$$Lambda$704/0x00007f4204328b40 +org.junit.jupiter.engine.descriptor.TestTemplateExtensionContext$$Lambda$705/0x00007f4204328d88 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$706/0x00007f4204328fc8 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$707/0x00007f4204329210 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$708/0x00007f4204329458 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$709/0x00007f42043296a0 +org.junit.jupiter.params.provider.ArgumentsUtils +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$710/0x00007f4204329ae8 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$711/0x00007f4204329d28 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$712/0x00007f4204329f50 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$713/0x00007f420432a198 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$714/0x00007f420432a3f0 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$715/0x00007f420432a618 +org.junit.jupiter.params.provider.Arguments$$Lambda$716/0x00007f420432aa38 +org.junit.jupiter.params.ParameterizedInvocationContext +org.junit.jupiter.params.ParameterizedTestInvocationContext +java.util.function.IntUnaryOperator +java.lang.invoke.LambdaForm$DMH/0x00007f420432c000 +org.junit.jupiter.params.ParameterizedInvocationContext$$Lambda$717/0x00007f420432b0d0 +org.junit.jupiter.params.EvaluatedArgumentSet +org.junit.jupiter.params.EvaluatedArgumentSet$$Lambda$718/0x00007f420432b560 +java.lang.invoke.LambdaForm$DMH/0x00007f420432c400 +org.junit.jupiter.params.provider.Arguments$ArgumentSet +org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor +org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor$$Lambda$719/0x00007f420432e000 +org.junit.jupiter.api.Named +org.junit.jupiter.params.EvaluatedArgumentSet$$Lambda$720/0x00007f420432e420 +org.junit.jupiter.params.EvaluatedArgumentSet$$Lambda$721/0x00007f420432e660 +java.util.stream.ReferencePipeline$$Lambda$722/0x00007f420412bc70 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$MessageFormatPartialFormatter +java.util.stream.Streams$RangeIntSpliterator +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$723/0x00007f420432ead8 +java.util.stream.IntPipeline$1 +java.util.stream.IntPipeline$1$1 +org.junit.jupiter.params.ResolverFacade$$Lambda$724/0x00007f420432ed00 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$725/0x00007f420432ef40 +java.text.MessageFormat +java.text.MessageFormat$Field +org.junit.jupiter.engine.descriptor.TemplateExecutor$$Lambda$726/0x00007f420432f180 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$727/0x00007f420432f3b8 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$728/0x00007f420432f5f0 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$729/0x00007f420432f818 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$730/0x00007f420432fa50 +org.junit.platform.engine.support.hierarchical.NodeTestTask$DefaultDynamicTestExecutor$$Lambda$731/0x00007f420432fc78 +org.junit.platform.engine.support.hierarchical.NodeTestTask$DynamicTaskState +org.junit.platform.engine.support.hierarchical.NodeTestTask$DynamicTaskState$$Lambda$732/0x00007f420432d200 +org.junit.jupiter.params.ParameterizedInvocationParameterResolver +org.junit.jupiter.params.ParameterizedTestMethodParameterResolver +org.junit.jupiter.params.ResolutionCache +org.junit.jupiter.params.ResolutionCache$$Lambda$733/0x00007f420432dac0 +org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor$$Lambda$734/0x00007f420432dce0 +org.junit.jupiter.params.EvaluatedArgumentSet$$Lambda$735/0x00007f420432c800 +org.junit.jupiter.params.ParameterizedInvocationContext$$Lambda$736/0x00007f420432ca40 +org.junit.jupiter.params.ParameterizedInvocationContext$$Lambda$737/0x00007f420432cc98 +org.junit.jupiter.params.ParameterizedInvocationContext$CloseableArgument +org.junit.jupiter.params.ParameterizedInvocationContext$$Lambda$738/0x00007f4204330248 +org.junit.jupiter.params.ParameterizedInvocationContext$$Lambda$739/0x00007f4204330488 +org.junit.jupiter.params.ArgumentCountValidator +org.junit.jupiter.params.ArgumentCountValidator$$Lambda$740/0x00007f42043308d8 +org.junit.jupiter.params.ArgumentCountValidator$1 +org.junit.jupiter.params.EvaluatedArgumentSet$$Lambda$741/0x00007f4204330d28 +org.junit.jupiter.params.aggregator.DefaultArgumentsAccessor +org.junit.jupiter.params.aggregator.ArgumentAccessException +org.junit.jupiter.params.aggregator.DefaultArgumentsAccessor$$Lambda$742/0x00007f42043314d8 +org.junit.jupiter.params.aggregator.DefaultArgumentsAccessor$$Lambda$743/0x00007f4204331710 +org.junit.jupiter.params.support.ParameterInfo +org.junit.jupiter.params.DefaultParameterInfo +org.junit.jupiter.engine.execution.DefaultParameterContext +org.junit.jupiter.engine.execution.ParameterResolutionUtils$$Lambda$744/0x00007f4204332048 +org.mockito.junit.jupiter.resolver.CompositeParameterResolver$$Lambda$745/0x00007f42043322a0 +org.junit.jupiter.params.ResolverFacade$$Lambda$746/0x00007f42043324f8 +org.junit.jupiter.params.ResolverFacade$$Lambda$747/0x00007f4204332718 +java.lang.invoke.LambdaForm$DMH/0x00007f4204334000 +org.junit.jupiter.params.ResolverFacade$$Lambda$748/0x00007f4204332938 +java.lang.invoke.LambdaForm$DMH/0x00007f4204334400 +java.lang.invoke.LambdaForm$DMH/0x00007f4204334800 +java.lang.invoke.LambdaForm$MH/0x00007f4204334c00 +org.junit.jupiter.params.ResolverFacade$$Lambda$749/0x00007f4204332b60 +org.junit.jupiter.params.converter.ConvertWith +org.junit.jupiter.params.ResolverFacade$$Lambda$750/0x00007f4204332fa8 +org.junit.jupiter.params.converter.ArgumentConverter +org.junit.jupiter.params.ResolverFacade$$Lambda$751/0x00007f42043333e8 +org.junit.jupiter.params.ResolverFacade$$Lambda$752/0x00007f4204333630 +org.junit.jupiter.params.ResolverFacade$Converter +org.junit.jupiter.params.ResolverFacade$$Lambda$753/0x00007f4204333ab8 +org.junit.jupiter.params.ResolverFacade$$Lambda$754/0x00007f4204333cf8 +org.junit.jupiter.params.converter.DefaultArgumentConverter +org.junit.platform.commons.support.conversion.ConversionException +org.junit.jupiter.params.converter.ArgumentConversionException +org.junit.jupiter.params.converter.DefaultArgumentConverter$LocaleConversionFormat +org.junit.jupiter.params.converter.DefaultArgumentConverter$$Lambda$755/0x00007f4204336940 +org.junit.jupiter.params.ResolverFacade$ExecutableParameterDeclaration$$Lambda$756/0x00007f4204336b80 +org.junit.jupiter.params.ResolverFacade$ExecutableParameterDeclaration$$Lambda$757/0x00007f4204336dd8 +org.junit.jupiter.params.EvaluatedArgumentSet$$Lambda$758/0x00007f4204337000 +org.junit.jupiter.engine.execution.ParameterResolutionUtils$$Lambda$759/0x00007f4204337240 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$760/0x00007f4204337468 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$761/0x00007f42043376c0 +org.apache.avro.Schema$Parser +org.apache.avro.SchemaParseException +com.fasterxml.jackson.core.exc.StreamReadException +com.fasterxml.jackson.core.JsonParseException +org.apache.avro.NameValidator +org.apache.avro.NameValidator$Result +org.apache.avro.NameValidator$1 +org.apache.avro.NameValidator$2 +org.apache.avro.NameValidator$3 +org.apache.avro.ParseContext +org.apache.avro.Schema$Type +org.apache.avro.AvroTypeException +org.apache.avro.util.SchemaVisitor +org.apache.avro.SchemaParser$ParseResult +org.apache.avro.util.internal.Accessor$JsonPropertiesAccessor +org.apache.avro.JsonProperties$1 +org.apache.avro.Schema$StringSchema +org.apache.avro.Schema$BytesSchema +org.apache.avro.Schema$IntSchema +org.apache.avro.Schema$LongSchema +org.apache.avro.Schema$FloatSchema +org.apache.avro.Schema$DoubleSchema +org.apache.avro.Schema$BooleanSchema +org.apache.avro.Schema$NullSchema +org.apache.avro.Schema$MapSchema +org.apache.avro.Schema$UnionSchema +org.apache.avro.Schema$Field +org.apache.avro.Schema$ArraySchema +org.apache.avro.Schema$NamedSchema +org.apache.avro.Schema$FixedSchema +org.apache.avro.Schema$RecordSchema +org.apache.avro.Schema$EnumSchema +org.apache.avro.util.internal.Accessor +org.apache.avro.JsonProperties$Null +org.apache.avro.Schema$$Lambda$762/0x00007f420433cd10 +org.apache.avro.util.internal.ThreadLocalWithInitial +org.apache.avro.Schema$$Lambda$763/0x00007f420433d138 +org.apache.avro.Schema$$Lambda$764/0x00007f420433d358 +org.apache.avro.Schema$$Lambda$765/0x00007f420433d578 +com.fasterxml.jackson.core.io.ContentReference +com.fasterxml.jackson.core.util.BufferRecyclers +com.fasterxml.jackson.core.util.BufferRecycler +com.fasterxml.jackson.core.io.IOContext +com.fasterxml.jackson.core.util.TextBuffer +com.fasterxml.jackson.core.util.ReadConstrainedTextBuffer +com.fasterxml.jackson.core.exc.InputCoercionException +com.fasterxml.jackson.core.io.JsonEOFException +com.fasterxml.jackson.core.JsonStreamContext +com.fasterxml.jackson.core.json.JsonReadContext +com.fasterxml.jackson.core.StreamReadCapability +com.fasterxml.jackson.core.util.JacksonFeatureSet +com.fasterxml.jackson.core.JsonToken +com.fasterxml.jackson.databind.util.ArrayIterator +com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer +com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer +com.fasterxml.jackson.databind.node.ArrayNode +com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer$ObjectDeserializer +com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer$ArrayDeserializer +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$WeightedValue +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$Node +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$AddTask +com.fasterxml.jackson.databind.util.LinkedNode +com.fasterxml.jackson.databind.annotation.JsonTypeResolver +com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer$ContainerStack +com.fasterxml.jackson.core.util.InternCache +com.fasterxml.jackson.databind.node.JsonNodeType +org.apache.avro.Schema$Name +java.lang.invoke.LambdaForm$MH/0x00007f4204348000 +java.lang.invoke.LambdaForm$MH/0x00007f4204348400 +java.lang.invoke.LambdaForm$MH/0x00007f4204348800 +java.lang.invoke.LambdaForm$MH/0x00007f4204348c00 +java.lang.invoke.LambdaForm$MH/0x00007f4204349000 +java.lang.invoke.LambdaForm$MH/0x00007f4204349400 +java.lang.invoke.LambdaForm$MH/0x00007f4204349800 +org.apache.avro.JsonProperties$2 +org.apache.avro.Schema$Field$Order +org.apache.avro.util.internal.Accessor$FieldAccessor +org.apache.avro.Schema$Field$1 +org.apache.avro.Schema$$Lambda$766/0x00007f4204345390 +org.apache.avro.util.MapEntry +org.apache.avro.LogicalTypes +org.apache.avro.LogicalType +org.apache.avro.LogicalTypes$Uuid +org.apache.avro.LogicalTypes$Duration +org.apache.avro.LogicalTypes$TimestampMillis +org.apache.avro.LogicalTypes$Decimal +org.apache.avro.LogicalTypes$BigDecimal +org.apache.avro.LogicalTypes$Date +org.apache.avro.LogicalTypes$TimestampMicros +org.apache.avro.LogicalTypes$TimestampNanos +org.apache.avro.LogicalTypes$TimeMillis +org.apache.avro.LogicalTypes$TimeMicros +org.apache.avro.LogicalTypes$LocalTimestampMicros +org.apache.avro.LogicalTypes$LocalTimestampMillis +org.apache.avro.LogicalTypes$LocalTimestampNanos +org.apache.avro.LogicalTypes$LogicalTypeFactory +org.apache.avro.Schema$LockableArrayList +org.apache.avro.util.SchemaResolver$ResolvingVisitor +org.apache.avro.ParseContext$$Lambda$767/0x00007f420434c240 +org.apache.avro.ParseContext$$Lambda$768/0x00007f420434c488 +org.apache.avro.util.Schemas +org.apache.avro.util.Schemas$1 +org.apache.avro.util.SchemaVisitor$SchemaVisitorAction +org.apache.avro.util.Schemas$$Lambda$769/0x00007f420434cf10 +org.apache.avro.util.SchemaResolver +org.apache.avro.util.Schemas$$Lambda$770/0x00007f420434d360 +org.apache.avro.util.Schemas$$Lambda$771/0x00007f420434d588 +org.apache.avro.util.Schemas$$Lambda$772/0x00007f420434d7c0 +org.apache.avro.util.Schemas$$Lambda$773/0x00007f420434da00 +java.util.ArrayDeque$DescendingIterator +org.apache.avro.util.SchemaResolver$1 +org.apache.avro.JsonProperties$2$1 +org.apache.avro.JsonProperties$2$1$1 +org.apache.avro.util.SchemaResolver$ResolvingVisitor$$Lambda$774/0x00007f420434e4a0 +org.apache.avro.util.SchemaResolver$ResolvingVisitor$$Lambda$775/0x00007f420434e6d8 +org.apache.avro.util.SchemaResolver$ResolvingVisitor$$Lambda$776/0x00007f420434e910 +org.apache.avro.util.SchemaResolver$ResolvingVisitor$$Lambda$777/0x00007f420434eb48 +org.apache.avro.ParseContext$$Lambda$778/0x00007f420434ed70 +org.apache.avro.UnresolvedUnionException +org.apache.avro.AvroMissingFieldException +org.apache.avro.specific.ExternalizableInput +org.apache.avro.specific.ExternalizableOutput +org.apache.avro.util.springframework.ConcurrentReferenceHashMap +org.apache.avro.util.springframework.ConcurrentReferenceHashMap$Task +org.apache.avro.util.springframework.ConcurrentReferenceHashMap$2 +org.apache.avro.util.springframework.ConcurrentReferenceHashMap$3 +org.apache.avro.util.springframework.ConcurrentReferenceHashMap$1 +org.apache.avro.util.springframework.ConcurrentReferenceHashMap$4 +org.apache.avro.util.springframework.ConcurrentReferenceHashMap$5 +org.apache.avro.util.springframework.ConcurrentReferenceHashMap$ReferenceType +org.apache.avro.util.springframework.Assert +org.apache.avro.util.springframework.ConcurrentReferenceHashMap$Segment +org.apache.avro.util.springframework.ConcurrentReferenceHashMap$ReferenceManager +org.apache.avro.util.springframework.ConcurrentReferenceHashMap$Reference +org.apache.avro.util.Utf8 +org.apache.avro.util.internal.ClassValueCache +org.apache.avro.util.internal.ClassValueCache$1 +org.apache.avro.specific.SpecificData$$Lambda$779/0x00007f42043504a8 +org.apache.avro.specific.SpecificData$$Lambda$780/0x00007f42043506f0 +org.apache.avro.specific.SpecificData$$Lambda$781/0x00007f4204350930 +org.apache.avro.specific.SpecificData$1 +org.apache.avro.message.RawMessageEncoder +org.apache.avro.message.BinaryMessageEncoder$V1MessageEncoder +org.apache.avro.message.RawMessageEncoder$BufferOutputStream +java.security.GeneralSecurityException +java.security.NoSuchAlgorithmException +org.apache.avro.message.RawMessageEncoder$$Lambda$782/0x00007f42043514c8 +org.apache.avro.generic.GenericDatumWriter +org.apache.avro.specific.SpecificDatumWriter +org.apache.avro.path.PathTracingException +org.apache.avro.path.TracingNullPointException +org.apache.avro.path.TracingClassCastException +org.apache.avro.path.TracingAvroTypeException +org.apache.avro.path.PathElement +java.util.ConcurrentModificationException +org.apache.avro.SchemaNormalization +org.apache.avro.SchemaNormalization$1 +java.lang.invoke.LambdaForm$MH/0x00007f4204354000 +java.lang.invoke.LambdaForm$MH/0x00007f4204354400 +java.lang.invoke.LambdaForm$MH/0x00007f4204354800 +java.lang.invoke.LambdaForm$MH/0x00007f4204354c00 +java.lang.invoke.LambdaForm$MH/0x00007f4204355000 +java.lang.invoke.LambdaForm$MH/0x00007f4204355400 +org.apache.avro.SchemaNormalization$FP64 +org.apache.avro.util.ReusableByteArrayInputStream +org.apache.avro.util.ReusableByteBufferInputStream +org.apache.avro.message.BadHeaderException +org.apache.avro.message.MissingSchemaException +org.apache.avro.message.MessageDecoder$BaseDecoder$$Lambda$783/0x00007f42043538f8 +org.apache.avro.message.MessageDecoder$BaseDecoder$$Lambda$784/0x00007f4204353b18 +org.apache.avro.message.BinaryMessageDecoder$$Lambda$785/0x00007f4204353d38 +org.apache.avro.message.BinaryMessageDecoder$$Lambda$786/0x00007f4204356000 +org.apache.avro.message.RawMessageDecoder +org.apache.avro.generic.GenericDatumReader +org.apache.avro.specific.SpecificDatumReader +org.apache.avro.util.WeakIdentityHashMap +org.apache.avro.generic.GenericDatumReader$$Lambda$787/0x00007f4204356ee8 +org.apache.avro.generic.GenericDatumReader$ReaderCache +org.apache.avro.generic.GenericDatumReader$$Lambda$788/0x00007f4204357328 +java.util.Base64 +java.util.Base64$Encoder +org.apache.avro.io.EncoderFactory +org.apache.avro.io.EncoderFactory$DefaultEncoderFactory +org.apache.avro.io.DirectBinaryEncoder +org.apache.avro.io.BufferedBinaryEncoder +org.apache.avro.io.BlockingDirectBinaryEncoder +org.apache.avro.io.BlockingBinaryEncoder +org.apache.avro.io.BufferedBinaryEncoder$ByteSink +org.apache.avro.io.BufferedBinaryEncoder$OutputStreamSink +java.nio.channels.Channels +java.nio.channels.InterruptibleChannel +java.nio.channels.spi.AbstractInterruptibleChannel +java.nio.channels.Channels$WritableByteChannelImpl +org.apache.avro.generic.GenericDatumWriter$1 +org.apache.avro.io.BinaryData +org.apache.avro.io.BinaryData$Decoders +org.apache.avro.io.BinaryData$$Lambda$789/0x00007f4204358b68 +org.apache.avro.io.BinaryData$HashData +org.apache.avro.io.BinaryData$$Lambda$790/0x00007f4204358fa0 +software.amazon.lambda.powertools.kafka.testutils.TestUtils$1 +com.amazonaws.services.lambda.runtime.events.KafkaEvent +com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper +com.fasterxml.jackson.core.json.UTF8StreamJsonParser +com.fasterxml.jackson.core.io.MergedStream +com.fasterxml.jackson.core.io.UTF32Reader +java.io.CharConversionException +com.fasterxml.jackson.core.JsonEncoding +com.fasterxml.jackson.databind.type.ClassStack +com.fasterxml.jackson.databind.introspect.AnnotationCollector$OneCollector +com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector +com.fasterxml.jackson.annotation.JsonAutoDetect +com.fasterxml.jackson.annotation.JsonIdentityInfo +com.fasterxml.jackson.databind.ext.OptionalHandlerFactory +org.w3c.dom.Node +org.w3c.dom.Document +com.fasterxml.jackson.databind.ext.Java7Handlers +com.fasterxml.jackson.databind.ext.Java7HandlersImpl +com.fasterxml.jackson.databind.ext.NioPathSerializer +com.fasterxml.jackson.databind.ext.NioPathDeserializer +com.fasterxml.jackson.databind.deser.std.JdkDeserializers +com.fasterxml.jackson.databind.deser.std.FromStringDeserializer +com.fasterxml.jackson.databind.deser.std.UUIDDeserializer +com.fasterxml.jackson.databind.deser.std.AtomicBooleanDeserializer +com.fasterxml.jackson.databind.deser.std.AtomicIntegerDeserializer +com.fasterxml.jackson.databind.deser.std.AtomicLongDeserializer +com.fasterxml.jackson.databind.deser.std.ByteBufferDeserializer +com.fasterxml.jackson.databind.deser.std.NullifyingDeserializer +com.fasterxml.jackson.databind.deser.std.StdNodeBasedDeserializer +com.fasterxml.jackson.databind.deser.std.ThreadGroupDeserializer +com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$StringBuilderDeserializer +com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$StringBufferDeserializer +com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$Std +java.net.InetAddress +com.fasterxml.jackson.databind.jsontype.impl.SubTypeValidator +com.fasterxml.jackson.databind.util.BeanUtil +com.fasterxml.jackson.databind.annotation.JsonValueInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators +com.fasterxml.jackson.databind.deser.ValueInstantiator +com.fasterxml.jackson.databind.deser.ValueInstantiator$Base +com.fasterxml.jackson.databind.deser.std.JsonLocationInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$JDKValueInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$ArrayListInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$HashSetInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$LinkedListInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$TreeSetInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$LinkedHashSetInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$ConstantValueInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$LinkedHashMapInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$HashMapInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$ConcurrentHashMapInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$TreeMapInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$PropertiesInstantiator +com.fasterxml.jackson.core.JsonLocation +com.fasterxml.jackson.databind.introspect.PotentialCreators +com.fasterxml.jackson.databind.introspect.CollectorBase +com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector +com.fasterxml.jackson.databind.introspect.AnnotationMap +com.fasterxml.jackson.databind.introspect.TypeResolutionContext$Basic +com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector$FieldBuilder +com.fasterxml.jackson.annotation.JsonKey +com.fasterxml.jackson.annotation.JsonValue +com.fasterxml.jackson.annotation.JsonAnyGetter +com.fasterxml.jackson.annotation.JsonAnySetter +com.fasterxml.jackson.databind.PropertyName +com.fasterxml.jackson.annotation.JsonSetter +com.fasterxml.jackson.annotation.JsonProperty +com.fasterxml.jackson.annotation.JsonAutoDetect$1 +com.fasterxml.jackson.annotation.PropertyAccessor +com.fasterxml.jackson.annotation.JsonIgnore +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$WithMember +com.fasterxml.jackson.databind.AnnotationIntrospector$ReferenceProperty +com.fasterxml.jackson.databind.AnnotationIntrospector$ReferenceProperty$Type +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$Linked +com.fasterxml.jackson.databind.introspect.AnnotatedMethodCollector +com.amazonaws.services.lambda.runtime.events.KafkaEvent$KafkaEventBuilder +com.fasterxml.jackson.databind.introspect.MemberKey +com.fasterxml.jackson.databind.introspect.AnnotatedMethodCollector$MethodBuilder +com.fasterxml.jackson.databind.introspect.AnnotatedMethodMap +com.fasterxml.jackson.annotation.JsonGetter +com.fasterxml.jackson.databind.introspect.AnnotatedCreatorCollector +com.fasterxml.jackson.databind.introspect.MethodGenericTypeResolver +com.fasterxml.jackson.annotation.JsonCreator +com.fasterxml.jackson.databind.introspect.PotentialCreator +com.fasterxml.jackson.annotation.JsonCreator$Mode +com.fasterxml.jackson.databind.cfg.ConstructorDetector +com.fasterxml.jackson.databind.cfg.ConstructorDetector$SingleArgConstructor +sun.reflect.generics.scope.ConstructorScope +com.amazonaws.services.lambda.runtime.events.KafkaEvent$KafkaEventRecord +com.fasterxml.jackson.databind.type.TypeBindings$TypeParamStash +com.fasterxml.jackson.databind.type.TypeBindings$AsKey +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$5 +com.fasterxml.jackson.annotation.JsonProperty$Access +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$6 +com.fasterxml.jackson.annotation.JacksonInject +com.fasterxml.jackson.databind.annotation.JsonNaming +com.fasterxml.jackson.annotation.JsonPropertyOrder +com.fasterxml.jackson.annotation.JsonPropertyDescription +com.fasterxml.jackson.databind.PropertyMetadata +com.fasterxml.jackson.databind.deser.impl.CreatorCollector +com.fasterxml.jackson.databind.deser.std.StdValueInstantiator +com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder +com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty +com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer +com.fasterxml.jackson.annotation.JsonIgnoreProperties +com.fasterxml.jackson.annotation.JsonIgnoreProperties$Value +com.fasterxml.jackson.annotation.JsonIncludeProperties +com.fasterxml.jackson.annotation.JsonIncludeProperties$Value +com.fasterxml.jackson.databind.util.IgnorePropertiesUtil +com.fasterxml.jackson.annotation.JsonIgnoreType +com.fasterxml.jackson.databind.deser.impl.FailingDeserializer +com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider +com.fasterxml.jackson.databind.util.AccessPattern +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$2 +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$4 +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$1 +com.fasterxml.jackson.annotation.JsonAlias +com.fasterxml.jackson.annotation.JsonFormat$Feature +com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap +com.fasterxml.jackson.databind.exc.IgnoredPropertyException +com.fasterxml.jackson.databind.deser.SettableBeanProperty$Delegating +com.fasterxml.jackson.databind.deser.impl.ManagedReferenceProperty +com.fasterxml.jackson.databind.deser.impl.ObjectIdReferenceProperty +com.fasterxml.jackson.databind.deser.impl.InnerClassProperty +com.fasterxml.jackson.databind.deser.impl.MergingSettableBeanProperty +com.fasterxml.jackson.databind.deser.impl.ReadableObjectId$Referring +com.fasterxml.jackson.databind.deser.BeanDeserializer$BeanReferring +com.fasterxml.jackson.databind.deser.impl.BeanAsArrayDeserializer +com.fasterxml.jackson.databind.deser.BasicDeserializerFactory$ContainerDefaultMappings +java.util.concurrent.ConcurrentNavigableMap +java.util.concurrent.ConcurrentSkipListMap +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringKD +com.fasterxml.jackson.databind.deser.ContextualKeyDeserializer +com.amazonaws.services.lambda.runtime.events.KafkaEvent$SchemaMetadata +com.amazonaws.services.lambda.runtime.events.KafkaEvent$KafkaEventRecord$KafkaEventRecordBuilder +sun.reflect.generics.tree.IntSignature +sun.reflect.generics.tree.LongSignature +sun.reflect.generics.tree.ByteSignature +com.fasterxml.jackson.databind.deser.std.NumberDeserializers +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$PrimitiveOrWrapperDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$IntegerDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BooleanDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$LongDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$DoubleDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$CharacterDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$ByteDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$ShortDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$FloatDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$NumberDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BigDecimalDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BigIntegerDeserializer +com.amazonaws.services.lambda.runtime.events.KafkaEvent$SchemaMetadata$SchemaMetadataBuilder +jdk.internal.reflect.GeneratedMethodAccessor8 +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$IntDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$LongDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$ByteDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$ShortDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$FloatDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$DoubleDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$BooleanDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$CharDeser +com.fasterxml.jackson.databind.exc.InvalidNullException +com.fasterxml.jackson.databind.annotation.JacksonStdImpl +com.fasterxml.jackson.annotation.JacksonAnnotation +jdk.proxy2.$Proxy73 +com.fasterxml.jackson.core.io.NumberInput +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializer$SchemaRegistryType +java.util.Base64$Decoder +org.apache.avro.specific.SpecificData$2 +org.apache.avro.specific.SpecificData$$Lambda$791/0x00007f420437eb90 +org.apache.avro.util.MapUtil +org.apache.avro.util.MapUtil$$Lambda$792/0x00007f420437efe0 +org.apache.avro.util.ClassUtils +org.apache.avro.io.DecoderFactory +org.apache.avro.io.DecoderFactory$DefaultDecoderFactory +org.apache.avro.io.DirectBinaryDecoder +org.apache.avro.InvalidNumberEncodingException +org.apache.avro.io.BinaryDecoder$ByteSource +org.apache.avro.io.BinaryDecoder$InputStreamByteSource +org.apache.avro.io.BinaryDecoder$ByteArrayByteSource +org.apache.avro.io.BinaryDecoder$BufferAccessor +org.apache.avro.util.WeakIdentityHashMap$IdentityWeakReference +org.apache.avro.io.parsing.ValidatingGrammarGenerator +org.apache.avro.io.parsing.ResolvingGrammarGenerator +org.apache.avro.util.internal.Accessor$ResolvingGrammarGeneratorAccessor +org.apache.avro.io.parsing.ResolvingGrammarGenerator$1 +org.apache.avro.io.parsing.Symbol +org.apache.avro.io.parsing.Symbol$ImplicitAction +org.apache.avro.io.parsing.Symbol$SkipAction +org.apache.avro.Resolver +org.apache.avro.Resolver$Action +org.apache.avro.Resolver$DoNothing +org.apache.avro.Resolver$ErrorAction +org.apache.avro.Resolver$Container +org.apache.avro.Resolver$1 +org.apache.avro.Resolver$RecordAdjust +org.apache.avro.Schema$SeenPair +org.apache.avro.Resolver$Action$Type +org.apache.avro.generic.GenericData$InstanceSupplier +java.lang.invoke.LambdaForm$DMH/0x00007f4204384000 +org.apache.avro.generic.GenericData$$Lambda$793/0x00007f4204380200 +org.apache.avro.Resolver$Skip +org.apache.avro.Resolver$Promote +org.apache.avro.Resolver$ReaderUnion +org.apache.avro.Resolver$EnumAdjust +org.apache.avro.io.parsing.Symbol$Terminal +org.apache.avro.io.parsing.Symbol$WriterUnionAction +org.apache.avro.io.parsing.Symbol$Repeater +org.apache.avro.io.parsing.Symbol$ResolvingAction +org.apache.avro.io.parsing.Symbol$Root +org.apache.avro.io.parsing.Symbol$Sequence +org.apache.avro.io.parsing.Symbol$ErrorAction +org.apache.avro.io.parsing.Symbol$Alternative +org.apache.avro.io.parsing.Symbol$Kind +org.apache.avro.io.parsing.Symbol$FieldOrderAction +org.apache.avro.io.parsing.ResolvingGrammarGenerator$2 +org.apache.avro.io.parsing.Parser +org.apache.avro.io.parsing.SkipParser +org.apache.avro.generic.GenericDatumReader$1 +org.apache.avro.generic.GenericData$StringType +org.apache.avro.specific.SpecificData$SchemaConstructable +org.apache.avro.generic.GenericDatumReader$IdentitySchemaKey +org.apache.avro.generic.GenericDatumReader$ReaderCache$$Lambda$794/0x00007f4204383748 +org.apache.avro.specific.SpecificDatumReader$1 +org.apache.avro.SystemLimitException +org.apache.kafka.common.header.internals.RecordHeaders +org.apache.kafka.common.header.Header +org.apache.kafka.clients.consumer.ConsumerRecord +org.apache.kafka.common.record.TimestampType +org.apache.kafka.common.TopicPartition +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializer$$Lambda$795/0x00007f4204386b98 +org.assertj.core.api.FactoryBasedNavigableIterableAssert +org.assertj.core.api.IterableAssert +org.assertj.core.api.AbstractIterableSizeAssert +org.assertj.core.api.IterableSizeAssert +org.assertj.core.api.AssertFactory +org.assertj.core.api.IterableAssert$$Lambda$796/0x00007f4204387b40 +org.assertj.core.internal.Iterables +org.assertj.core.internal.Predicates +org.assertj.core.api.ListAssert$$Lambda$797/0x00007f420438b3e0 +org.assertj.core.internal.Lists +org.assertj.core.util.IterableUtil +org.assertj.core.internal.WholeNumbers +org.assertj.core.internal.Numbers +org.assertj.core.internal.Integers +org.assertj.core.internal.RealNumbers +org.assertj.core.internal.Doubles +org.assertj.core.internal.ComparatorBasedComparisonStrategy +org.junit.jupiter.engine.descriptor.TestTemplateExtensionContext$$Lambda$798/0x00007f420438cdf8 +org.junit.platform.engine.support.hierarchical.NodeTestTask$DefaultDynamicTestExecutor$$Lambda$799/0x00007f420438d038 +com.amazonaws.services.lambda.runtime.serialization.factories.PojoSerializerFactory +com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory +com.amazonaws.services.lambda.runtime.serialization.PojoSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.Versioned +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.Module +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.module.SimpleModule +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.Jdk8Module +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.TokenStreamFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.DataOutputAsStream +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.SerializableString +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.TSFBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonFactoryBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.async.NonBlockingInputFeeder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.async.ByteBufferFeeder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.base.ParserMinimalBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.base.ParserBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.async.NonBlockingJsonParserBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.async.NonBlockingUtf8JsonParserBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.async.NonBlockingByteBufferJsonParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.UTF8DataInputJsonParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.ReaderBasedJsonParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.base.GeneratorBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.JsonGeneratorImpl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.WriterBasedJsonGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.UTF8JsonGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.UTF8Writer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.async.ByteArrayFeeder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.async.NonBlockingJsonParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.JacksonFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonFactory$Feature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonParser$Feature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonGenerator$Feature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.PrettyPrinter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.Instantiatable +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.DefaultPrettyPrinter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.DefaultPrettyPrinter$Indenter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.SerializedString +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.JsonStringEncoder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.CharTypes +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.FormatFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.JsonReadFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.JsonWriteFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer$TableInfo +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer$Bucket +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer$TableInfo +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.TreeCodec +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.ObjectCodec +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectMapper +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.json.JsonMapper +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.MappingJsonFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.SubtypeResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.impl.StdSubtypeResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DatabindContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.SerializerProvider +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.DefaultSerializerProvider +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.DefaultSerializerProvider$Impl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.DeserializerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BasicDeserializerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BeanDeserializerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DeserializationContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.DefaultDeserializationContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.DefaultDeserializationContext$Impl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.SerializerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BasicSerializerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BeanSerializerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.AnnotationIntrospector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy$Provider +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy$Provider +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.StdDateFormat +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JacksonException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonProcessingException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DatabindException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonMappingException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.SegmentedStringWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.ByteArrayBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.TreeNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonSerializable +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonSerializable$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.BaseJsonNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ValueNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.NullNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.ClassIntrospector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.BasicClassIntrospector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.Module$SetupContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.VisibilityChecker +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.TreeTraversingParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.TokenBuffer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.type.ResolvedType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JavaType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.ArrayType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.CollectionLikeType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.CollectionType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.MapLikeType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.MapType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.MismatchedInputException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.Annotated +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.TypeResolutionContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedClass +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedMember +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.VirtualAnnotatedMember +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.Named +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.SimpleBeanPropertyDefinition +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.BeanProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.ConcreteBeanPropertyBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.PropertyWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BeanPropertyWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.AttributePropertyWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedWithParams +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedMethod +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JsonSerialize +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonView +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonFormat +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonTypeInfo +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonRawValue +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonUnwrapped +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonBackReference +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonManagedReference +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JsonDeserialize +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonMerge +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7Support +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.ClassUtil +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.ClassUtil$Ctor +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.LookupCache +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.LRUMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$Builder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.Linked +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.LinkedDeque +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$2 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$3 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.BaseSettings +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.SimpleType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.PlaceholderForType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.ReferenceType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.ResolvedRecursiveType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeBindings +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.Base64Variants +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.Base64Variant +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.Base64Variant$PaddingReadBehaviour +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy$RecordNaming +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.MapperBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.json.JsonMapper$Builder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.RootNameLookup +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.ClassIntrospector$MixInResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.SimpleMixInResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.BeanDescription +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.BasicBeanDescription +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.MapperConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.MapperConfigBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.SerializationConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DeserializationConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.Annotations +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationCollector$EmptyCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationCollector$NoAnnotations +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedClass$Creators +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedConstructor +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedParameter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ConfigOverrides +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JacksonAnnotationValue +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonInclude$Value +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonInclude$Include +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonSetter$Value +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.Nulls +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.VisibilityChecker$Std +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.CoercionConfigs +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.LogicalType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.CoercionAction +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.CoercionConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.MutableCoercionConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.CoercionInputShape +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.DefaultBaseTypeLimitingValidator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonFormat$Value +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonFormat$Shape +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonFormat$Features +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ConfigOverride +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ConfigOverride$Empty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ConfigFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.MapperFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.DefaultPrettyPrinter$NopIndenter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.DefaultPrettyPrinter$FixedSpaceIndenter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.DefaultIndenter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.Separators +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.SerializationFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.DatatypeFeatures +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.DatatypeFeatures$DefaultHolder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.DatatypeFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.EnumFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.JsonNodeFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ContextAttributes +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ContextAttributes$Impl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DeserializationFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.JsonNodeCreator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.JsonNodeFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.MissingNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.BooleanNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.NumericNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.DecimalNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.DoubleNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.IntNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ShortNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.FloatNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.LongNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.BigIntegerNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.TextNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.BinaryNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.POJONode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsonschema.SchemaAware +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NullSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.FailingSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ToEmptyObjectSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.UnknownSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.ContextualSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.InvalidDefinitionException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.InvalidTypeIdException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ContainerNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ObjectNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.ResolvableSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.SerializerCache +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.NullValueProvider +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.PropertyBindingException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.InvalidFormatException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.ValueInstantiationException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.UnresolvedForwardReference +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ContextualDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ResolvableDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiator$Gettable +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.EnumMapDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.AbstractDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.MapDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StringDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.MapEntryDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.TokenBufferDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.SettableBeanProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.CreatorProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.EnumDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.ReferenceTypeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.AtomicReferenceDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.EnumSetDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.CollectionDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.ArrayBlockingQueueDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StringCollectionDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.MethodProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.FieldProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.SetterlessProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.UnsupportedTypeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.ErrorThrowingDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.ObjectIdGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.ObjectIdGenerators$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.ObjectIdGenerators$PropertyGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BeanDeserializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BeanDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.ThrowableDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.PropertyName +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BeanDeserializerModifier +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.AbstractTypeResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiators +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.KeyDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.KeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$DelegatingKD +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$EnumKD +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringCtorKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringFactoryKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.DeserializerCache +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdDelegatingDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.JsonValueSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.SerializableSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdScalarSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.DateTimeSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.CalendarSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.DateSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ByteBufferSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.InetAddressSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.InetSocketAddressSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.TimeZoneSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ToStringSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ToStringSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.ContainerSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.CollectionSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StaticListSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.IndexedStringListSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.StringCollectionSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.EnumSetSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.MapSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.MapEntrySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ArraySerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.StringArraySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ReferenceTypeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.IteratorSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.IterableSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.EnumSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.BeanSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BeanSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.PropertyBasedObjectIdGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedField +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StringSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$IntegerSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonParser$NumberType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$LongSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$IntLikeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$ShortSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$DoubleSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$FloatSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.BooleanSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.BooleanSerializer$AsNumber +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializer$BigDecimalAsStringSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdJdkSerializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.UUIDSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicBooleanSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicIntegerSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicLongSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.FileSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ClassSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.TokenBufferSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BeanSerializerModifier +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.module.SimpleAbstractTypeResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.JSR310DeserializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.JSR310DateTimeDeserializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.MonthDayDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.OffsetTimeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.YearDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.YearMonthDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.JSR310SerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.JSR310FormattedSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.DurationSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.MonthDaySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.OffsetDateTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.OffsetTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.YearSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.YearMonthSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.ZonedDateTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.ZoneIdSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.key.ZonedDateTimeKeySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.Jsr310KeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.DurationKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.InstantKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.LocalDateTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.LocalDateKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.LocalTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.MonthDayKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.OffsetDateTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.OffsetTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.PeriodKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.YearKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.YearMonthKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.ZonedDateTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.ZoneIdKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.ZoneOffsetKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.PackageVersion +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.VersionUtil +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.Version +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.exc.StreamReadException +java.util.regex.Pattern$BnM +java.util.regex.Pattern$Pos +java.time.format.DateTimeFormatter +java.time.format.DateTimeFormatterBuilder +java.time.temporal.TemporalQuery +java.time.format.DateTimeFormatterBuilder$$Lambda$10/0x80000000e +java.time.temporal.IsoFields +java.time.temporal.IsoFields$Field +java.time.temporal.IsoFields$Field$1 +java.time.temporal.IsoFields$Field$2 +java.time.temporal.IsoFields$Field$3 +java.time.temporal.IsoFields$Field$4 +java.time.temporal.IsoFields$Unit +java.time.temporal.JulianFields +java.time.temporal.JulianFields$Field +java.time.format.SignStyle +java.time.format.DateTimeFormatterBuilder$DateTimePrinterParser +java.time.format.DateTimeFormatterBuilder$NumberPrinterParser +java.time.format.DateTimeFormatterBuilder$CharLiteralPrinterParser +java.time.format.ResolverStyle +java.time.chrono.Chronology +java.time.chrono.AbstractChronology +java.time.chrono.IsoChronology +java.time.format.DateTimeFormatterBuilder$CompositePrinterParser +java.time.format.DecimalStyle +java.time.format.DateTimeFormatterBuilder$SettingsParser +java.time.format.DateTimeFormatterBuilder$OffsetIdPrinterParser +java.time.format.DateTimeFormatterBuilder$FractionPrinterParser +java.time.format.DateTimeFormatterBuilder$ZoneIdPrinterParser +java.time.format.DateTimeFormatterBuilder$StringLiteralPrinterParser +java.time.format.DateTimeFormatterBuilder$InstantPrinterParser +java.time.format.TextStyle +java.time.format.DateTimeTextProvider$LocaleStore +java.time.format.DateTimeTextProvider +java.time.format.DateTimeTextProvider$1 +java.time.format.DateTimeFormatterBuilder$1 +java.time.format.DateTimeFormatterBuilder$TextPrinterParser +java.time.chrono.ChronoPeriod +java.time.Period +java.time.format.DateTimeFormatter$$Lambda$8/0x80000000c +java.time.format.DateTimeFormatter$$Lambda$9/0x80000000d +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$803/0x00007f42043e9648 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$FromIntegerArguments +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$804/0x00007f42043e9a98 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$FromDecimalArguments +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$805/0x00007f42043e9ee8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$806/0x00007f42043ea128 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$807/0x00007f42043ea358 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$808/0x00007f42043ea598 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$809/0x00007f42043ea7d8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$810/0x00007f42043eaa18 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$811/0x00007f42043eac48 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$812/0x00007f42043eae88 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$813/0x00007f42043eb0c8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$814/0x00007f42043eb308 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.module.SimpleDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.ClassKey +java.time.MonthDay +java.time.OffsetTime +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.JSR310StringParsableDeserializer +java.time.Year +java.time.YearMonth +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.module.SimpleSerializers +java.util.function.ToLongFunction +java.lang.invoke.LambdaForm$DMH/0x00007f42043f0000 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer$$Lambda$815/0x00007f42043ec758 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer$$Lambda$816/0x00007f42043ec978 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer$$Lambda$817/0x00007f42043ecb98 +java.lang.invoke.LambdaForm$DMH/0x00007f42043f0400 +java.lang.invoke.LambdaForm$DMH/0x00007f42043f0800 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.OffsetDateTimeSerializer$$Lambda$818/0x00007f42043ecdb8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.OffsetDateTimeSerializer$$Lambda$819/0x00007f42043ecfd8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.OffsetDateTimeSerializer$$Lambda$820/0x00007f42043ed1f8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.ZonedDateTimeSerializer$$Lambda$821/0x00007f42043ed418 +java.lang.invoke.LambdaForm$DMH/0x00007f42043f0c00 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.ZonedDateTimeSerializer$$Lambda$822/0x00007f42043ed638 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.ZonedDateTimeSerializer$$Lambda$823/0x00007f42043ed858 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.module.SimpleKeyDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectMapper$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.ArrayBuilders +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiators$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.JavaTimeModule$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeModifier +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.Jdk8TypeModifier +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.Jdk8BeanSerializerModifier +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.PackageVersion +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.Jdk8Serializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalIntSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalLongSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalDoubleSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.LongStreamSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.IntStreamSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.DoubleStreamSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.StreamSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.Jdk8Deserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.BaseScalarOptionalDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalIntDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalLongDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalDoubleDeserializer +com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory$InternalSerializer +com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory$TypeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.ClassStack +java.util.OptionalLong +java.util.OptionalDouble +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$WeightedValue +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$Node +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$AddTask +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectReader +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.filter.TokenFilter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.filter.JsonPointerBasedFilter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ArrayNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.JsonParserDelegate +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.filter.FilteringParserDelegate +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonParseException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationCollector$OneCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonAutoDetect +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIdentityInfo +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.ArrayIterator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.OptionalHandlerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7Handlers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.NioPathSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.NioPathDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.JdkDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.FromStringDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.UUIDDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.AtomicBooleanDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.AtomicIntegerDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.AtomicLongDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.ByteBufferDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NullifyingDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$StringBuilderDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$StringBufferDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$Std +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.impl.SubTypeValidator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.BeanUtil +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JsonValueInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiator$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.JsonLocationInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$ArrayListInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$ConstantValueInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$LinkedHashMapInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$HashMapInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonLocation +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ConstructorDetector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ConstructorDetector$SingleArgConstructor +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.CreatorCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdValueInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.CollectorBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.TypeResolutionContext$Basic +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector$FieldBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonKey +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonValue +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonAnyGetter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonAnySetter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.InternCache +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonSetter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonAutoDetect$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.PropertyAccessor +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIgnore +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$WithMember +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.AnnotationIntrospector$ReferenceProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.AnnotationIntrospector$ReferenceProperty$Type +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$Linked +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedMethodCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.MemberKey +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedMethodCollector$MethodBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedMethodMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonGetter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedCreatorCollector +sun.reflect.generics.tree.DoubleSignature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeBindings$TypeParamStash +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeBindings$AsKey +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$5 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonProperty$Access +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$6 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JacksonInject +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JsonNaming +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonPropertyOrder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonPropertyDescription +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.PropertyMetadata +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BasicDeserializerFactory$CreatorCollectionState +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonCreator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonCreator$Mode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIgnoreProperties +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIgnoreProperties$Value +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIncludeProperties +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIncludeProperties$Value +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.IgnorePropertiesUtil +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIgnoreType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JsonTypeResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.FailingDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.AccessPattern +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$2 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$4 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonAlias +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonFormat$Feature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.IgnoredPropertyException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.SettableBeanProperty$Delegating +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.ManagedReferenceProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.ObjectIdReferenceProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.InnerClassProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.MergingSettableBeanProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.ReadableObjectId$Referring +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BeanDeserializer$BeanReferring +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.BeanAsArrayDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$PrimitiveOrWrapperDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$IntegerDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BooleanDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$LongDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$DoubleDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$CharacterDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$ByteDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$ShortDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$FloatDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$NumberDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BigDecimalDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BigIntegerDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BasicDeserializerFactory$ContainerDefaultMappings +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.LinkedNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JacksonStdImpl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JacksonAnnotation +jdk.proxy2.$Proxy74 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.MinimalPrettyPrinter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectWriter$GeneratorSettings +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectWriter$Prefetch +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.RuntimeJsonMappingException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonEncoding +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.ContentReference +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.IOContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.BufferRecyclers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.BufferRecycler +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.exc.StreamWriteException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonGenerationException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonStreamContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.JsonWriteContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.StreamWriteCapability +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.JacksonFeatureSet +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap$Bucket +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.TypeKey +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap$$Lambda$824/0x00007f4204413340 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$EntrySet +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$EntryIterator +java.util.stream.LongStream +java.util.stream.DoubleStream +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BeanSerializerBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.PropertyBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonInclude +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$3 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonTypeId +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.BeanProperty$Std +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.PropertyBuilder$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Empty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Single +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JsonAppend +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonFilter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.BeanAsArraySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.NumberOutput +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$SerializerAndMapResult +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Double +org.assertj.core.internal.Strings$$Lambda$825/0x00007f42044178b8 +org.assertj.core.internal.Strings$$Lambda$826/0x00007f4204417b10 +jdk.internal.reflect.GeneratedConstructorAccessor11 +com.google.protobuf.RuntimeVersion$RuntimeDomain +com.google.protobuf.RuntimeVersion +com.google.protobuf.RuntimeVersion$ProtobufRuntimeVersionException +com.google.protobuf.AbstractParser +com.google.protobuf.UnknownFieldSet$Parser +com.google.protobuf.AbstractMessageLite$Builder$LimitedInputStream +com.google.protobuf.Protobuf +com.google.protobuf.SchemaFactory +com.google.protobuf.ManifestSchemaFactory +com.google.protobuf.MessageInfoFactory +com.google.protobuf.ManifestSchemaFactory$1 +com.google.protobuf.ManifestSchemaFactory$CompositeMessageInfoFactory +com.google.protobuf.GeneratedMessageInfoFactory +com.google.protobuf.DescriptorMessageInfoFactory +com.google.protobuf.Internal$EnumVerifier +com.google.protobuf.MessageInfo +com.google.protobuf.DescriptorMessageInfoFactory$IsInitializedCheckAnalyzer +com.google.protobuf.FieldType +com.google.protobuf.Internal$EnumLite +com.google.protobuf.ProtocolMessageEnum +com.google.protobuf.DescriptorProtos$Edition +com.google.protobuf.ProtoSyntax +com.google.protobuf.DescriptorMessageInfoFactory$OneofState +com.google.protobuf.FieldInfo +com.google.protobuf.Internal +com.google.protobuf.CodedInputStream$ArrayDecoder +com.google.protobuf.CodedInputStream$UnsafeDirectNioDecoder +com.google.protobuf.CodedInputStream$IterableDirectByteBufferDecoder +com.google.protobuf.IterableByteBufferInputStream +com.google.protobuf.CodedInputStream$StreamDecoder +com.google.protobuf.InvalidProtocolBufferException$InvalidWireTypeException +com.google.protobuf.ExtensionRegistryFactory +com.google.protobuf.ExtensionRegistry$ExtensionInfo +com.google.protobuf.Extension$ExtensionType +software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProduct$1 +com.google.protobuf.CodedOutputStream$AbstractBufferedEncoder +com.google.protobuf.CodedOutputStream$OutputStreamEncoder +com.google.protobuf.CodedOutputStream$ByteOutputEncoder +com.google.protobuf.CodedOutputStream$ArrayEncoder +com.google.protobuf.CodedOutputStream$HeapNioEncoder +com.google.protobuf.Utf8$UnpairedSurrogateException +com.google.protobuf.CodedOutputStream$OutOfSpaceException +com.google.protobuf.CodedOutputStream$UnsafeDirectNioEncoder +com.google.protobuf.CodedOutputStream$SafeDirectNioEncoder +com.google.protobuf.Writer +com.google.protobuf.UnsafeUtil +com.google.protobuf.UnsafeUtil$MemoryAccessor +com.google.protobuf.UnsafeUtil$Android64MemoryAccessor +com.google.protobuf.UnsafeUtil$Android32MemoryAccessor +com.google.protobuf.UnsafeUtil$JvmMemoryAccessor +com.google.protobuf.UnsafeUtil$1 +sun.misc.Unsafe +com.google.protobuf.Android +jdk.internal.access.foreign.MemorySegmentProxy +com.google.protobuf.WireFormat +com.google.protobuf.Utf8 +com.google.protobuf.Utf8$Processor +com.google.protobuf.Utf8$UnsafeProcessor +com.google.protobuf.Utf8$SafeProcessor +software.amazon.lambda.powertools.kafka.serializers.KafkaProtobufDeserializer$1 +com.fasterxml.jackson.core.exc.StreamWriteException +com.fasterxml.jackson.core.JsonGenerationException +com.fasterxml.jackson.core.json.JsonWriteContext +com.fasterxml.jackson.core.StreamWriteCapability +com.fasterxml.jackson.core.FormatFeature +com.fasterxml.jackson.core.json.JsonWriteFeature +com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap +com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap$Bucket +com.fasterxml.jackson.databind.util.TypeKey +com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap$$Lambda$827/0x00007f4204423ad8 +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$EntrySet +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$EntryIterator +com.fasterxml.jackson.databind.ObjectReader +com.fasterxml.jackson.databind.ObjectWriter +com.fasterxml.jackson.databind.ser.BeanSerializerBuilder +com.fasterxml.jackson.databind.ser.PropertyBuilder +com.fasterxml.jackson.annotation.JsonInclude +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$3 +com.fasterxml.jackson.annotation.JsonTypeId +com.fasterxml.jackson.databind.BeanProperty$Std +com.fasterxml.jackson.databind.ser.PropertyBuilder$1 +com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Empty +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Single +com.fasterxml.jackson.databind.annotation.JsonAppend +com.fasterxml.jackson.annotation.JsonFilter +com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer +com.fasterxml.jackson.databind.ser.impl.BeanAsArraySerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$1 +com.fasterxml.jackson.databind.ser.AnyGetterWriter +com.fasterxml.jackson.core.io.NumberOutput +sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$SerializerAndMapResult +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Double +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.UTF8StreamJsonParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.MergedStream +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.UTF32Reader +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.exc.InputCoercionException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.JsonEOFException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.JsonReadContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.StreamReadCapability +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.TextBuffer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonToken +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.NumberInput +org.assertj.core.util.Lists$$Lambda$828/0x00007f420442b718 +org.assertj.core.util.Preconditions +org.assertj.core.internal.IterableDiff +org.assertj.core.internal.IterableDiff$$Lambda$829/0x00007f420442bd58 +org.assertj.core.internal.StandardComparisonStrategy$$Lambda$830/0x00007f420442bfb0 +java.util.AbstractMap$SimpleEntry +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$WriteThroughEntry +com.fasterxml.jackson.core.StreamReadFeature +software.amazon.lambda.powertools.kafka.testutils.StringHandler +software.amazon.lambda.powertools.kafka.testutils.DefaultHandler +jdk.internal.reflect.GeneratedConstructorAccessor12 +software.amazon.lambda.powertools.kafka.DeserializationTest$1TestHandler +org.assertj.core.api.AbstractClassAssert +org.assertj.core.api.ClassAssert +org.assertj.core.internal.Classes +org.assertj.core.api.AbstractObjectArrayAssert +org.assertj.core.api.ObjectArrayAssert +org.assertj.core.internal.ObjectArrays +org.assertj.core.internal.Arrays +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$831/0x00007f420442fc20 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$832/0x00007f4204435150 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$833/0x00007f4204435370 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$834/0x00007f4204435598 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$835/0x00007f42044357f0 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$836/0x00007f4204435a18 +software.amazon.lambda.powertools.kafka.serializers.KafkaJsonDeserializerTest$$Lambda$837/0x00007f4204435c40 +software.amazon.lambda.powertools.kafka.serializers.KafkaProtobufDeserializerTest$$Lambda$838/0x00007f4204435e68 +org.apache.kafka.common.utils.ByteUtils +software.amazon.lambda.powertools.kafka.serializers.KafkaProtobufDeserializerTest$$Lambda$839/0x00007f4204436298 +software.amazon.lambda.powertools.kafka.serializers.KafkaAvroDeserializerTest$$Lambda$840/0x00007f42044364c0 +software.amazon.lambda.powertools.kafka.serializers.KafkaAvroDeserializerTest$$Lambda$841/0x00007f42044366e8 +java.util.AbstractMap$2 +java.util.AbstractMap$2$1 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$$Lambda$842/0x00007f4204436b08 +org.assertj.core.util.Throwables +org.assertj.core.util.Throwables$$Lambda$843/0x00007f4204436f38 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$$Lambda$844/0x00007f4204437178 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$$Lambda$845/0x00007f42044373a0 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$$Lambda$846/0x00007f42044375c8 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$$Lambda$847/0x00007f42044377f0 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$$Lambda$848/0x00007f4204437a18 +jdk.internal.reflect.GeneratedConstructorAccessor13 +jdk.internal.reflect.GeneratedMethodAccessor9 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$$Lambda$849/0x00007f4204437c40 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$$Lambda$850/0x00007f4204432000 +jdk.internal.reflect.GeneratedMethodAccessor10 +com.fasterxml.jackson.databind.util.ArrayBuilders +com.fasterxml.jackson.databind.util.PrimitiveArrayBuilder +com.fasterxml.jackson.databind.util.ArrayBuilders$ByteBuilder +org.apache.kafka.common.header.internals.RecordHeader +jdk.internal.reflect.GeneratedConstructorAccessor14 +jdk.internal.reflect.GeneratedMethodAccessor11 +jdk.internal.reflect.GeneratedConstructorAccessor15 +jdk.internal.reflect.GeneratedMethodAccessor12 +jdk.internal.reflect.GeneratedMethodAccessor13 +jdk.internal.reflect.GeneratedMethodAccessor14 +jdk.internal.reflect.GeneratedMethodAccessor15 +jdk.internal.reflect.GeneratedMethodAccessor16 +jdk.internal.reflect.GeneratedMethodAccessor17 +jdk.internal.reflect.GeneratedMethodAccessor18 +jdk.internal.reflect.GeneratedMethodAccessor19 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$$Lambda$851/0x00007f4204432ce0 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$$Lambda$852/0x00007f4204432f08 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$$Lambda$853/0x00007f4204433130 +software.amazon.lambda.powertools.kafka.serializers.AbstractKafkaDeserializerTest$$Lambda$854/0x00007f4204433358 +jdk.internal.reflect.GeneratedConstructorAccessor16 +jdk.internal.reflect.GeneratedConstructorAccessor17 +jdk.internal.reflect.GeneratedMethodAccessor20 +jdk.internal.reflect.GeneratedMethodAccessor21 +jdk.internal.reflect.GeneratedMethodAccessor22 +jdk.internal.reflect.GeneratedMethodAccessor23 +jdk.internal.reflect.GeneratedConstructorAccessor18 +jdk.internal.reflect.GeneratedMethodAccessor24 +jdk.internal.reflect.GeneratedMethodAccessor25 +jdk.internal.reflect.GeneratedMethodAccessor26 +jdk.internal.reflect.GeneratedMethodAccessor27 +jdk.internal.reflect.GeneratedMethodAccessor28 +org.junit.platform.launcher.core.OutcomeDelayingEngineExecutionListener$Outcome +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$855/0x00007f4204433bb8 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$856/0x00007f420443e000 +java.lang.invoke.LambdaForm$DMH/0x00007f420443cc00 +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$857/0x00007f420443e228 +org.junit.platform.launcher.core.DefaultLauncherSession$ClosedLauncher +org.apache.maven.surefire.api.suite.RunResult +org.apache.maven.surefire.booter.ForkedBooter$6 +org.apache.maven.surefire.booter.ForkedBooter$7 +java.util.concurrent.locks.AbstractQueuedSynchronizer$SharedNode +org.apache.maven.surefire.booter.ForkedBooter$1 +org.apache.maven.surefire.booter.spi.AbstractMasterProcessChannelProcessorFactory$2 +java.util.concurrent.locks.AbstractQueuedSynchronizer$ExclusiveNode diff --git a/powertools-kafka/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-kafka/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..1ce363ad9 --- /dev/null +++ b/powertools-kafka/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.kafka.internal.KafkaUserAgentInterceptor diff --git a/powertools-kafka/src/test/avro/TestProduct.avsc b/powertools-kafka/src/test/avro/TestProduct.avsc new file mode 100644 index 000000000..aad903d40 --- /dev/null +++ b/powertools-kafka/src/test/avro/TestProduct.avsc @@ -0,0 +1,10 @@ +{ + "namespace": "software.amazon.lambda.powertools.kafka.serializers.test.avro", + "type": "record", + "name": "TestProduct", + "fields": [ + {"name": "id", "type": "int"}, + {"name": "name", "type": "string"}, + {"name": "price", "type": "double"} + ] +} \ No newline at end of file diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/DeserializationTest.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/DeserializationTest.java new file mode 100644 index 000000000..964498d99 --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/DeserializationTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.reflect.Method; + +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.junit.jupiter.api.Test; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +class DeserializationTest { + + @Test + void shouldHaveCorrectAnnotationRetention() { + // Given + Class<Deserialization> annotationClass = Deserialization.class; + + // When/Then + assertThat(annotationClass.isAnnotation()).isTrue(); + assertThat(annotationClass.getAnnotation(java.lang.annotation.Retention.class).value()) + .isEqualTo(java.lang.annotation.RetentionPolicy.RUNTIME); + assertThat(annotationClass.getAnnotation(java.lang.annotation.Target.class).value()) + .contains(java.lang.annotation.ElementType.METHOD); + } + + @Test + void shouldHaveTypeMethod() throws NoSuchMethodException { + // Given + Class<Deserialization> annotationClass = Deserialization.class; + + // When + java.lang.reflect.Method typeMethod = annotationClass.getMethod("type"); + + // Then + assertThat(typeMethod.getReturnType()).isEqualTo(DeserializationType.class); + } + + @Test + void shouldBeAccessibleReflectivelyAtRuntime() throws NoSuchMethodException, SecurityException { + // Given + class TestHandler implements RequestHandler<ConsumerRecords<String, Object>, String> { + @Override + @Deserialization(type = DeserializationType.KAFKA_JSON) + public String handleRequest(ConsumerRecords<String, Object> input, Context context) { + return "OK"; + } + } + + // When + Method handleRequestMethod = TestHandler.class.getMethod("handleRequest", ConsumerRecords.class, Context.class); + + // Then + Deserialization annotation = handleRequestMethod.getAnnotation(Deserialization.class); + assertThat(annotation).isNotNull(); + assertThat(annotation.type()).isEqualTo(DeserializationType.KAFKA_JSON); + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/DeserializationTypeTest.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/DeserializationTypeTest.java new file mode 100644 index 000000000..6999b66d4 --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/DeserializationTypeTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +// Mainly present to remind us to write unit tests once we add support for a new Deserializer. If we add a new type in +// the enum it will fail this test. +class DeserializationTypeTest { + + @Test + void shouldHaveExpectedEnumValues() { + // Given/When + DeserializationType[] values = DeserializationType.values(); + + // Then + assertThat(values).contains( + DeserializationType.LAMBDA_DEFAULT, + DeserializationType.KAFKA_JSON, + DeserializationType.KAFKA_AVRO, + DeserializationType.KAFKA_PROTOBUF); + } + + @Test + void shouldBeAbleToValueOf() { + // Given/When + DeserializationType jsonType = DeserializationType.valueOf("KAFKA_JSON"); + DeserializationType avroType = DeserializationType.valueOf("KAFKA_AVRO"); + DeserializationType protobufType = DeserializationType.valueOf("KAFKA_PROTOBUF"); + DeserializationType defaultType = DeserializationType.valueOf("LAMBDA_DEFAULT"); + + // Then + assertThat(jsonType).isEqualTo(DeserializationType.KAFKA_JSON); + assertThat(avroType).isEqualTo(DeserializationType.KAFKA_AVRO); + assertThat(protobufType).isEqualTo(DeserializationType.KAFKA_PROTOBUF); + assertThat(defaultType).isEqualTo(DeserializationType.LAMBDA_DEFAULT); + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/PowertoolsSerializerTest.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/PowertoolsSerializerTest.java new file mode 100644 index 000000000..d7d045877 --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/PowertoolsSerializerTest.java @@ -0,0 +1,440 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static software.amazon.lambda.powertools.kafka.testutils.TestUtils.createConsumerRecordsType; +import static software.amazon.lambda.powertools.kafka.testutils.TestUtils.serializeAvro; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.stream.Stream; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.common.TopicPartition; +import org.crac.Context; +import org.crac.Resource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junitpioneer.jupiter.SetEnvironmentVariable; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.kafka.serializers.LambdaDefaultDeserializer; +import software.amazon.lambda.powertools.kafka.serializers.PowertoolsDeserializer; +import software.amazon.lambda.powertools.kafka.testutils.TestProductPojo; + +// This is testing the whole serializer end-to-end. More detailed serializer tests are placed in serializers folder. +@ExtendWith(MockitoExtension.class) +class PowertoolsSerializerTest { + + @Mock + private PowertoolsDeserializer mockDeserializer; + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + // CustomPojoSerializer has fromJson(String input, ...) and fromJson(InputStream input, ...). We want to test both. + static Stream<InputType> inputTypes() { + return Stream.of(InputType.INPUT_STREAM, InputType.STRING); + } + + @ParameterizedTest + @MethodSource("inputTypes") + @SetEnvironmentVariable(key = "_HANDLER", value = "") + void shouldUseDefaultDeserializerWhenHandlerNotFound(InputType inputType) throws JsonProcessingException, IOException { + // When + PowertoolsSerializer serializer = new PowertoolsSerializer(); + + // Then + TestProductPojo product = new TestProductPojo(123, "Test Product", 99.99, Arrays.asList("tag1", "tag2")); + String json = objectMapper.writeValueAsString(product); + + // This will use the Lambda default deserializer (no Kafka logic) + TestProductPojo result; + if (inputType == InputType.INPUT_STREAM) { + try (ByteArrayInputStream input = new ByteArrayInputStream(json.getBytes())) { + result = serializer.fromJson(input, TestProductPojo.class); + } + } else { + result = serializer.fromJson(json, TestProductPojo.class); + } + + assertThat(result.getId()).isEqualTo(123); + assertThat(result.getName()).isEqualTo("Test Product"); + assertThat(result.getPrice()).isEqualTo(99.99); + assertThat(result.getTags()).containsExactly("tag1", "tag2"); + } + + @ParameterizedTest + @MethodSource("inputTypes") + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.testutils.DefaultHandler::handleRequest") + void shouldUseLambdaDefaultDeserializer(InputType inputType) throws JsonProcessingException, IOException { + // When + PowertoolsSerializer serializer = new PowertoolsSerializer(); + + // Then + TestProductPojo product = new TestProductPojo(123, "Test Product", 99.99, Arrays.asList("tag1", "tag2")); + String json = objectMapper.writeValueAsString(product); + + // This will use the Lambda default deserializer (no Kafka logic) + TestProductPojo result; + if (inputType == InputType.INPUT_STREAM) { + try (ByteArrayInputStream input = new ByteArrayInputStream(json.getBytes())) { + result = serializer.fromJson(input, TestProductPojo.class); + } + } else { + result = serializer.fromJson(json, TestProductPojo.class); + } + + assertThat(result.getId()).isEqualTo(123); + assertThat(result.getName()).isEqualTo("Test Product"); + assertThat(result.getPrice()).isEqualTo(99.99); + assertThat(result.getTags()).containsExactly("tag1", "tag2"); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.testutils.StringHandler::handleRequest") + void shouldHandleStringInputType() { + // When + PowertoolsSerializer serializer = new PowertoolsSerializer(); + + // Then + String testInput = "This is a test string"; + + // This should directly return the input string + String result = serializer.fromJson(testInput, String.class); + + assertThat(result).isEqualTo(testInput); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.testutils.InputStreamHandler::handleRequest") + void shouldHandleInputStreamType() throws IOException { + // When + PowertoolsSerializer serializer = new PowertoolsSerializer(); + + // Then + String testInput = "This is a test string"; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(testInput.getBytes()); + InputStream result = serializer.fromJson(inputStream, InputStream.class)) { + // Read the content to verify it's the same + String resultString = new String(result.readAllBytes()); + assertThat(resultString).isEqualTo(testInput); + } + } + + @Test + void shouldConvertInputStreamToString() throws IOException { + // When + LambdaDefaultDeserializer deserializer = new LambdaDefaultDeserializer(); + + // Then + String expected = "This is a test string"; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(expected.getBytes())) { + // Convert InputStream to String + String result = deserializer.fromJson(inputStream, String.class); + + // Verify the result + assertThat(result).isEqualTo(expected); + } + } + + @Test + void shouldThrowRuntimeExceptionWhenInputStreamIsInvalid() { + // When + LambdaDefaultDeserializer deserializer = new LambdaDefaultDeserializer(); + + // Create a problematic InputStream that throws IOException when read + try (InputStream problematicStream = new InputStream() { + @Override + public int read() throws IOException { + throw new IOException("Simulated IO error"); + } + + @Override + public byte[] readAllBytes() throws IOException { + throw new IOException("Simulated IO error"); + } + }) { + // Then + assertThatThrownBy(() -> deserializer.fromJson(problematicStream, String.class)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Failed to read input stream as String"); + } catch (IOException e) { + // Expected for this test case + } + } + + @Test + void shouldConvertStringToByteArray() { + // When + LambdaDefaultDeserializer deserializer = new LambdaDefaultDeserializer(); + + // Then + String input = "This is a test string"; + + // Convert String to InputStream + byte[] result = deserializer.fromJson(input, InputStream.class); + + // Verify the result + String resultString = new String(result); + assertThat(resultString).isEqualTo(input); + } + + @ParameterizedTest + @MethodSource("inputTypes") + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.testutils.JsonHandler::handleRequest") + void shouldUseKafkaJsonDeserializer(InputType inputType) throws IOException { + // When + PowertoolsSerializer serializer = new PowertoolsSerializer(); + + // Create a TestProductPojo and serialize it + TestProductPojo product = new TestProductPojo(123, "Test Product", 99.99, Arrays.asList("tag1", "tag2")); + String productJson = objectMapper.writeValueAsString(product); + String base64Value = Base64.getEncoder().encodeToString(productJson.getBytes()); + + // Then + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {\n" + + " \"test-topic-1\": [\n" + + " {\n" + + " \"topic\": \"test-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": \"" + base64Value + "\",\n" + + " \"headers\": []\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + + Type type = createConsumerRecordsType(String.class, TestProductPojo.class); + + // This should use the KafkaJsonDeserializer + ConsumerRecords<String, TestProductPojo> records; + + if (inputType == InputType.INPUT_STREAM) { + try (ByteArrayInputStream input = new ByteArrayInputStream(kafkaJson.getBytes())) { + records = serializer.fromJson(input, type); + } + } else { + records = serializer.fromJson(kafkaJson, type); + } + + // Verify we got a valid ConsumerRecords object + assertThat(records).isNotNull(); + + // Get the record and verify its content + TopicPartition tp = new TopicPartition("test-topic-1", 0); + List<ConsumerRecord<String, TestProductPojo>> topicRecords = records.records(tp); + assertThat(topicRecords).hasSize(1); + + ConsumerRecord<String, TestProductPojo> consumerRecord = topicRecords.get(0); + TestProductPojo deserializedProduct = consumerRecord.value(); + + assertThat(deserializedProduct.getId()).isEqualTo(123); + assertThat(deserializedProduct.getName()).isEqualTo("Test Product"); + assertThat(deserializedProduct.getPrice()).isEqualTo(99.99); + assertThat(deserializedProduct.getTags()).containsExactly("tag1", "tag2"); + } + + @ParameterizedTest + @MethodSource("inputTypes") + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.testutils.AvroHandler::handleRequest") + void shouldUseKafkaAvroDeserializer(InputType inputType) throws IOException { + // When + PowertoolsSerializer serializer = new PowertoolsSerializer(); + + // Create an Avro TestProduct and serialize it + software.amazon.lambda.powertools.kafka.serializers.test.avro.TestProduct product = new software.amazon.lambda.powertools.kafka.serializers.test.avro.TestProduct( + 123, "Test Product", 99.99); + String base64Value = Base64.getEncoder().encodeToString(serializeAvro(product)); + + // Then + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {\n" + + " \"test-topic-1\": [\n" + + " {\n" + + " \"topic\": \"test-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": \"" + base64Value + "\",\n" + + " \"headers\": []\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + + Type type = createConsumerRecordsType(String.class, + software.amazon.lambda.powertools.kafka.serializers.test.avro.TestProduct.class); + + // This should use the KafkaAvroDeserializer + ConsumerRecords<String, software.amazon.lambda.powertools.kafka.serializers.test.avro.TestProduct> records; + + if (inputType == InputType.INPUT_STREAM) { + try (ByteArrayInputStream input = new ByteArrayInputStream(kafkaJson.getBytes())) { + records = serializer.fromJson(input, type); + } + } else { + records = serializer.fromJson(kafkaJson, type); + } + + // Verify we got a valid ConsumerRecords object + assertThat(records).isNotNull(); + + // Get the record and verify its content + TopicPartition tp = new TopicPartition("test-topic-1", 0); + List<ConsumerRecord<String, software.amazon.lambda.powertools.kafka.serializers.test.avro.TestProduct>> topicRecords = records + .records(tp); + assertThat(topicRecords).hasSize(1); + + ConsumerRecord<String, software.amazon.lambda.powertools.kafka.serializers.test.avro.TestProduct> consumerRecord = topicRecords + .get(0); + software.amazon.lambda.powertools.kafka.serializers.test.avro.TestProduct deserializedProduct = consumerRecord + .value(); + + assertThat(deserializedProduct.getId()).isEqualTo(123); + assertThat(deserializedProduct.getName()).isEqualTo("Test Product"); + assertThat(deserializedProduct.getPrice()).isEqualTo(99.99); + } + + @ParameterizedTest + @MethodSource("inputTypes") + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.testutils.ProtobufHandler::handleRequest") + void shouldUseKafkaProtobufDeserializer(InputType inputType) throws IOException { + // When + PowertoolsSerializer serializer = new PowertoolsSerializer(); + + // Create a Protobuf TestProduct and serialize it + software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProduct product = software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProduct + .newBuilder() + .setId(123) + .setName("Test Product") + .setPrice(99.99) + .build(); + String base64Value = Base64.getEncoder().encodeToString(product.toByteArray()); + + // Then + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {\n" + + " \"test-topic-1\": [\n" + + " {\n" + + " \"topic\": \"test-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": \"" + base64Value + "\",\n" + + " \"headers\": []\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + + Type type = createConsumerRecordsType(String.class, + software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProduct.class); + + // This should use the KafkaProtobufDeserializer + ConsumerRecords<String, software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProduct> records; + + if (inputType == InputType.INPUT_STREAM) { + try (ByteArrayInputStream input = new ByteArrayInputStream(kafkaJson.getBytes())) { + records = serializer.fromJson(input, type); + } + } else { + records = serializer.fromJson(kafkaJson, type); + } + + // Verify we got a valid ConsumerRecords object + assertThat(records).isNotNull(); + + // Get the record and verify its content + TopicPartition tp = new TopicPartition("test-topic-1", 0); + List<ConsumerRecord<String, software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProduct>> topicRecords = records + .records(tp); + assertThat(topicRecords).hasSize(1); + + ConsumerRecord<String, software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProduct> consumerRecord = topicRecords + .get(0); + software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProduct deserializedProduct = consumerRecord + .value(); + + assertThat(deserializedProduct.getId()).isEqualTo(123); + assertThat(deserializedProduct.getName()).isEqualTo("Test Product"); + assertThat(deserializedProduct.getPrice()).isEqualTo(99.99); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "") + void shouldDelegateToJsonOutput() { + // Given + PowertoolsSerializer serializer = new PowertoolsSerializer(); + + // When + TestProductPojo product = new TestProductPojo(123, "Test Product", 99.99, Arrays.asList("tag1", "tag2")); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + // Then + serializer.toJson(product, output, TestProductPojo.class); + String json = output.toString(); + + // Verify the output is valid JSON + assertThat(json).contains("\"id\":123") + .contains("\"name\":\"Test Product\"") + .contains("\"price\":99.99") + .contains("\"tags\":[\"tag1\",\"tag2\"]"); + } + + private enum InputType { + INPUT_STREAM, STRING + } + + @Test + void testBeforeCheckpointDoesNotThrowException() { + PowertoolsSerializer serializer = new PowertoolsSerializer(); + Context<Resource> context = mock(Context.class); + assertThatNoException().isThrownBy(() -> serializer.beforeCheckpoint(context)); + } + + @Test + void testAfterRestoreDoesNotThrowException() { + PowertoolsSerializer serializer = new PowertoolsSerializer(); + Context<Resource> context = mock(Context.class); + assertThatNoException().isThrownBy(() -> serializer.afterRestore(context)); + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/internal/DeserializationUtilsTest.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/internal/DeserializationUtilsTest.java new file mode 100644 index 000000000..21f38d9ab --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/internal/DeserializationUtilsTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; + +import software.amazon.lambda.powertools.kafka.DeserializationType; + +class DeserializationUtilsTest { + + // NOTE: We don't use a parameterized test here because this is not compatible with the @SetEnvironmentVariable + // annotation. + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "") + void shouldReturnDefaultDeserializationTypeWhenHandlerIsEmpty() { + // When + DeserializationType type = DeserializationUtils.determineDeserializationType(); + + // Then + assertThat(type).isEqualTo(DeserializationType.LAMBDA_DEFAULT); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = " ") + void shouldReturnDefaultDeserializationTypeWhenHandlerIsWhitespaceOnly() { + // When + DeserializationType type = DeserializationUtils.determineDeserializationType(); + + // Then + assertThat(type).isEqualTo(DeserializationType.LAMBDA_DEFAULT); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "InvalidHandlerFormat") + void shouldReturnDefaultDeserializationTypeWhenHandlerFormatIsInvalid() { + // When + DeserializationType type = DeserializationUtils.determineDeserializationType(); + + // Then + assertThat(type).isEqualTo(DeserializationType.LAMBDA_DEFAULT); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "com.example.NonExistentClass::handleRequest") + void shouldReturnDefaultDeserializationTypeWhenClassNotFound() { + // When + DeserializationType type = DeserializationUtils.determineDeserializationType(); + + // Then + assertThat(type).isEqualTo(DeserializationType.LAMBDA_DEFAULT); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "java.lang.String::toString") + void shouldReturnDefaultDeserializationTypeWhenClassIsNotRequestHandler() { + // When + DeserializationType type = DeserializationUtils.determineDeserializationType(); + + // Then + assertThat(type).isEqualTo(DeserializationType.LAMBDA_DEFAULT); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.internal.DeserializationUtilsTest$TestHandler::nonExistentMethod") + void shouldReturnDefaultDeserializationTypeWhenMethodNotFound() { + // When + DeserializationType type = DeserializationUtils.determineDeserializationType(); + + // Then + assertThat(type).isEqualTo(DeserializationType.LAMBDA_DEFAULT); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.testutils.JsonHandler::handleRequest") + void shouldReturnJsonDeserializationTypeFromAnnotation() { + // When + DeserializationType type = DeserializationUtils.determineDeserializationType(); + + // Then + assertThat(type).isEqualTo(DeserializationType.KAFKA_JSON); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.testutils.AvroHandler::handleRequest") + void shouldReturnAvroDeserializationTypeFromAnnotation() { + // When + DeserializationType type = DeserializationUtils.determineDeserializationType(); + + // Then + assertThat(type).isEqualTo(DeserializationType.KAFKA_AVRO); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.testutils.ProtobufHandler::handleRequest") + void shouldReturnProtobufDeserializationTypeFromAnnotation() { + // When + DeserializationType type = DeserializationUtils.determineDeserializationType(); + + // Then + assertThat(type).isEqualTo(DeserializationType.KAFKA_PROTOBUF); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.testutils.JsonHandler") + void shouldReturnJsonDeserializationTypeFromAnnotationWithAbbreviatedHandler() { + // When + DeserializationType type = DeserializationUtils.determineDeserializationType(); + + // Then + assertThat(type).isEqualTo(DeserializationType.KAFKA_JSON); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.testutils.AvroHandler") + void shouldReturnAvroDeserializationTypeFromAnnotationWithAbbreviatedHandler() { + // When + DeserializationType type = DeserializationUtils.determineDeserializationType(); + + // Then + assertThat(type).isEqualTo(DeserializationType.KAFKA_AVRO); + } + + @Test + @SetEnvironmentVariable(key = "_HANDLER", value = "software.amazon.lambda.powertools.kafka.testutils.ProtobufHandler") + void shouldReturnProtobufDeserializationTypeFromAnnotationWithAbbreviatedHandler() { + // When + DeserializationType type = DeserializationUtils.determineDeserializationType(); + + // Then + assertThat(type).isEqualTo(DeserializationType.KAFKA_PROTOBUF); + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/internal/KafkaUserAgentInterceptorTest.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/internal/KafkaUserAgentInterceptorTest.java new file mode 100644 index 000000000..4323a42e9 --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/internal/KafkaUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.kafka.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class KafkaUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/KAFKA/"); + } +} \ No newline at end of file diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/serializers/AbstractKafkaDeserializerTest.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/serializers/AbstractKafkaDeserializerTest.java new file mode 100644 index 000000000..b40a2a2b0 --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/serializers/AbstractKafkaDeserializerTest.java @@ -0,0 +1,580 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.serializers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Base64; +import java.util.List; +import java.util.stream.Stream; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.common.TopicPartition; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.kafka.testutils.TestProductPojo; +import software.amazon.lambda.powertools.kafka.testutils.TestUtils; + +class AbstractKafkaDeserializerTest { + + private TestDeserializer deserializer; + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @BeforeEach + void setUp() { + deserializer = new TestDeserializer(); + } + + // CustomPojoSerializer has fromJson(String input, ...) and fromJson(InputStream input, ...). We want to test both. + static Stream<InputType> inputTypes() { + return Stream.of(InputType.INPUT_STREAM, InputType.STRING); + } + + @ParameterizedTest + @MethodSource("inputTypes") + void shouldThrowExceptionWhenTypeIsNotConsumerRecords(InputType inputType) { + // Given + String json = "{}"; + + // When/Then + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(json.getBytes()); + assertThatThrownBy(() -> deserializer.fromJson(inputStream, String.class)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Type must be ConsumerRecords<K, V>"); + } else { + assertThatThrownBy(() -> deserializer.fromJson(json, String.class)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Type must be ConsumerRecords<K, V>"); + } + } + + @ParameterizedTest + @MethodSource("inputTypes") + void shouldThrowExceptionWhenJsonIsInvalid(InputType inputType) { + // Given + String invalidJson = "{invalid json"; + Type type = TestUtils.createConsumerRecordsType(String.class, TestProductPojo.class); + + // When/Then + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(invalidJson.getBytes()); + assertThatThrownBy(() -> deserializer.fromJson(inputStream, type)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Failed to deserialize Lambda handler input to ConsumerRecords"); + } else { + assertThatThrownBy(() -> deserializer.fromJson(invalidJson, type)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Failed to deserialize Lambda handler input to ConsumerRecords"); + } + } + + @ParameterizedTest + @MethodSource("inputTypes") + void shouldThrowExceptionWhenKeyDeserializationFails(InputType inputType) { + // Given + // Create a Kafka event with invalid Base64 for the key + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {\n" + + " \"test-topic-1\": [\n" + + " {\n" + + " \"topic\": \"test-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": \"invalid-base64!\",\n" + + " \"value\": \"eyJrZXkiOiJ2YWx1ZSJ9\",\n" + + " \"headers\": []\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + Type type = TestUtils.createConsumerRecordsType(String.class, TestProductPojo.class); + + // When/Then + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(kafkaJson.getBytes()); + assertThatThrownBy(() -> deserializer.fromJson(inputStream, type)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Failed to deserialize Kafka record key"); + } else { + assertThatThrownBy(() -> deserializer.fromJson(kafkaJson, type)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Failed to deserialize Kafka record key"); + } + } + + @ParameterizedTest + @MethodSource("inputTypes") + void shouldThrowExceptionWhenValueDeserializationFails(InputType inputType) { + // Given + // Create a Kafka event with invalid Base64 for the value + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {\n" + + " \"test-topic-1\": [\n" + + " {\n" + + " \"topic\": \"test-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": \"invalid-base64!\",\n" + + " \"headers\": []\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + Type type = TestUtils.createConsumerRecordsType(String.class, TestProductPojo.class); + + // When/Then + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(kafkaJson.getBytes()); + assertThatThrownBy(() -> deserializer.fromJson(inputStream, type)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Failed to deserialize Kafka record value"); + } else { + assertThatThrownBy(() -> deserializer.fromJson(kafkaJson, type)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Failed to deserialize Kafka record value"); + } + } + + @ParameterizedTest + @MethodSource("inputTypes") + void shouldHandleNullKeyAndValue(InputType inputType) { + // Given + // Create a Kafka event with null key and value + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {\n" + + " \"test-topic-1\": [\n" + + " {\n" + + " \"topic\": \"test-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": null,\n" + + " \"headers\": []\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + Type type = TestUtils.createConsumerRecordsType(String.class, TestProductPojo.class); + + // When + ConsumerRecords<String, TestProductPojo> records; + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(kafkaJson.getBytes()); + records = deserializer.fromJson(inputStream, type); + } else { + records = deserializer.fromJson(kafkaJson, type); + } + + // Then + assertThat(records).isNotNull(); + TopicPartition tp = new TopicPartition("test-topic-1", 0); + List<ConsumerRecord<String, TestProductPojo>> topicRecords = records.records(tp); + assertThat(topicRecords).hasSize(1); + + ConsumerRecord<String, TestProductPojo> consumerRecord = topicRecords.get(0); + assertThat(consumerRecord.key()).isNull(); + assertThat(consumerRecord.value()).isNull(); + } + + @ParameterizedTest + @MethodSource("inputTypes") + void shouldHandleHeadersCorrectly(InputType inputType) { + // Given + // Create a Kafka event with headers + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {\n" + + " \"test-topic-1\": [\n" + + " {\n" + + " \"topic\": \"test-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": null,\n" + + " \"headers\": [\n" + + " {\n" + + " \"headerKey1\": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101, 49],\n" + + " \"headerKey2\": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101, 50]\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + Type type = TestUtils.createConsumerRecordsType(String.class, TestProductPojo.class); + + // When + ConsumerRecords<String, TestProductPojo> records; + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(kafkaJson.getBytes()); + records = deserializer.fromJson(inputStream, type); + } else { + records = deserializer.fromJson(kafkaJson, type); + } + + // Then + assertThat(records).isNotNull(); + TopicPartition tp = new TopicPartition("test-topic-1", 0); + List<ConsumerRecord<String, TestProductPojo>> topicRecords = records.records(tp); + assertThat(topicRecords).hasSize(1); + + ConsumerRecord<String, TestProductPojo> consumerRecord = topicRecords.get(0); + assertThat(consumerRecord.headers()).isNotNull(); + assertThat(consumerRecord.headers().toArray()).hasSize(2); + assertThat(new String(consumerRecord.headers().lastHeader("headerKey1").value())).isEqualTo("headerValue1"); + assertThat(new String(consumerRecord.headers().lastHeader("headerKey2").value())).isEqualTo("headerValue2"); + } + + @ParameterizedTest + @MethodSource("inputTypes") + void shouldHandleEmptyRecords(InputType inputType) { + // Given + // Create a Kafka event with no records + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {}\n" + + "}"; + Type type = TestUtils.createConsumerRecordsType(String.class, TestProductPojo.class); + + // When + ConsumerRecords<String, TestProductPojo> records; + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(kafkaJson.getBytes()); + records = deserializer.fromJson(inputStream, type); + } else { + records = deserializer.fromJson(kafkaJson, type); + } + + // Then + assertThat(records).isNotNull(); + assertThat(records.count()).isZero(); + } + + @ParameterizedTest + @MethodSource("inputTypes") + void shouldHandleNullRecords(InputType inputType) { + // Given + // Create a Kafka event with null records + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\"\n" + + "}"; + Type type = TestUtils.createConsumerRecordsType(String.class, TestProductPojo.class); + + // When + ConsumerRecords<String, TestProductPojo> records; + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(kafkaJson.getBytes()); + records = deserializer.fromJson(inputStream, type); + } else { + records = deserializer.fromJson(kafkaJson, type); + } + + // Then + assertThat(records).isNotNull(); + assertThat(records.count()).isZero(); + } + + @ParameterizedTest + @MethodSource("inputTypes") + void shouldThrowExceptionWhenEventSourceIsNull(InputType inputType) { + // Given + // Create a JSON without eventSource property + String kafkaJson = "{\n" + + " \"records\": {\n" + + " \"test-topic-1\": [\n" + + " {\n" + + " \"topic\": \"test-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": null,\n" + + " \"headers\": []\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + Type type = TestUtils.createConsumerRecordsType(String.class, TestProductPojo.class); + + // When/Then + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(kafkaJson.getBytes()); + assertThatThrownBy(() -> deserializer.fromJson(inputStream, type)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Input is not a valid Kafka event"); + } else { + assertThatThrownBy(() -> deserializer.fromJson(kafkaJson, type)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Input is not a valid Kafka event"); + } + } + + static Stream<Arguments> primitiveTypesProvider() { + return Stream.of( + // For each primitive type, test with both INPUT_STREAM and STRING + Arguments.of("String-InputStream", String.class, "test-string", "test-string", InputType.INPUT_STREAM), + Arguments.of("String-String", String.class, "test-string", "test-string", InputType.STRING), + Arguments.of("Integer-InputStream", Integer.class, "123", 123, InputType.INPUT_STREAM), + Arguments.of("Integer-String", Integer.class, "123", 123, InputType.STRING), + Arguments.of("Long-InputStream", Long.class, "123456789", 123456789L, InputType.INPUT_STREAM), + Arguments.of("Long-String", Long.class, "123456789", 123456789L, InputType.STRING), + Arguments.of("Double-InputStream", Double.class, "123.456", 123.456, InputType.INPUT_STREAM), + Arguments.of("Double-String", Double.class, "123.456", 123.456, InputType.STRING), + Arguments.of("Float-InputStream", Float.class, "123.45", 123.45f, InputType.INPUT_STREAM), + Arguments.of("Float-String", Float.class, "123.45", 123.45f, InputType.STRING), + Arguments.of("Boolean-InputStream", Boolean.class, "true", true, InputType.INPUT_STREAM), + Arguments.of("Boolean-String", Boolean.class, "true", true, InputType.STRING), + Arguments.of("Byte-InputStream", Byte.class, "127", (byte) 127, InputType.INPUT_STREAM), + Arguments.of("Byte-String", Byte.class, "127", (byte) 127, InputType.STRING), + Arguments.of("Short-InputStream", Short.class, "32767", (short) 32767, InputType.INPUT_STREAM), + Arguments.of("Short-String", Short.class, "32767", (short) 32767, InputType.STRING), + Arguments.of("Character-InputStream", Character.class, "A", 'A', InputType.INPUT_STREAM), + Arguments.of("Character-String", Character.class, "A", 'A', InputType.STRING)); + } + + @ParameterizedTest(name = "Should handle {0}") + @MethodSource("primitiveTypesProvider") + <T> void shouldHandlePrimitiveTypes(String testName, Class<T> keyType, String keyValue, T expectedKey, + InputType inputType) throws IOException { + // Given + // Create a TestProductPojo and serialize it to JSON + TestProductPojo product = new TestProductPojo(123, "Test Product", 99.99, null); + String productJson = objectMapper.writeValueAsString(product); + String base64Value = Base64.getEncoder().encodeToString(productJson.getBytes()); + String base64Key = Base64.getEncoder().encodeToString(keyValue.getBytes()); + + // Create a Kafka event with primitive type for key + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {\n" + + " \"test-topic-1\": [\n" + + " {\n" + + " \"topic\": \"test-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": \"" + base64Key + "\",\n" + + " \"value\": \"" + base64Value + "\",\n" + + " \"headers\": []\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + Type type = TestUtils.createConsumerRecordsType(keyType, TestProductPojo.class); + + // When + ConsumerRecords<T, TestProductPojo> records; + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(kafkaJson.getBytes()); + records = deserializer.fromJson(inputStream, type); + } else { + records = deserializer.fromJson(kafkaJson, type); + } + + // Then + assertThat(records).isNotNull(); + TopicPartition tp = new TopicPartition("test-topic-1", 0); + List<ConsumerRecord<T, TestProductPojo>> topicRecords = records.records(tp); + assertThat(topicRecords).hasSize(1); + + ConsumerRecord<T, TestProductPojo> consumerRecord = topicRecords.get(0); + assertThat(consumerRecord.key()).isEqualTo(expectedKey); + assertThat(consumerRecord.value()).isNotNull(); + assertThat(consumerRecord.value().getId()).isEqualTo(123); + } + + @ParameterizedTest + @MethodSource("inputTypes") + void shouldThrowExceptionWhenConvertingEmptyStringToChar(InputType inputType) { + // Given + String base64EmptyString = Base64.getEncoder().encodeToString("".getBytes()); + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {\n" + + " \"test-topic-1\": [\n" + + " {\n" + + " \"topic\": \"test-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": \"" + base64EmptyString + "\",\n" + + " \"value\": null,\n" + + " \"headers\": []\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + Type type = TestUtils.createConsumerRecordsType(Character.class, TestProductPojo.class); + + // When/Then + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(kafkaJson.getBytes()); + assertThatThrownBy(() -> deserializer.fromJson(inputStream, type)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Failed to deserialize Kafka record key") + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage("Cannot convert empty string to char"); + } else { + assertThatThrownBy(() -> deserializer.fromJson(kafkaJson, type)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Failed to deserialize Kafka record key") + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage("Cannot convert empty string to char"); + } + } + + @ParameterizedTest + @MethodSource("inputTypes") + void shouldHandleGlueSchemaMetadata(InputType inputType) throws IOException { + // Given + TestProductPojo product = new TestProductPojo(123, "Test Product", 99.99, null); + String productJson = objectMapper.writeValueAsString(product); + String base64Value = Base64.getEncoder().encodeToString(productJson.getBytes()); + + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {\n" + + " \"test-topic-1\": [\n" + + " {\n" + + " \"topic\": \"test-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": \"" + base64Value + "\",\n" + + " \"headers\": [],\n" + + " \"keySchemaMetadata\": {\n" + + " \"schemaId\": \"12345678-1234-1234-1234-123456789012\",\n" + + " \"dataFormat\": \"PROTOBUF\"\n" + + " },\n" + + " \"valueSchemaMetadata\": {\n" + + " \"schemaId\": \"87654321-4321-4321-4321-210987654321\",\n" + + " \"dataFormat\": \"PROTOBUF\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + Type type = TestUtils.createConsumerRecordsType(String.class, TestProductPojo.class); + + // When + ConsumerRecords<String, TestProductPojo> records; + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(kafkaJson.getBytes()); + records = deserializer.fromJson(inputStream, type); + } else { + records = deserializer.fromJson(kafkaJson, type); + } + + // Then + assertThat(records).isNotNull(); + TopicPartition tp = new TopicPartition("test-topic-1", 0); + List<ConsumerRecord<String, TestProductPojo>> topicRecords = records.records(tp); + assertThat(topicRecords).hasSize(1); + + ConsumerRecord<String, TestProductPojo> consumerRecord = topicRecords.get(0); + assertThat(consumerRecord.value()).isNotNull(); + assertThat(consumerRecord.value().getId()).isEqualTo(123); + } + + @ParameterizedTest + @MethodSource("inputTypes") + void shouldHandleConfluentSchemaMetadata(InputType inputType) throws IOException { + // Given + TestProductPojo product = new TestProductPojo(456, "Confluent Product", 199.99, null); + String productJson = objectMapper.writeValueAsString(product); + String base64Value = Base64.getEncoder().encodeToString(productJson.getBytes()); + + String kafkaJson = "{\n" + + " \"eventSource\": \"aws:kafka\",\n" + + " \"records\": {\n" + + " \"test-topic-1\": [\n" + + " {\n" + + " \"topic\": \"test-topic-1\",\n" + + " \"partition\": 0,\n" + + " \"offset\": 15,\n" + + " \"timestamp\": 1545084650987,\n" + + " \"timestampType\": \"CREATE_TIME\",\n" + + " \"key\": null,\n" + + " \"value\": \"" + base64Value + "\",\n" + + " \"headers\": [],\n" + + " \"keySchemaMetadata\": {\n" + + " \"schemaId\": \"123\",\n" + + " \"dataFormat\": \"PROTOBUF\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + Type type = TestUtils.createConsumerRecordsType(String.class, TestProductPojo.class); + + // When + ConsumerRecords<String, TestProductPojo> records; + if (inputType == InputType.INPUT_STREAM) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(kafkaJson.getBytes()); + records = deserializer.fromJson(inputStream, type); + } else { + records = deserializer.fromJson(kafkaJson, type); + } + + // Then + assertThat(records).isNotNull(); + TopicPartition tp = new TopicPartition("test-topic-1", 0); + List<ConsumerRecord<String, TestProductPojo>> topicRecords = records.records(tp); + assertThat(topicRecords).hasSize(1); + + ConsumerRecord<String, TestProductPojo> consumerRecord = topicRecords.get(0); + assertThat(consumerRecord.value()).isNotNull(); + assertThat(consumerRecord.value().getId()).isEqualTo(456); + } + + // Test implementation of AbstractKafkaDeserializer + private static final class TestDeserializer extends AbstractKafkaDeserializer { + @Override + protected <T> T deserializeObject(byte[] data, Class<T> type, SchemaRegistryType schemaRegistryType) + throws IOException { + return objectMapper.readValue(data, type); + } + } + + enum InputType { + INPUT_STREAM, STRING + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/serializers/KafkaAvroDeserializerTest.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/serializers/KafkaAvroDeserializerTest.java new file mode 100644 index 000000000..f501d7d98 --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/serializers/KafkaAvroDeserializerTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.serializers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static software.amazon.lambda.powertools.kafka.testutils.TestUtils.serializeAvro; + +import java.io.IOException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import software.amazon.lambda.powertools.kafka.serializers.test.avro.TestProduct; + +class KafkaAvroDeserializerTest { + + private KafkaAvroDeserializer deserializer; + + @BeforeEach + void setUp() { + deserializer = new KafkaAvroDeserializer(); + } + + @Test + void shouldThrowExceptionWhenTypeIsNotAvroSpecificRecord() { + // Given + byte[] data = new byte[] { 1, 2, 3 }; + + // When/Then + assertThatThrownBy(() -> deserializer.deserializeObject(data, String.class, + AbstractKafkaDeserializer.SchemaRegistryType.NONE)) + .isInstanceOf(IOException.class) + .hasMessageContaining("Unsupported type for Avro deserialization"); + } + + @Test + void shouldDeserializeValidAvroData() throws IOException { + // Given + TestProduct product = new TestProduct(123, "Test Product", 99.99); + byte[] avroData = serializeAvro(product); + + // When + TestProduct result = deserializer.deserializeObject(avroData, TestProduct.class, + AbstractKafkaDeserializer.SchemaRegistryType.NONE); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(123); + assertThat(result.getName()).isEqualTo("Test Product"); + assertThat(result.getPrice()).isEqualTo(99.99); + } + + @Test + void shouldThrowExceptionWhenDeserializingInvalidAvroData() { + // Given + byte[] invalidAvroData = new byte[] { 1, 2, 3, 4, 5 }; + + // When/Then + assertThatThrownBy(() -> deserializer.deserializeObject(invalidAvroData, TestProduct.class, + AbstractKafkaDeserializer.SchemaRegistryType.NONE)) + .isInstanceOf(IOException.class) + .hasMessageContaining("Failed to deserialize Avro data"); + } + +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/serializers/KafkaJsonDeserializerTest.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/serializers/KafkaJsonDeserializerTest.java new file mode 100644 index 000000000..c01e09d8d --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/serializers/KafkaJsonDeserializerTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.serializers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.IOException; +import java.util.Arrays; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.kafka.testutils.TestProductPojo; + +class KafkaJsonDeserializerTest { + + private KafkaJsonDeserializer deserializer; + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @BeforeEach + void setUp() { + deserializer = new KafkaJsonDeserializer(); + } + + @Test + void shouldThrowExceptionWhenTypeIsNotSupportedForJson() { + // Given + byte[] data = new byte[] { 1, 2, 3 }; + + // When/Then + assertThatThrownBy(() -> deserializer.deserializeObject(data, Object.class, + AbstractKafkaDeserializer.SchemaRegistryType.NONE)) + .isInstanceOf(JsonParseException.class); + } + + @Test + void shouldDeserializeValidJsonData() throws IOException { + // Given + TestProductPojo product = new TestProductPojo(123, "Test Product", 99.99, Arrays.asList("tag1", "tag2")); + byte[] jsonData = objectMapper.writeValueAsBytes(product); + + // When + TestProductPojo result = deserializer.deserializeObject(jsonData, TestProductPojo.class, + AbstractKafkaDeserializer.SchemaRegistryType.NONE); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(123); + assertThat(result.getName()).isEqualTo("Test Product"); + assertThat(result.getPrice()).isEqualTo(99.99); + assertThat(result.getTags()).containsExactly("tag1", "tag2"); + } + +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/serializers/KafkaProtobufDeserializerTest.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/serializers/KafkaProtobufDeserializerTest.java new file mode 100644 index 000000000..34a376947 --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/serializers/KafkaProtobufDeserializerTest.java @@ -0,0 +1,200 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.serializers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.kafka.common.utils.ByteUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.google.protobuf.CodedOutputStream; + +import software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProduct; + +class KafkaProtobufDeserializerTest { + + private KafkaProtobufDeserializer deserializer; + + @BeforeEach + void setUp() { + deserializer = new KafkaProtobufDeserializer(); + } + + @Test + void shouldThrowExceptionWhenTypeIsNotProtobufMessage() { + // Given + byte[] data = new byte[] { 1, 2, 3 }; + + // When/Then + assertThatThrownBy(() -> deserializer.deserializeObject(data, String.class, + AbstractKafkaDeserializer.SchemaRegistryType.NONE)) + .isInstanceOf(IOException.class) + .hasMessageContaining("Unsupported type for Protobuf deserialization"); + } + + @Test + void shouldDeserializeValidProtobufData() throws IOException { + // Given + TestProduct product = TestProduct.newBuilder() + .setId(123) + .setName("Test Product") + .setPrice(99.99) + .build(); + byte[] protobufData = product.toByteArray(); + + // When + TestProduct result = deserializer.deserializeObject(protobufData, TestProduct.class, + AbstractKafkaDeserializer.SchemaRegistryType.NONE); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(123); + assertThat(result.getName()).isEqualTo("Test Product"); + assertThat(result.getPrice()).isEqualTo(99.99); + } + + @Test + void shouldThrowExceptionWhenDeserializingInvalidProtobufData() { + // Given + byte[] invalidProtobufData = new byte[] { 1, 2, 3, 4, 5 }; + + // When/Then + assertThatThrownBy(() -> deserializer.deserializeObject(invalidProtobufData, TestProduct.class, + AbstractKafkaDeserializer.SchemaRegistryType.NONE)) + .isInstanceOf(IOException.class) + .hasMessageContaining("Failed to deserialize Protobuf data"); + } + + @Test + void shouldDeserializeProtobufDataWithSimpleMessageIndexGlue() throws IOException { + // Given + TestProduct product = TestProduct.newBuilder() + .setId(456) + .setName("Simple Index Product") + .setPrice(199.99) + .build(); + + // Create protobuf data with simple message index (single 0) + byte[] protobufDataWithSimpleIndex = createProtobufDataWithGlueMagicByte(product); + + // When + TestProduct result = deserializer.deserializeObject(protobufDataWithSimpleIndex, TestProduct.class, + AbstractKafkaDeserializer.SchemaRegistryType.GLUE); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(456); + assertThat(result.getName()).isEqualTo("Simple Index Product"); + assertThat(result.getPrice()).isEqualTo(199.99); + } + + @Test + void shouldDeserializeProtobufDataWithComplexMessageIndex() throws IOException { + // Given + TestProduct product = TestProduct.newBuilder() + .setId(789) + .setName("Complex Index Product") + .setPrice(299.99) + .build(); + + // Create protobuf data with complex message index (array [2,2]) + byte[] protobufDataWithComplexIndex = createProtobufDataWithComplexMessageIndexConfluent(product); + + // When + TestProduct result = deserializer.deserializeObject(protobufDataWithComplexIndex, TestProduct.class, + AbstractKafkaDeserializer.SchemaRegistryType.CONFLUENT); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(789); + assertThat(result.getName()).isEqualTo("Complex Index Product"); + assertThat(result.getPrice()).isEqualTo(299.99); + } + + @Test + void shouldDeserializeProtobufDataWithSimpleMessageIndexConfluent() throws IOException { + // Given + TestProduct product = TestProduct.newBuilder() + .setId(789) + .setName("Complex Index Product") + .setPrice(299.99) + .build(); + + // Create protobuf data with simple message index for Confluent + byte[] protobufDataWithComplexIndex = createProtobufDataWithSimpleMessageIndexConfluent(product); + + // When + TestProduct result = deserializer.deserializeObject(protobufDataWithComplexIndex, TestProduct.class, + AbstractKafkaDeserializer.SchemaRegistryType.CONFLUENT); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(789); + assertThat(result.getName()).isEqualTo("Complex Index Product"); + assertThat(result.getPrice()).isEqualTo(299.99); + } + + private byte[] createProtobufDataWithGlueMagicByte(TestProduct product) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + CodedOutputStream codedOutput = CodedOutputStream.newInstance(baos); + + // Write simple message index for Glue (single UInt32) + codedOutput.writeUInt32NoTag(1); + + // Write the protobuf data + product.writeTo(codedOutput); + + codedOutput.flush(); + return baos.toByteArray(); + } + + private byte[] createProtobufDataWithSimpleMessageIndexConfluent(TestProduct product) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // Write optimized simple message index for Confluent (single 0 byte for [0]) + // https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format + baos.write(0); + + // Write the protobuf data + baos.write(product.toByteArray()); + + return baos.toByteArray(); + } + + private byte[] createProtobufDataWithComplexMessageIndexConfluent(TestProduct product) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // Write complex message index array [1,0] using ByteUtils + // https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format + ByteBuffer buffer = ByteBuffer.allocate(1024); + ByteUtils.writeVarint(2, buffer); // Array length + ByteUtils.writeVarint(1, buffer); // First index value + ByteUtils.writeVarint(0, buffer); // Second index value + + buffer.flip(); + byte[] indexData = new byte[buffer.remaining()]; + buffer.get(indexData); + baos.write(indexData); + + // Write the protobuf data + baos.write(product.toByteArray()); + + return baos.toByteArray(); + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/AvroHandler.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/AvroHandler.java new file mode 100644 index 000000000..d0fc9c1ba --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/AvroHandler.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.testutils; + +import org.apache.kafka.clients.consumer.ConsumerRecords; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.kafka.Deserialization; +import software.amazon.lambda.powertools.kafka.DeserializationType; +import software.amazon.lambda.powertools.kafka.serializers.test.avro.TestProduct; + +public class AvroHandler implements RequestHandler<ConsumerRecords<String, TestProduct>, String> { + @Override + @Deserialization(type = DeserializationType.KAFKA_AVRO) + public String handleRequest(ConsumerRecords<String, TestProduct> input, Context context) { + return "OK"; + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/DefaultHandler.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/DefaultHandler.java new file mode 100644 index 000000000..31e93d872 --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/DefaultHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.testutils; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.kafka.Deserialization; +import software.amazon.lambda.powertools.kafka.DeserializationType; + +// This is a non-Kafka specific handler. Just a handler using default deserialization into a Pojo. Used for testing +// fallback to default Lambda serialization. +public class DefaultHandler implements RequestHandler<TestProductPojo, String> { + @Override + @Deserialization(type = DeserializationType.LAMBDA_DEFAULT) + public String handleRequest(TestProductPojo input, Context context) { + return "OK"; + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/InputStreamHandler.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/InputStreamHandler.java new file mode 100644 index 000000000..63e225ab8 --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/InputStreamHandler.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.testutils; + +import java.io.IOException; +import java.io.InputStream; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +public class InputStreamHandler implements RequestHandler<InputStream, String> { + @Override + public String handleRequest(InputStream input, Context context) { + try { + return new String(input.readAllBytes()); + } catch (IOException e) { + throw new RuntimeException("Failed to read input stream", e); + } + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/JsonHandler.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/JsonHandler.java new file mode 100644 index 000000000..b6422f73c --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/JsonHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.testutils; + +import org.apache.kafka.clients.consumer.ConsumerRecords; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.kafka.Deserialization; +import software.amazon.lambda.powertools.kafka.DeserializationType; + +public class JsonHandler implements RequestHandler<ConsumerRecords<String, TestProductPojo>, String> { + @Override + @Deserialization(type = DeserializationType.KAFKA_JSON) + public String handleRequest(ConsumerRecords<String, TestProductPojo> input, Context context) { + return "OK"; + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/ProtobufHandler.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/ProtobufHandler.java new file mode 100644 index 000000000..a4ce61765 --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/ProtobufHandler.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.testutils; + +import org.apache.kafka.clients.consumer.ConsumerRecords; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.kafka.Deserialization; +import software.amazon.lambda.powertools.kafka.DeserializationType; +import software.amazon.lambda.powertools.kafka.serializers.test.protobuf.TestProduct; + +public class ProtobufHandler implements RequestHandler<ConsumerRecords<String, TestProduct>, String> { + @Override + @Deserialization(type = DeserializationType.KAFKA_PROTOBUF) + public String handleRequest(ConsumerRecords<String, TestProduct> input, Context context) { + return "OK"; + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/StringHandler.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/StringHandler.java new file mode 100644 index 000000000..3ac5649f1 --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/StringHandler.java @@ -0,0 +1,23 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.testutils; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +public class StringHandler implements RequestHandler<String, String> { + @Override + public String handleRequest(String input, Context context) { + return input; + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/TestProductPojo.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/TestProductPojo.java new file mode 100644 index 000000000..8cd261aef --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/TestProductPojo.java @@ -0,0 +1,87 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.testutils; + +import java.util.List; +import java.util.Objects; + +/** + * Simple POJO for testing JSON deserialization + */ +public class TestProductPojo { + private int id; + private String name; + private double price; + private List<String> tags; + + // Default constructor required for Jackson + public TestProductPojo() { + } + + public TestProductPojo(int id, String name, double price, List<String> tags) { + this.id = id; + this.name = name; + this.price = price; + this.tags = tags; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + public List<String> getTags() { + return tags; + } + + public void setTags(List<String> tags) { + this.tags = tags; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + TestProductPojo that = (TestProductPojo) o; + return id == that.id && + Double.compare(that.price, price) == 0 && + Objects.equals(name, that.name) && + Objects.equals(tags, that.tags); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, price, tags); + } +} diff --git a/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/TestUtils.java b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/TestUtils.java new file mode 100644 index 000000000..33623a9b2 --- /dev/null +++ b/powertools-kafka/src/test/java/software/amazon/lambda/powertools/kafka/testutils/TestUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + */ +package software.amazon.lambda.powertools.kafka.testutils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import org.apache.avro.io.BinaryEncoder; +import org.apache.avro.io.DatumWriter; +import org.apache.avro.io.EncoderFactory; +import org.apache.avro.specific.SpecificDatumWriter; +import org.apache.avro.specific.SpecificRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; + +/** + * Utility class for common test functions + */ +public class TestUtils { + + /** + * Helper method to create a ParameterizedType for ConsumerRecords + * + * @param keyClass The class for the key type + * @param valueClass The class for the value type + * @return A Type representing ConsumerRecords<K, V> + */ + public static Type createConsumerRecordsType(final Class<?> keyClass, final Class<?> valueClass) { + return new ParameterizedType() { + @Override + public Type[] getActualTypeArguments() { + return new Type[] { keyClass, valueClass }; + } + + @Override + public Type getRawType() { + return ConsumerRecords.class; + } + + @Override + public Type getOwnerType() { + return null; + } + }; + } + + /** + * Helper method to serialize an Avro object + * + * @param <T> The type of the Avro record + * @param consumerRecord The Avro record to serialize + * @return The serialized bytes + * @throws IOException If serialization fails + */ + public static <T extends SpecificRecord> byte[] serializeAvro(T consumerRecord) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryEncoder encoder = EncoderFactory.get().binaryEncoder(baos, null); + @SuppressWarnings("unchecked") + DatumWriter<T> writer = new SpecificDatumWriter<>((Class<T>) consumerRecord.getClass()); + writer.write(consumerRecord, encoder); + encoder.flush(); + return baos.toByteArray(); + } +} diff --git a/powertools-kafka/src/test/proto/TestProduct.proto b/powertools-kafka/src/test/proto/TestProduct.proto new file mode 100644 index 000000000..53c654494 --- /dev/null +++ b/powertools-kafka/src/test/proto/TestProduct.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package software.amazon.lambda.powertools.kafka.serializers.test.protobuf; + +option java_package = "software.amazon.lambda.powertools.kafka.serializers.test.protobuf"; +option java_outer_classname = "TestProductOuterClass"; +option java_multiple_files = true; + +message TestProduct { + int32 id = 1; + string name = 2; + double price = 3; +} \ No newline at end of file diff --git a/powertools-kafka/src/test/resources/simplelogger.properties b/powertools-kafka/src/test/resources/simplelogger.properties new file mode 100644 index 000000000..167581f74 --- /dev/null +++ b/powertools-kafka/src/test/resources/simplelogger.properties @@ -0,0 +1,13 @@ +# SLF4J Simple Logger configuration for tests +org.slf4j.simpleLogger.defaultLogLevel=debug +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss.SSS +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showLogName=true +org.slf4j.simpleLogger.showShortLogName=false + +# Redirect logs to a file instead of console to avoid bloated console output during tests +org.slf4j.simpleLogger.logFile=target/test.log + +# Set specific logger levels +org.slf4j.simpleLogger.log.software.amazon.lambda.powertools=debug diff --git a/powertools-large-messages/pom.xml b/powertools-large-messages/pom.xml new file mode 100644 index 000000000..ad5910ec1 --- /dev/null +++ b/powertools-large-messages/pom.xml @@ -0,0 +1,158 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <description>A suite of utilities for AWS Lambda Functions that makes handling large messages in SQS and SNS easier.</description> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parent</artifactId> + <version>2.9.0</version> + </parent> + + <artifactId>powertools-large-messages</artifactId> + <packaging>jar</packaging> + + <name>Powertools for AWS Lambda (Java) - Large messages</name> + + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.payloadoffloading</groupId> + <artifactId>payloadoffloading-common</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>utils</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <exclusions> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>netty-nio-client</artifactId> + </exclusion> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>apache-client</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>url-connection-client</artifactId> + <version>${aws.sdk.version}</version> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit-pioneer</groupId> + <artifactId>junit-pioneer</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjweaver</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j2-impl</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>${maven-surefire-plugin.version}</version> + <configuration> + <environmentVariables> + <AWS_REGION>eu-central-1</AWS_REGION> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/LargeMessage.java b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/LargeMessage.java new file mode 100644 index 000000000..eb5b368a5 --- /dev/null +++ b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/LargeMessage.java @@ -0,0 +1,72 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p>Use this annotation to handle large messages (> 1 MB) from SQS or SNS. + * When large messages are sent to an SQS Queue or SNS Topic, they are offloaded to S3 and only a reference is passed in the message/record.</p> + * + * <p>{@code @LargeMessage} automatically retrieves and deletes messages + * which have been offloaded to S3 using the {@code amazon-sqs-java-extended-client-lib} or {@code amazon-sns-java-extended-client-lib} + * client libraries.</p> + * + * <p>This version of the {@code @LargeMessage} is compatible with version 1.1.0+ and 2.0.0+ + * of {@code amazon-sqs-java-extended-client-lib} / {@code amazon-sns-java-extended-client-lib}.</p> + * <br/> + * <p>Put this annotation on a method where the first parameter is either a {@link com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage} or {@link com.amazonaws.services.lambda.runtime.events.SNSEvent.SNSRecord}. + * <br/> + * <u>SQS</u>:<br/> + * <pre> + * @LargeMessage + * private void processRawMessage(SQSMessage sqsMessage, Context context) { + * // sqsMessage.getBody() will contain the content of the S3 Object + * } + * </pre> + * <u>SNS</u>:<br/> + * <pre> + * @LargeMessage + * private void processMessage(SNSRecord snsRecord) { + * // snsRecord.getSNS().getMessage() will contain the content of the S3 Object + * } + * </pre> + * </p> + * + * <p>To disable the deletion of S3 objects, you can configure the {@code deleteS3Object} option to false (default is true): + * <pre> + * @LargeMessage(deleteS3Object = false) + * </pre> + * </p> + * + * <p><b>Note 1</b>: The message object (SQSMessage or SNSRecord) is modified in-place to avoid duplicating + * the large blob in memory. The message body will be replaced with the S3 object content.</p> + * <p><b>Note 2</b>: Retrieving payloads and deleting objects from S3 will increase the duration of the + * Lambda function.</p> + * <p><b>Note 3</b>: Make sure to configure your function with enough memory to be able to retrieve S3 objects.</p> + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface LargeMessage { + + /** + * Specify if S3 objects must be deleted after being processed (default = true) + * Alternatively you might consider using S3 lifecycle policies to remove the payloads automatically after a period of time. + */ + boolean deleteS3Object() default true; +} diff --git a/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/LargeMessageConfig.java b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/LargeMessageConfig.java new file mode 100644 index 000000000..6ad529496 --- /dev/null +++ b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/LargeMessageConfig.java @@ -0,0 +1,83 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages; + +import static software.amazon.lambda.powertools.common.internal.LambdaConstants.AWS_REGION_ENV; + +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3ClientBuilder; + +/** + * Singleton instance for Large Message Config. We need this to provide a way to customize the S3 client configuration used by the annotation. + * <br/> + * Optional: Use it in your Lambda constructor to pass a custom {@link S3Client} to the {@link software.amazon.lambda.powertools.largemessages.internal.LargeMessageProcessor} + * <br/> + * If you don't use this, a default S3Client will be created. + * <pre> + * public MyLambdaHandler() { + * LargeMessageConfig.init().withS3Client(S3Client.create()); + * } + * </pre> + */ +public class LargeMessageConfig { + + private static final LargeMessageConfig INSTANCE = new LargeMessageConfig(); + private S3Client s3Client; + + private LargeMessageConfig() { + } + + /** + * Retrieve the singleton instance (you generally don't need to use this one, used internally by the library) + * + * @return the singleton instance + */ + public static LargeMessageConfig get() { + return INSTANCE; + } + + /** + * Initialize the singleton instance + * + * @return the singleton instance + */ + public static LargeMessageConfig init() { + return INSTANCE; + } + + public void withS3Client(S3Client s3Client) { + if (this.s3Client == null) { + this.s3Client = s3Client; + } + } + + // For tests purpose + void resetS3Client() { + this.s3Client = null; + } + + // Getter needs to initialize if not done with setter + public S3Client getS3Client() { + if (this.s3Client == null) { + S3ClientBuilder s3ClientBuilder = S3Client.builder() + .httpClient(UrlConnectionHttpClient.builder().build()) + .region(Region.of(System.getenv(AWS_REGION_ENV))); + this.s3Client = s3ClientBuilder.build(); + } + return this.s3Client; + } +} diff --git a/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/LargeMessageProcessingException.java b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/LargeMessageProcessingException.java new file mode 100644 index 000000000..20b19230a --- /dev/null +++ b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/LargeMessageProcessingException.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages; + +/** + * Exception that occurs when the utility fails to retrieve the content from S3 + */ +public class LargeMessageProcessingException extends RuntimeException { + public LargeMessageProcessingException(String message, Throwable cause) { + super(message, cause); + } + + public LargeMessageProcessingException(String message) { + super(message); + } +} diff --git a/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/LargeMessages.java b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/LargeMessages.java new file mode 100644 index 000000000..52675d3eb --- /dev/null +++ b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/LargeMessages.java @@ -0,0 +1,156 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages; + +import java.util.Optional; +import java.util.function.Function; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import software.amazon.lambda.powertools.largemessages.internal.LargeMessageProcessor; +import software.amazon.lambda.powertools.largemessages.internal.LargeMessageProcessorFactory; + +/** + * Functional API for processing large messages without AspectJ. + * <p> + * Use this class to handle large messages (> 1 MB) from SQS or SNS. + * When large messages are sent to an SQS Queue or SNS Topic, they are offloaded to S3 and only a reference is passed in the message/record. + * <p> + * {@code LargeMessages} automatically retrieves and optionally deletes messages + * which have been offloaded to S3 using the {@code amazon-sqs-java-extended-client-lib} or {@code amazon-sns-java-extended-client-lib} + * client libraries. + * <p> + * This version is compatible with version 1.1.0+ and 2.0.0+ of {@code amazon-sqs-java-extended-client-lib} / {@code amazon-sns-java-extended-client-lib}. + * <p> + * <u>SQS Example</u>: + * <pre> + * public class SqsBatchHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + * private final BatchMessageHandler<SQSEvent, SQSBatchResponse> handler; + * + * public SqsBatchHandler() { + * handler = new BatchMessageHandlerBuilder() + * .withSqsBatchHandler() + * .buildWithRawMessageHandler(this::processMessage); + * } + * + * @Override + * public SQSBatchResponse handleRequest(SQSEvent sqsEvent, Context context) { + * return handler.processBatch(sqsEvent, context); + * } + * + * private void processMessage(SQSEvent.SQSMessage sqsMessage) { + * LargeMessages.processLargeMessage(sqsMessage, this::handleProcessedMessage); + * } + * + * private void handleProcessedMessage(SQSEvent.SQSMessage processedMessage) { + * // processedMessage.getBody() will contain the content of the S3 Object + * } + * } + * </pre> + * <p> + * To disable the deletion of S3 objects: + * <pre> + * LargeMessages.processLargeMessage(sqsMessage, this::handleProcessedMessage, false); + * </pre> + * <p> + * For multi-argument methods, use a lambda to pass additional parameters: + * <pre> + * public void handleRequest(SQSEvent event, Context context) { + * event.getRecords().forEach(message -> + * LargeMessages.processLargeMessage(message, processedMsg -> processMessage(processedMsg, context)) + * ); + * } + * + * private void processMessage(SQSMessage processedMessage, Context context) { + * // processedMessage.getBody() will contain the content of the S3 Object + * } + * </pre> + * <p> + * <b>Note 1</b>: The message object (SQSMessage or SNSRecord) is modified in-place to avoid duplicating + * the large blob in memory. The message body will be replaced with the S3 object content. + * <p> + * <b>Note 2</b>: Retrieving payloads and deleting objects from S3 will increase the duration of the Lambda function. + * <p> + * <b>Note 3</b>: Make sure to configure your function with enough memory to be able to retrieve S3 objects. + * + * @see LargeMessage + */ +public final class LargeMessages { + + private static final Logger LOG = LoggerFactory.getLogger(LargeMessages.class); + + private LargeMessages() { + // Utility class + } + + /** + * Process a large message and execute the function with the processed message. + * <p> + * The S3 object will be deleted after processing (default behavior). + * To disable S3 object deletion, use {@link #processLargeMessage(Object, Function, boolean)}. + * <p> + * Example usage: + * <pre> + * String returnValueOfFunction = LargeMessages.processLargeMessage(sqsMessage, this::handleMessage); + * String returnValueOfFunction = LargeMessages.processLargeMessage(sqsMessage, processedMsg -> processOrder(processedMsg, orderId, amount)); + * </pre> + * + * @param message the message to process (SQSMessage or SNSRecord) + * @param function the function to execute with the processed message + * @param <T> the message type + * @param <R> the return type of the function + * @return the result of the function execution + */ + public static <T, R> R processLargeMessage(T message, Function<T, R> function) { + return processLargeMessage(message, function, true); + } + + /** + * Process a large message and execute the function with the processed message. + * <p> + * Example usage: + * <pre> + * String returnValueOfFunction = LargeMessages.processLargeMessage(sqsMessage, this::handleMessage, false); + * String returnValueOfFunction = LargeMessages.processLargeMessage(sqsMessage, processedMsg -> processOrder(processedMsg, orderId, amount), false); + * </pre> + * + * @param message the message to process (SQSMessage or SNSRecord) + * @param function the function to execute with the processed message + * @param deleteS3Object whether to delete the S3 object after processing + * @param <T> the message type + * @param <R> the return type of the function + * @return the result of the function execution + */ + public static <T, R> R processLargeMessage(T message, Function<T, R> function, boolean deleteS3Object) { + Optional<LargeMessageProcessor<?>> processor = LargeMessageProcessorFactory.get(message); + + if (!processor.isPresent()) { + LOG.warn("Unsupported message type [{}], proceeding without large message processing", + message.getClass()); + return function.apply(message); + } + + try { + @SuppressWarnings("unchecked") + LargeMessageProcessor<T> typedProcessor = (LargeMessageProcessor<T>) processor.get(); + return typedProcessor.process(message, function::apply, deleteS3Object); + } catch (RuntimeException e) { + throw e; + } catch (Throwable t) { + throw new LargeMessageProcessingException("Failed to process large message", t); + } + } +} diff --git a/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageAspect.java b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageAspect.java new file mode 100644 index 000000000..0a8b93095 --- /dev/null +++ b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageAspect.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages.internal; + +import java.util.Optional; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import software.amazon.lambda.powertools.largemessages.LargeMessage; + +/** + * Handle {@link LargeMessage} annotations. + */ +@Aspect +public class LargeMessageAspect { + + private static final Logger LOG = LoggerFactory.getLogger(LargeMessageAspect.class); + + @SuppressWarnings({ "EmptyMethod" }) + @Pointcut("@annotation(largeMessage)") + public void callAt(LargeMessage largeMessage) { + // Pointcut method - body intentionally empty + } + + @SuppressWarnings("unchecked") + @Around(value = "callAt(largeMessage) && execution(@LargeMessage * *.*(..))", argNames = "pjp,largeMessage") + public Object around(ProceedingJoinPoint pjp, LargeMessage largeMessage) throws Throwable { + Object[] proceedArgs = pjp.getArgs(); + + if (proceedArgs.length == 0) { + LOG.warn("@LargeMessage annotation is placed on a method without any message to process, proceeding"); + return pjp.proceed(proceedArgs); + } + + Object message = proceedArgs[0]; + Optional<LargeMessageProcessor<?>> largeMessageProcessor = LargeMessageProcessorFactory.get(message); + + if (!largeMessageProcessor.isPresent()) { + LOG.warn("@LargeMessage annotation is placed on a method with unsupported message type [{}], proceeding", + message.getClass()); + return pjp.proceed(proceedArgs); + } + + return ((LargeMessageProcessor<Object>) largeMessageProcessor.get()).process(message, + msg -> pjp.proceed(proceedArgs), largeMessage.deleteS3Object()); + } + +} diff --git a/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageFunction.java b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageFunction.java new file mode 100644 index 000000000..1690eedac --- /dev/null +++ b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageFunction.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages.internal; + +/** + * Functional interface for large message processing. + * <p> + * This interface is similar to {@link java.util.function.Function} but throws {@link Throwable} + * instead of no exceptions. This is necessary to support AspectJ's {@code ProceedingJoinPoint.proceed()} + * which throws {@code Throwable}, allowing exceptions to bubble up naturally without wrapping. + * <p> + * This interface should not be exposed to user-facing APIs such as + * {@link software.amazon.lambda.powertools.largemessages.LargeMessages}. These should use plain + * {@link java.util.function.Function}. + * + * @param <T> the input type (message type) + * @param <R> the return type of the function + */ +@FunctionalInterface +public interface LargeMessageFunction<T, R> { + @SuppressWarnings("java:S112") // Throwable is required for AspectJ compatibility + R apply(T processedMessage) throws Throwable; +} diff --git a/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageProcessor.java b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageProcessor.java new file mode 100644 index 000000000..c41af0cea --- /dev/null +++ b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageProcessor.java @@ -0,0 +1,155 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages.internal; + +import static java.lang.String.format; + +import java.nio.charset.StandardCharsets; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.lambda.powertools.largemessages.LargeMessageConfig; +import software.amazon.lambda.powertools.largemessages.LargeMessageProcessingException; +import software.amazon.payloadoffloading.S3BackedPayloadStore; +import software.amazon.payloadoffloading.S3Dao; + +/** + * Abstract processor for Large Messages. + * <p> + * Handles the download from S3 and replaces the S3 pointer with the actual content + * of the S3 Object, leveraging the payloadoffloading library. + * + * @param <T> any message type that supports Large Messages with S3 pointers + * ({@link com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage} + * and {@link com.amazonaws.services.lambda.runtime.events.SNSEvent.SNSRecord}) + */ +public abstract class LargeMessageProcessor<T> { + protected static final String RESERVED_ATTRIBUTE_NAME = "ExtendedPayloadSize"; + private static final Logger LOG = LoggerFactory.getLogger(LargeMessageProcessor.class); + + private final S3Client s3Client = LargeMessageConfig.get().getS3Client(); + private final S3BackedPayloadStore payloadStore = new S3BackedPayloadStore(new S3Dao(s3Client), "DUMMY"); + + /** + * Process a large message using a functional interface. + * + * @param message the message to process + * @param function the function to execute with the processed message + * @param deleteS3Object whether to delete the S3 object after processing + * @param <R> the return type of the wrapped function + * @return the result of the function execution + * @throws Throwable if an error occurs during processing + */ + public <R> R process(T message, LargeMessageFunction<T, R> function, boolean deleteS3Object) throws Throwable { + if (!isLargeMessage(message)) { + LOG.warn("Not a large message, proceeding"); + return function.apply(message); + } + + String payloadPointer = getMessageContent(message); + if (payloadPointer == null) { + LOG.warn("No content in the message, proceeding"); + return function.apply(message); + } + + // legacy attribute (sqs only) + payloadPointer = payloadPointer.replace("com.amazon.sqs.javamessaging.MessageS3Pointer", + "software.amazon.payloadoffloading.PayloadS3Pointer"); + + if (LOG.isInfoEnabled()) { + LOG.info("Large message [{}]: retrieving content from S3", getMessageId(message)); + } + + String s3ObjectContent = getS3ObjectContent(payloadPointer); + + if (LOG.isDebugEnabled()) { + LOG.debug("Large message [{}] retrieved in S3 [{}]: {}KB", getMessageId(message), payloadPointer, + s3ObjectContent.getBytes(StandardCharsets.UTF_8).length / 1024); + } + + updateMessageContent(message, s3ObjectContent); + removeLargeMessageAttributes(message); + + R result = function.apply(message); + + if (deleteS3Object) { + if (LOG.isInfoEnabled()) { + LOG.info("Large message [{}]: deleting object from S3", getMessageId(message)); + } + deleteS3Object(payloadPointer); + } + + return result; + } + + /** + * Retrieve the message ID. + * + * @param message the message + * @return the message ID + */ + protected abstract String getMessageId(T message); + + /** + * Retrieve the content of the message (e.g., body of an SQSMessage). + * + * @param message the message + * @return the message content + */ + protected abstract String getMessageContent(T message); + + /** + * Update the message content (e.g., body of an SQSMessage). + * + * @param message the message + * @param messageContent the new message content + */ + protected abstract void updateMessageContent(T message, String messageContent); + + /** + * Check if the message is a large message (based on message attributes). + * + * @param message the message + * @return true if the message is a large message, false otherwise + */ + protected abstract boolean isLargeMessage(T message); + + /** + * Remove the large message indicator from message attributes. + * + * @param message the message + */ + protected abstract void removeLargeMessageAttributes(T message); + + private String getS3ObjectContent(String payloadPointer) { + try { + return payloadStore.getOriginalPayload(payloadPointer); + } catch (SdkException e) { + throw new LargeMessageProcessingException(format("Failed processing S3 record [%s]", payloadPointer), e); + } + } + + private void deleteS3Object(String payloadPointer) { + try { + payloadStore.deleteOriginalPayload(payloadPointer); + } catch (SdkException e) { + throw new LargeMessageProcessingException(format("Failed deleting S3 record [%s]", payloadPointer), e); + } + } + +} diff --git a/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageProcessorFactory.java b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageProcessorFactory.java new file mode 100644 index 000000000..06ee92968 --- /dev/null +++ b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageProcessorFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages.internal; + +import java.util.Optional; + +import com.amazonaws.services.lambda.runtime.events.SNSEvent.SNSRecord; +import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; + +public final class LargeMessageProcessorFactory { + + private LargeMessageProcessorFactory() { + // Utility class + } + + public static Optional<LargeMessageProcessor<?>> get(Object message) { + if (message instanceof SQSMessage) { + return Optional.of(new LargeSQSMessageProcessor()); + } else if (message instanceof SNSRecord) { + return Optional.of(new LargeSNSMessageProcessor()); + } else { + return Optional.empty(); + } + } +} diff --git a/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessagesUserAgentInterceptor.java b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessagesUserAgentInterceptor.java new file mode 100644 index 000000000..87d179dd7 --- /dev/null +++ b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessagesUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.largemessages.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-largemessages module is on the classpath. + */ +public final class LargeMessagesUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("large-messages"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeSNSMessageProcessor.java b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeSNSMessageProcessor.java new file mode 100644 index 000000000..1ed7f5eaa --- /dev/null +++ b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeSNSMessageProcessor.java @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages.internal; + +import com.amazonaws.services.lambda.runtime.events.SNSEvent.MessageAttribute; +import com.amazonaws.services.lambda.runtime.events.SNSEvent.SNSRecord; +import java.util.HashMap; +import java.util.Map; + +class LargeSNSMessageProcessor extends LargeMessageProcessor<SNSRecord> { + + @Override + protected String getMessageId(SNSRecord message) { + return message.getSNS().getMessageId(); + } + + @Override + protected String getMessageContent(SNSRecord message) { + return message.getSNS().getMessage(); + } + + @Override + protected void updateMessageContent(SNSRecord message, String messageContent) { + message.getSNS().setMessage(messageContent); + } + + @Override + protected boolean isLargeMessage(SNSRecord message) { + Map<String, MessageAttribute> msgAttributes = message.getSNS().getMessageAttributes(); + return msgAttributes != null && msgAttributes.containsKey(RESERVED_ATTRIBUTE_NAME); + } + + @Override + protected void removeLargeMessageAttributes(SNSRecord message) { + // message.getSNS().getMessageAttributes() does not support remove operation, copy to new map + Map<String, MessageAttribute> newAttributes = new HashMap<>(message.getSNS().getMessageAttributes()); + newAttributes.remove(RESERVED_ATTRIBUTE_NAME); + message.getSNS().setMessageAttributes(newAttributes); + } +} diff --git a/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeSQSMessageProcessor.java b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeSQSMessageProcessor.java new file mode 100644 index 000000000..7fd79b7c9 --- /dev/null +++ b/powertools-large-messages/src/main/java/software/amazon/lambda/powertools/largemessages/internal/LargeSQSMessageProcessor.java @@ -0,0 +1,175 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages.internal; + +import com.amazonaws.services.lambda.runtime.events.SQSEvent.MessageAttribute; +import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.utils.BinaryUtils; +import software.amazon.awssdk.utils.Md5Utils; + +class LargeSQSMessageProcessor extends LargeMessageProcessor<SQSMessage> { + + private static final Logger LOG = LoggerFactory.getLogger(LargeSQSMessageProcessor.class); + private static final String LEGACY_RESERVED_ATTRIBUTE_NAME = "SQSLargePayloadSize"; + private static final int INTEGER_SIZE_IN_BYTES = 4; + private static final byte STRING_TYPE_FIELD_INDEX = 1; + private static final byte BINARY_TYPE_FIELD_INDEX = 2; + private static final byte STRING_LIST_TYPE_FIELD_INDEX = 3; + private static final byte BINARY_LIST_TYPE_FIELD_INDEX = 4; + + /** + * Compute the MD5 of the message body.<br/> + * Inspired from {@code software.amazon.awssdk.services.sqs.internal.MessageMD5ChecksumInterceptor}.<br/> + * package protected for testing purpose. + * + * @param messageBody body of the SQS Message + * @return the MD5 digest of the SQS Message body (or empty in case of error) + */ + static Optional<String> calculateMessageBodyMd5(String messageBody) { + byte[] expectedMd5; + try { + expectedMd5 = Md5Utils.computeMD5Hash(messageBody.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + LOG.warn("Unable to calculate the MD5 hash of the message body. ", e); + return Optional.empty(); + } + return Optional.of(BinaryUtils.toHex(expectedMd5)); + } + + /** + * Compute the MD5 of the message attributes.<br/> + * Inspired from {@code software.amazon.awssdk.services.sqs.internal.MessageMD5ChecksumInterceptor}.<br/> + * package protected for testing purpose. + * + * @param messageAttributes attributes of the SQS Message + * @return the MD5 digest of the SQS Message attributes (or empty in case of error) + */ + @SuppressWarnings("squid:S4790") // MD5 algorithm is used by SQS, we must use MD5 + static Optional<String> calculateMessageAttributesMd5(final Map<String, MessageAttribute> messageAttributes) { + List<String> sortedAttributeNames = new ArrayList<>(messageAttributes.keySet()); + Collections.sort(sortedAttributeNames); + + MessageDigest md5Digest; + try { + md5Digest = MessageDigest.getInstance("MD5"); + + for (String attrName : sortedAttributeNames) { + MessageAttribute attrValue = messageAttributes.get(attrName); + + // Encoded Name + updateLengthAndBytes(md5Digest, attrName); + + // Encoded Type + updateLengthAndBytes(md5Digest, attrValue.getDataType()); + + // Encoded Value + if (attrValue.getStringValue() != null) { + md5Digest.update(STRING_TYPE_FIELD_INDEX); + updateLengthAndBytes(md5Digest, attrValue.getStringValue()); + } else if (attrValue.getBinaryValue() != null) { + md5Digest.update(BINARY_TYPE_FIELD_INDEX); + updateLengthAndBytes(md5Digest, attrValue.getBinaryValue()); + } else if (attrValue.getStringListValues() != null && + attrValue.getStringListValues().size() > 0) { + md5Digest.update(STRING_LIST_TYPE_FIELD_INDEX); + for (String strListMember : attrValue.getStringListValues()) { + updateLengthAndBytes(md5Digest, strListMember); + } + } else if (attrValue.getBinaryListValues() != null && + attrValue.getBinaryListValues().size() > 0) { + md5Digest.update(BINARY_LIST_TYPE_FIELD_INDEX); + for (ByteBuffer byteListMember : attrValue.getBinaryListValues()) { + updateLengthAndBytes(md5Digest, byteListMember); + } + } + } + } catch (Exception e) { + LOG.warn("Unable to calculate the MD5 hash of the message attributes. ", e); + return Optional.empty(); + } + + return Optional.of(BinaryUtils.toHex(md5Digest.digest())); + } + + /** + * Update the digest using a sequence of bytes that consists of the length (in 4 bytes) of the + * input String and the actual utf8-encoded byte values. + */ + private static void updateLengthAndBytes(MessageDigest digest, String str) { + byte[] utf8Encoded = str.getBytes(StandardCharsets.UTF_8); + ByteBuffer lengthBytes = ByteBuffer.allocate(INTEGER_SIZE_IN_BYTES).putInt(utf8Encoded.length); + digest.update(lengthBytes.array()); + digest.update(utf8Encoded); + } + + /** + * Update the digest using a sequence of bytes that consists of the length (in 4 bytes) of the + * input ByteBuffer and all the bytes it contains. + */ + private static void updateLengthAndBytes(MessageDigest digest, ByteBuffer binaryValue) { + ByteBuffer readOnlyBuffer = binaryValue.asReadOnlyBuffer(); + int size = readOnlyBuffer.remaining(); + ByteBuffer lengthBytes = ByteBuffer.allocate(INTEGER_SIZE_IN_BYTES).putInt(size); + digest.update(lengthBytes.array()); + digest.update(readOnlyBuffer); + } + + @Override + protected String getMessageId(SQSMessage message) { + return message.getMessageId(); + } + + @Override + protected String getMessageContent(SQSMessage message) { + return message.getBody(); + } + + @Override + protected void updateMessageContent(SQSMessage message, String messageContent) { + message.setBody(messageContent); + // we update the MD5 digest so it doesn't look tempered + message.setMd5OfBody(calculateMessageBodyMd5(messageContent).orElse(message.getMd5OfBody())); + } + + @Override + protected boolean isLargeMessage(SQSMessage message) { + Map<String, MessageAttribute> msgAttributes = message.getMessageAttributes(); + return msgAttributes != null && (msgAttributes.containsKey(RESERVED_ATTRIBUTE_NAME) || + msgAttributes.containsKey(LEGACY_RESERVED_ATTRIBUTE_NAME)); + } + + @Override + protected void removeLargeMessageAttributes(SQSMessage message) { + // message.getMessageAttributes() does not support remove operation, copy to new map + Map<String, MessageAttribute> newAttributes = new HashMap<>(message.getMessageAttributes()); + newAttributes.remove(RESERVED_ATTRIBUTE_NAME); + newAttributes.remove(LEGACY_RESERVED_ATTRIBUTE_NAME); + message.setMessageAttributes(newAttributes); + // we update the MD5 digest so it doesn't look tempered + message.setMd5OfMessageAttributes( + calculateMessageAttributesMd5(newAttributes).orElse(message.getMd5OfMessageAttributes())); + } +} diff --git a/powertools-large-messages/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-large-messages/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..ab5a6f378 --- /dev/null +++ b/powertools-large-messages/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.largemessages.internal.LargeMessagesUserAgentInterceptor diff --git a/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/LargeMessageConfigTest.java b/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/LargeMessageConfigTest.java new file mode 100644 index 000000000..b6bcaf6b5 --- /dev/null +++ b/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/LargeMessageConfigTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +public class LargeMessageConfigTest { + + @BeforeEach + public void setup() { + LargeMessageConfig.get().resetS3Client(); + } + + @AfterEach + public void tearDown() { + LargeMessageConfig.get().resetS3Client(); + } + + @Test + public void singleton_shouldNotChangeWhenCalledMultipleTimes() { + LargeMessageConfig.init().withS3Client(S3Client.builder().region(Region.US_EAST_1).build()); + LargeMessageConfig config = LargeMessageConfig.get(); + + LargeMessageConfig.init().withS3Client(null); + LargeMessageConfig config2 = LargeMessageConfig.get(); + + assertThat(config2).isEqualTo(config); + } + + @Test + public void singletonWithDefaultClient_shouldNotChangeWhenCalledMultipleTimes() { + S3Client s3Client = LargeMessageConfig.get().getS3Client(); + + LargeMessageConfig.init().withS3Client(S3Client.create()); + S3Client s3Client2 = LargeMessageConfig.get().getS3Client(); + + assertThat(s3Client2).isEqualTo(s3Client); + } +} diff --git a/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/LargeMessagesTest.java b/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/LargeMessagesTest.java new file mode 100644 index 000000000..7bd3e80e2 --- /dev/null +++ b/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/LargeMessagesTest.java @@ -0,0 +1,287 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +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.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.amazonaws.services.lambda.runtime.events.KinesisEvent.KinesisEventRecord; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; +import com.amazonaws.services.lambda.runtime.events.SNSEvent.SNS; +import com.amazonaws.services.lambda.runtime.events.SNSEvent.SNSRecord; +import com.amazonaws.services.lambda.runtime.events.SQSEvent.MessageAttribute; +import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; + +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.S3Exception; + +@ExtendWith(MockitoExtension.class) +class LargeMessagesTest { + + private static final String BIG_MSG = "A biiiiiiiig message"; + private static final String BUCKET_NAME = "bucketname"; + private static final String BUCKET_KEY = "c71eb2ae-37e0-4265-8909-32f4153faddf"; + private static final String BIG_MESSAGE_BODY = "[\"software.amazon.payloadoffloading.PayloadS3Pointer\", {\"s3BucketName\":\"" + + BUCKET_NAME + + "\", \"s3Key\":\"" + BUCKET_KEY + "\"}]"; + + @Mock + private S3Client s3Client; + + @BeforeEach + void init() throws NoSuchFieldException, IllegalAccessException { + // need to clean the s3Client with introspection (singleton) + Field client = LargeMessageConfig.class.getDeclaredField("s3Client"); + client.setAccessible(true); + client.set(LargeMessageConfig.get(), null); + LargeMessageConfig.init().withS3Client(s3Client); + } + + @Test + void testProcessLargeSQSMessage_shouldRetrieveFromS3AndDelete() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + + // when + String result = LargeMessages.processLargeMessage(sqsMessage, SQSMessage::getBody); + + // then + assertThat(result).isEqualTo(BIG_MSG); + ArgumentCaptor<DeleteObjectRequest> delete = ArgumentCaptor.forClass(DeleteObjectRequest.class); + verify(s3Client).deleteObject(delete.capture()); + assertThat(delete.getValue().bucket()).isEqualTo(BUCKET_NAME); + assertThat(delete.getValue().key()).isEqualTo(BUCKET_KEY); + } + + @Test + void testProcessLargeSQSMessage_withDeleteDisabled_shouldNotDelete() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + + // when + String result = LargeMessages.processLargeMessage(sqsMessage, SQSMessage::getBody, false); + + // then + assertThat(result).isEqualTo(BIG_MSG); + verify(s3Client, never()).deleteObject(any(DeleteObjectRequest.class)); + } + + @Test + void testProcessLargeSNSMessage_shouldRetrieveFromS3AndDelete() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + SNSRecord snsRecord = snsRecordWithMessage(BIG_MESSAGE_BODY, true); + + // when + String result = LargeMessages.processLargeMessage(snsRecord, msg -> msg.getSNS().getMessage()); + + // then + assertThat(result).isEqualTo(BIG_MSG); + verify(s3Client).deleteObject(any(DeleteObjectRequest.class)); + } + + @Test + void testProcessSmallMessage_shouldNotInteractWithS3() { + // given + SQSMessage sqsMessage = sqsMessageWithBody("Small message", false); + + // when + String result = LargeMessages.processLargeMessage(sqsMessage, SQSMessage::getBody); + + // then + assertThat(result).isEqualTo("Small message"); + verifyNoInteractions(s3Client); + } + + @Test + void testProcessUnsupportedMessageType_shouldCallHandlerDirectly() { + // given + KinesisEventRecord kinesisRecord = new KinesisEventRecord(); + kinesisRecord.setEventID("kinesis-123"); + + // when + String result = LargeMessages.processLargeMessage(kinesisRecord, KinesisEventRecord::getEventID); + + // then + assertThat(result).isEqualTo("kinesis-123"); + verifyNoInteractions(s3Client); + } + + @Test + void testProcessMessageWithNullBody_shouldCallHandler() { + // given + SQSMessage sqsMessage = sqsMessageWithBody(null, true); + + // when + String result = LargeMessages.processLargeMessage(sqsMessage, SQSMessage::getBody); + + // then + assertThat(result).isNull(); + verifyNoInteractions(s3Client); + } + + @Test + void testProcessMessage_whenS3GetFails_shouldThrowException() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))) + .thenThrow(S3Exception.create("Access denied", new Exception("Permission denied"))); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + + // when / then + assertThatThrownBy(() -> LargeMessages.processLargeMessage(sqsMessage, SQSMessage::getBody)) + .isInstanceOf(LargeMessageProcessingException.class) + .hasMessageContaining("Failed processing S3 record"); + } + + @Test + void testProcessMessage_whenS3DeleteFails_shouldThrowException() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + when(s3Client.deleteObject(any(DeleteObjectRequest.class))) + .thenThrow(S3Exception.create("Access denied", new Exception("Permission denied"))); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + + // when / then + assertThatThrownBy(() -> LargeMessages.processLargeMessage(sqsMessage, SQSMessage::getBody)) + .isInstanceOf(LargeMessageProcessingException.class) + .hasMessageContaining("Failed deleting S3 record"); + } + + @Test + void testProcessMessage_whenHandlerThrowsRuntimeException_shouldPropagate() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + + // when / then + assertThatThrownBy(() -> LargeMessages.processLargeMessage(sqsMessage, msg -> { + throw new IllegalStateException("Handler error"); + })) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Handler error"); + } + + @Test + void testProcessLargeMessage_withMultiParam_shouldRetrieveFromS3AndDelete() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + String orderId = "order-123"; + + // when + String result = LargeMessages.processLargeMessage(sqsMessage, msg -> processOrderSimple(msg, orderId)); + + // then + assertThat(result).isEqualTo("order-123-processed"); + verify(s3Client).deleteObject(any(DeleteObjectRequest.class)); + } + + @Test + void testProcessLargeMessage_withMultiParamAndDeleteDisabled_shouldNotDelete() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + String orderId = "order-456"; + + // when + String result = LargeMessages.processLargeMessage(sqsMessage, msg -> processOrderSimple(msg, orderId), false); + + // then + assertThat(result).isEqualTo("order-456-processed"); + verify(s3Client, never()).deleteObject(any(DeleteObjectRequest.class)); + } + + @Test + void testProcessLargeMessage_shouldModifyMessageInPlace() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + String originalBody = sqsMessage.getBody(); + + // when + LargeMessages.processLargeMessage(sqsMessage, msg -> { + assertThat(msg.getBody()).isEqualTo(BIG_MSG); + return null; + }); + + // then - verify the original message object was modified + assertThat(sqsMessage.getBody()).isEqualTo(BIG_MSG); + assertThat(sqsMessage.getBody()).isNotEqualTo(originalBody); + } + + private String processOrderSimple(SQSMessage message, String orderId) { + assertThat(message.getBody()).isEqualTo(BIG_MSG); + return orderId + "-processed"; + } + + private ResponseInputStream<GetObjectResponse> s3ObjectWithLargeMessage() { + return new ResponseInputStream<>(GetObjectResponse.builder().build(), + AbortableInputStream.create(new ByteArrayInputStream(BIG_MSG.getBytes()))); + } + + private SQSMessage sqsMessageWithBody(String messageBody, boolean largeMessage) { + SQSMessage sqsMessage = new SQSMessage(); + sqsMessage.setBody(messageBody); + if (messageBody != null) { + sqsMessage.setMd5OfBody("dummy-md5"); + } + + if (largeMessage) { + Map<String, MessageAttribute> attributeMap = new HashMap<>(); + MessageAttribute payloadAttribute = new MessageAttribute(); + payloadAttribute.setStringValue("300450"); + payloadAttribute.setDataType("Number"); + attributeMap.put("ExtendedPayloadSize", payloadAttribute); + + sqsMessage.setMessageAttributes(attributeMap); + sqsMessage.setMd5OfMessageAttributes("dummy-md5"); + } + return sqsMessage; + } + + private SNSRecord snsRecordWithMessage(String messageBody, boolean largeMessage) { + SNS sns = new SNS().withMessage(messageBody); + if (largeMessage) { + sns.setMessageAttributes(Collections.singletonMap("ExtendedPayloadSize", + new SNSEvent.MessageAttribute())); + } + return new SNSRecord().withSns(sns); + } +} diff --git a/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageAspectTest.java b/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageAspectTest.java new file mode 100644 index 000000000..b84709ddc --- /dev/null +++ b/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageAspectTest.java @@ -0,0 +1,365 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages.internal; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static software.amazon.lambda.powertools.largemessages.internal.LargeSQSMessageProcessor.calculateMessageAttributesMd5; +import static software.amazon.lambda.powertools.largemessages.internal.LargeSQSMessageProcessor.calculateMessageBodyMd5; + +import java.io.ByteArrayInputStream; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import org.assertj.core.api.Assertions; +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.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.amazonaws.services.lambda.runtime.events.KinesisEvent.KinesisEventRecord; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; +import com.amazonaws.services.lambda.runtime.events.SNSEvent.SNS; +import com.amazonaws.services.lambda.runtime.events.SNSEvent.SNSRecord; +import com.amazonaws.services.lambda.runtime.events.SQSEvent.MessageAttribute; +import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; + +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.S3Exception; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; +import software.amazon.lambda.powertools.largemessages.LargeMessage; +import software.amazon.lambda.powertools.largemessages.LargeMessageConfig; +import software.amazon.lambda.powertools.largemessages.LargeMessageProcessingException; + +@ExtendWith(MockitoExtension.class) +class LargeMessageAspectTest { + + private static final String BIG_MSG = "A biiiiiiiig message"; + private static final String BIG_MSG_MD5 = "919ebd392d8cb7161f95cb612a903d42"; + + private static final String BUCKET_NAME = "bucketname"; + private static final String BUCKET_KEY = "c71eb2ae-37e0-4265-8909-32f4153faddf"; + + private static final String BIG_MESSAGE_BODY = "[\"software.amazon.payloadoffloading.PayloadS3Pointer\", {\"s3BucketName\":\"" + + BUCKET_NAME + + "\", \"s3Key\":\"" + BUCKET_KEY + "\"}]"; + + @Mock + private S3Client s3Client; + + private final TestLambdaContext context = new TestLambdaContext(); + + @BeforeEach + void init() throws NoSuchFieldException, IllegalAccessException { + // need to clean the s3Client with introspection (singleton) + Field client = LargeMessageConfig.class.getDeclaredField("s3Client"); + client.setAccessible(true); + client.set(LargeMessageConfig.get(), null); + LargeMessageConfig.init().withS3Client(s3Client); + } + + @LargeMessage + private String processSQSMessage(SQSMessage sqsMessage, TestLambdaContext context) { + return sqsMessage.getBody(); + } + + @LargeMessage + private String processSQSMessageWithMd5Checks(SQSMessage transformedMessage, String initialBodyMD5, + String initialAttributesMD5) { + assertThat(transformedMessage.getMd5OfBody()).isNotEqualTo(initialBodyMD5); + assertThat(transformedMessage.getMd5OfBody()).isEqualTo(BIG_MSG_MD5); + + assertThat(transformedMessage.getMessageAttributes()).hasSize(3); + + assertThat(transformedMessage.getMd5OfMessageAttributes()).isNotEqualTo(initialAttributesMD5); + return transformedMessage.getBody(); + } + + @LargeMessage + private String processSNSMessageWithoutContext(SNSRecord snsRecord) { + return snsRecord.getSNS().getMessage(); + } + + @LargeMessage(deleteS3Object = false) + private String processSQSMessageNoDelete(SQSMessage sqsMessage, TestLambdaContext context) { + return sqsMessage.getBody(); + } + + @LargeMessage + private String processKinesisMessage(KinesisEventRecord kinesisEventRecord) { + return kinesisEventRecord.getEventID(); + } + + @LargeMessage + private String processNoMessage() { + return "Hello World"; + } + + @LargeMessage + private void verifyMessageObjectIsModified(SQSMessage sqsMessage) { + // This test verifies the message object itself is modified, not a copy + assertThat(sqsMessage.getBody()).isEqualTo(BIG_MSG); + assertThat(sqsMessage.getMd5OfBody()).isEqualTo(BIG_MSG_MD5); + } + + @Test + void testLargeSQSMessageWithDefaultDeletion() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + + // when + String message = processSQSMessage(sqsMessage, context); + + // then + assertThat(message).isEqualTo(BIG_MSG); + ArgumentCaptor<DeleteObjectRequest> delete = ArgumentCaptor.forClass(DeleteObjectRequest.class); + verify(s3Client).deleteObject(delete.capture()); + Assertions.assertThat(delete.getValue()) + .satisfies((Consumer<DeleteObjectRequest>) deleteObjectRequest -> { + assertThat(deleteObjectRequest.bucket()) + .isEqualTo(BUCKET_NAME); + + assertThat(deleteObjectRequest.key()) + .isEqualTo(BUCKET_KEY); + }); + } + + @Test + void testLargeSQSMessage_shouldChangeMd5OfBodyAndAttributes() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + + MessageAttribute stringListAttribute = new MessageAttribute(); + stringListAttribute.setStringListValues(Collections.singletonList("customAttributeValue")); + stringListAttribute.setDataType("StringList"); + + MessageAttribute binAttribute = new MessageAttribute(); + binAttribute.setBinaryValue(ByteBuffer.wrap("customAttributeValue".getBytes(StandardCharsets.UTF_8))); + binAttribute.setDataType("Binary"); + + MessageAttribute listBinAttribute = new MessageAttribute(); + listBinAttribute.setBinaryListValues( + Collections.singletonList(ByteBuffer.wrap("customAttributeValue".getBytes(StandardCharsets.UTF_8)))); + listBinAttribute.setDataType("BinaryList"); + + Map<String, MessageAttribute> attrs = new HashMap<>(); + attrs.put("stringListAttribute", stringListAttribute); + attrs.put("binAttribute", binAttribute); + attrs.put("listBinAttribute", listBinAttribute); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true, attrs); + + // when + String message = processSQSMessageWithMd5Checks(sqsMessage, sqsMessage.getMd5OfBody(), + sqsMessage.getMd5OfMessageAttributes()); + + // then + assertThat(message).isEqualTo(BIG_MSG); + } + + @Test + void testLargeSNSMessageWithDefaultDeletion() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + SNSRecord snsRecord = snsRecordWithMessage(BIG_MESSAGE_BODY, true); + + // when + String message = processSNSMessageWithoutContext(snsRecord); + + // then + assertThat(message).isEqualTo(BIG_MSG); + ArgumentCaptor<DeleteObjectRequest> delete = ArgumentCaptor.forClass(DeleteObjectRequest.class); + verify(s3Client).deleteObject(delete.capture()); + Assertions.assertThat(delete.getValue()) + .satisfies((Consumer<DeleteObjectRequest>) deleteObjectRequest -> { + assertThat(deleteObjectRequest.bucket()) + .isEqualTo(BUCKET_NAME); + + assertThat(deleteObjectRequest.key()) + .isEqualTo(BUCKET_KEY); + }); + } + + @Test + void testLargeSQSMessageWithNoDeletion_shouldNotDelete() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + + // when + String message = processSQSMessageNoDelete(sqsMessage, context); + + // then + assertThat(message).isEqualTo(BIG_MSG); + verify(s3Client, never()).deleteObject(any(DeleteObjectRequest.class)); + } + + @Test + void testKinesisMessage_shouldProceedWithoutS3() { + // given + KinesisEventRecord kinesisEventRecord = new KinesisEventRecord(); + kinesisEventRecord.setEventID("kinesis_id1234567890"); + + // when + String message = processKinesisMessage(kinesisEventRecord); + + // then + assertThat(message).isEqualTo("kinesis_id1234567890"); + verifyNoInteractions(s3Client); + } + + @Test + void testNoMessage_shouldProceedWithoutS3() { + // when + String message = processNoMessage(); + + // then + assertThat(message).isEqualTo("Hello World"); + verifyNoInteractions(s3Client); + } + + @Test + void testSmallMessage_shouldProceedWithoutS3() { + // given + SQSMessage sqsMessage = sqsMessageWithBody("This is small message", false); + + // when + String message = processSQSMessage(sqsMessage, context); + + // then + assertThat(message) + .isEqualTo("This is small message"); + verifyNoInteractions(s3Client); + } + + @Test + void testNullMessage_shouldProceedWithoutS3() { + // given + SQSMessage sqsMessage = sqsMessageWithBody(null, true); + + // when + String message = processSQSMessage(sqsMessage, context); + + // then + assertThat(message).isNull(); + verifyNoInteractions(s3Client); + } + + @Test + void testGetS3ObjectException_shouldThrowLargeMessageProcessingException() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenThrow(S3Exception.create("Permission denied", + new Exception("User is not allowed to access bucket " + BUCKET_NAME))); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + + // when / then + assertThatThrownBy(() -> processSQSMessage(sqsMessage, context)) + .isInstanceOf(LargeMessageProcessingException.class) + .hasMessage(format("Failed processing S3 record [%s]", BIG_MESSAGE_BODY)); + } + + @Test + void testDeleteS3ObjectException_shouldThrowLargeMessageProcessingException() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + when(s3Client.deleteObject(any(DeleteObjectRequest.class))).thenThrow(S3Exception.create("Permission denied", + new Exception("User is not allowed to access bucket " + BUCKET_NAME))); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + + // when / then + assertThatThrownBy(() -> processSQSMessage(sqsMessage, context)) + .isInstanceOf(LargeMessageProcessingException.class) + .hasMessage(format("Failed deleting S3 record [%s]", BIG_MESSAGE_BODY)); + } + + @Test + void testMessageObjectIsModifiedInPlace() { + // given + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); + SQSMessage sqsMessage = sqsMessageWithBody(BIG_MESSAGE_BODY, true); + String originalBody = sqsMessage.getBody(); + + // when + verifyMessageObjectIsModified(sqsMessage); + + // then - verify the original message object was modified + assertThat(sqsMessage.getBody()).isEqualTo(BIG_MSG); + assertThat(sqsMessage.getBody()).isNotEqualTo(originalBody); + assertThat(sqsMessage.getMd5OfBody()).isEqualTo(BIG_MSG_MD5); + } + + private ResponseInputStream<GetObjectResponse> s3ObjectWithLargeMessage() { + return new ResponseInputStream<>(GetObjectResponse.builder().build(), + AbortableInputStream.create(new ByteArrayInputStream(BIG_MSG.getBytes()))); + } + + private SQSMessage sqsMessageWithBody(String messageBody, boolean largeMessage) { + return sqsMessageWithBody(messageBody, largeMessage, null); + } + + private SQSMessage sqsMessageWithBody(String messageBody, boolean largeMessage, + Map<String, MessageAttribute> optionalAttributes) { + SQSMessage sqsMessage = new SQSMessage(); + sqsMessage.setBody(messageBody); + if (messageBody != null) { + sqsMessage.setMd5OfBody(calculateMessageBodyMd5(messageBody).orElseThrow( + () -> new RuntimeException("Unable to md5 body " + messageBody))); + } + + if (largeMessage) { + Map<String, MessageAttribute> attributeMap = new HashMap<>(); + if (optionalAttributes != null) { + attributeMap.putAll(optionalAttributes); + } + MessageAttribute payloadAttribute = new MessageAttribute(); + payloadAttribute.setStringValue("300450"); + payloadAttribute.setDataType("Number"); + attributeMap.put(LargeMessageProcessor.RESERVED_ATTRIBUTE_NAME, payloadAttribute); + + sqsMessage.setMessageAttributes(attributeMap); + sqsMessage.setMd5OfMessageAttributes(calculateMessageAttributesMd5(attributeMap).orElseThrow( + () -> new RuntimeException("Unable to md5 attributes " + attributeMap))); + } + return sqsMessage; + } + + private SNSRecord snsRecordWithMessage(String messageBody, boolean largeMessage) { + SNS sns = new SNS().withMessage(messageBody); + if (largeMessage) { + sns.setMessageAttributes(Collections.singletonMap(LargeMessageProcessor.RESERVED_ATTRIBUTE_NAME, + new SNSEvent.MessageAttribute())); + } + return new SNSRecord().withSns(sns); + } + +} diff --git a/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageProcessorFactoryTest.java b/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageProcessorFactoryTest.java new file mode 100644 index 000000000..3011c8189 --- /dev/null +++ b/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessageProcessorFactoryTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.largemessages.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import org.junit.jupiter.api.Test; + +public class LargeMessageProcessorFactoryTest { + + @Test + public void createLargeSQSMessageProcessor() { + assertThat(LargeMessageProcessorFactory.get(new SQSEvent.SQSMessage())) + .isPresent() + .get() + .isInstanceOf(LargeSQSMessageProcessor.class); + } + + @Test + public void createLargeSNSMessageProcessor() { + assertThat(LargeMessageProcessorFactory.get(new SNSEvent.SNSRecord())) + .isPresent() + .get() + .isInstanceOf(LargeSNSMessageProcessor.class); + } + + @Test + public void createUnknownMessageProcessor() { + assertThat(LargeMessageProcessorFactory.get(new KinesisEvent.KinesisEventRecord())).isNotPresent(); + } +} diff --git a/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessagesUserAgentInterceptorTest.java b/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessagesUserAgentInterceptorTest.java new file mode 100644 index 000000000..b32ee1176 --- /dev/null +++ b/powertools-large-messages/src/test/java/software/amazon/lambda/powertools/largemessages/internal/LargeMessagesUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.largemessages.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class LargeMessagesUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/LARGE-MESSAGES/"); + } +} \ No newline at end of file diff --git a/powertools-large-messages/src/test/resources/simplelogger.properties b/powertools-large-messages/src/test/resources/simplelogger.properties new file mode 100644 index 000000000..559c22385 --- /dev/null +++ b/powertools-large-messages/src/test/resources/simplelogger.properties @@ -0,0 +1,7 @@ +org.slf4j.simpleLogger.logFile=target/large-messages-test.log +org.slf4j.simpleLogger.defaultLogLevel=warn +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS +org.slf4j.simpleLogger.showThreadName=false +org.slf4j.simpleLogger.showLogName=true +org.slf4j.simpleLogger.showShortLogName=false diff --git a/powertools-logging/pom.xml b/powertools-logging/pom.xml index 32f40384f..200358e0b 100644 --- a/powertools-logging/pom.xml +++ b/powertools-logging/pom.xml @@ -1,49 +1,42 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + <project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> - <artifactId>powertools-logging</artifactId> - <packaging>jar</packaging> - <parent> <artifactId>powertools-parent</artifactId> <groupId>software.amazon.lambda</groupId> - <version>1.10.2</version> + <version>2.9.0</version> </parent> - <name>AWS Lambda Powertools Java library Logging</name> - <description> - A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. - </description> - <url>https://aws.amazon.com/lambda/</url> - <issueManagement> - <system>GitHub Issues</system> - <url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url> - </issueManagement> - <scm> - <url>https://github.com/awslabs/aws-lambda-powertools-java.git</url> - </scm> - <developers> - <developer> - <name>AWS Lambda Powertools team</name> - <organization>Amazon Web Services</organization> - <organizationUrl>https://aws.amazon.com/</organizationUrl> - </developer> - </developers> - - <distributionManagement> - <snapshotRepository> - <id>ossrh</id> - <url>https://aws.oss.sonatype.org/content/repositories/snapshots</url> - </snapshotRepository> - </distributionManagement> + <name>Powertools for AWS Lambda (Java) - Logging</name> + <description>Set of utility for better logging - common</description> + <artifactId>powertools-logging</artifactId> + <packaging>jar</packaging> <dependencies> <dependency> <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-core</artifactId> + <artifactId>powertools-common</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-serialization</artifactId> </dependency> <dependency> <groupId>com.amazonaws</groupId> @@ -54,24 +47,13 @@ <artifactId>jackson-databind</artifactId> </dependency> <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-layout-template-json</artifactId> - </dependency> - <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-core</artifactId> - </dependency> - <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-slf4j-impl</artifactId> - </dependency> - <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-api</artifactId> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> + <scope>provided</scope> </dependency> <!-- Test dependencies --> @@ -86,18 +68,18 @@ <scope>test</scope> </dependency> <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> <scope>test</scope> </dependency> <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> + <groupId>org.junit-pioneer</groupId> + <artifactId>junit-pioneer</artifactId> <scope>test</scope> </dependency> <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-inline</artifactId> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> <scope>test</scope> </dependency> <dependency> @@ -125,6 +107,88 @@ <artifactId>jsonassert</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> </dependencies> - -</project> \ No newline at end of file + <profiles> + <profile> + <id>generate-graalvm-files</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-logging</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + <build> + <resources> + <!-- GraalVM Native Image Configuration Files --> + <resource> + <directory>src/main/resources</directory> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <AWS_LAMBDA_LOG_FORMAT>JSON</AWS_LAMBDA_LOG_FORMAT> + <POWERTOOLS_SERVICE_NAME>testService</POWERTOOLS_SERVICE_NAME> + <AWS_LAMBDA_INITIALIZATION_TYPE>on-demand</AWS_LAMBDA_INITIALIZATION_TYPE> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml new file mode 100644 index 000000000..aa4aca181 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -0,0 +1,217 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>powertools-parent</artifactId> + <groupId>software.amazon.lambda</groupId> + <version>2.9.0</version> + <relativePath>../../pom.xml</relativePath> + </parent> + + <artifactId>powertools-logging-log4j</artifactId> + <packaging>jar</packaging> + <name>Powertools for AWS Lambda (Java) - Logging with Log4j2</name> + <description>Set of utility for better logging with log4j</description> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j2-impl</artifactId> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-layout-template-json</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit-pioneer</groupId> + <artifactId>junit-pioneer</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjweaver</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-tests</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.skyscreamer</groupId> + <artifactId>jsonassert</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <profiles> + <profile> + <id>generate-graalvm-files</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-log4j,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-logging-log4j</imageName> + <buildArgs> + <buildArg> + --initialize-at-build-time=org.junit.platform.launcher.core.DiscoveryIssueNotifier$1 + </buildArg> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + <build> + <resources> + <!-- GraalVM Native Image Configuration Files --> + <resource> + <directory>src/main/resources</directory> + </resource> + </resources> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <POWERTOOLS_SERVICE_NAME>testLog4j</POWERTOOLS_SERVICE_NAME> + <AWS_REGION>eu-central-1</AWS_REGION> + <_X_AMZN_TRACE_ID>Root=1-63441c4a-abcdef012345678912345678</_X_AMZN_TRACE_ID> + <AWS_LAMBDA_INITIALIZATION_TYPE>on-demand</AWS_LAMBDA_INITIALIZATION_TYPE> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java new file mode 100644 index 000000000..cef5b86ee --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java @@ -0,0 +1,262 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.apache.logging.log4j.layout.template.json.resolver; + +import static java.util.Arrays.stream; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.CORRELATION_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_ARN; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_NAME; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_REQUEST_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_TRACE_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_VERSION; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SAMPLING_RATE; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SERVICE; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.layout.template.json.util.JsonWriter; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import software.amazon.lambda.powertools.common.internal.LambdaConstants; +import software.amazon.lambda.powertools.common.internal.SystemWrapper; +import software.amazon.lambda.powertools.logging.argument.StructuredArgument; +import software.amazon.lambda.powertools.logging.internal.JsonSerializer; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; + +/** + * Custom {@link org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver} + * used by {@link org.apache.logging.log4j.layout.template.json.JsonTemplateLayout} + * to be able to recognize powertools fields in the LambdaJsonLayout.json file. + */ +final class PowertoolsResolver implements EventResolver { + + private static final EventResolver COLD_START_RESOLVER = new EventResolver() { + @Override + public boolean isResolvable(LogEvent logEvent) { + final String coldStart = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_COLD_START.getName()); + return null != coldStart; + } + + @Override + public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { + final String coldStart = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_COLD_START.getName()); + jsonWriter.writeBoolean(Boolean.parseBoolean(coldStart)); + } + }; + + private static final EventResolver FUNCTION_NAME_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> { + final String functionName = + logEvent.getContextData().getValue(FUNCTION_NAME.getName()); + jsonWriter.writeString(functionName); + }; + + private static final EventResolver FUNCTION_VERSION_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> { + final String functionVersion = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_VERSION.getName()); + jsonWriter.writeString(functionVersion); + }; + + private static final EventResolver FUNCTION_ARN_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> { + final String functionArn = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_ARN.getName()); + jsonWriter.writeString(functionArn); + }; + + private static final EventResolver FUNCTION_REQ_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> { + final String functionRequestId = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_REQUEST_ID.getName()); + jsonWriter.writeString(functionRequestId); + }; + + private static final EventResolver FUNCTION_MEMORY_RESOLVER = new EventResolver() { + @Override + public boolean isResolvable(LogEvent logEvent) { + final String functionMemory = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName()); + return null != functionMemory; + } + + @Override + public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { + final String functionMemory = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName()); + jsonWriter.writeNumber(Integer.parseInt(functionMemory)); + } + }; + + private static final EventResolver SAMPLING_RATE_RESOLVER = new EventResolver() { + @Override + public boolean isResolvable(LogEvent logEvent) { + final String samplingRate = + logEvent.getContextData().getValue(PowertoolsLoggedFields.SAMPLING_RATE.getName()); + try { + return null != samplingRate && Float.parseFloat(samplingRate) > 0.f; + } catch (NumberFormatException nfe) { + return false; + } + } + + @Override + public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { + final String samplingRate = + logEvent.getContextData().getValue(PowertoolsLoggedFields.SAMPLING_RATE.getName()); + jsonWriter.writeNumber(Float.parseFloat(samplingRate)); + } + }; + + private static final EventResolver XRAY_TRACE_RESOLVER = new EventResolver() { + @Override + public boolean isResolvable(LogEvent logEvent) { + final String traceId = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_TRACE_ID.getName()); + return null != traceId; + } + + @Override + public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { + final String traceId = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_TRACE_ID.getName()); + jsonWriter.writeString(traceId); + } + }; + + private static final EventResolver CORRELATION_ID_RESOLVER = new EventResolver() { + @Override + public boolean isResolvable(LogEvent logEvent) { + final String correlationId = + logEvent.getContextData().getValue(PowertoolsLoggedFields.CORRELATION_ID.getName()); + return null != correlationId; + } + + @Override + public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { + final String correlationId = + logEvent.getContextData().getValue(PowertoolsLoggedFields.CORRELATION_ID.getName()); + jsonWriter.writeString(correlationId); + } + }; + + private static final EventResolver SERVICE_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> { + final String service = logEvent.getContextData().getValue(PowertoolsLoggedFields.SERVICE.getName()); + jsonWriter.writeString(service); + }; + + private static final EventResolver REGION_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> + jsonWriter.writeString(SystemWrapper.getenv(LambdaConstants.AWS_REGION_ENV)); + + public static final String LAMBDA_ARN_REGEX = + "^arn:(aws|aws-us-gov|aws-cn):lambda:[a-zA-Z0-9-]+:\\d{12}:function:[a-zA-Z0-9-_]+(:[a-zA-Z0-9-_]+)?$"; + + private static final EventResolver ACCOUNT_ID_RESOLVER = new EventResolver() { + @Override + public boolean isResolvable(LogEvent logEvent) { + final String arn = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_ARN.getName()); + return null != arn && !arn.isEmpty() && arn.matches(LAMBDA_ARN_REGEX); + } + + @Override + public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { + final String arn = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_ARN.getName()); + jsonWriter.writeString(arn.split(":")[4]); + } + }; + + @SuppressWarnings("java:S106") + private static final EventResolver NON_POWERTOOLS_FIELD_RESOLVER = + (LogEvent logEvent, JsonWriter jsonWriter) -> { + StringBuilder stringBuilder = jsonWriter.getStringBuilder(); + try (JsonSerializer serializer = new JsonSerializer(stringBuilder)) { + + // remove dummy field to kick in powertools resolver + stringBuilder.setLength(stringBuilder.length() - 4); + + // log other MDC values + ReadOnlyStringMap contextData = logEvent.getContextData(); + contextData.forEach((key, value) -> { + if (!PowertoolsLoggedFields.stringValues().contains(key)) { + serializer.writeSeparator(); + serializer.writeObjectField(key, value); + } + }); + + // log structured arguments + Object[] arguments = logEvent.getMessage().getParameters(); + if (arguments != null) { + stream(arguments).filter(StructuredArgument.class::isInstance).forEach(argument -> { + try { + serializer.writeRaw(','); + ((StructuredArgument) argument).writeTo(serializer); + } catch (IOException e) { + System.err.printf("Failed to encode log event, error: %s.%n", e.getMessage()); + } + }); + } + } + }; + + private final EventResolver internalResolver; + + private static final Map<String, EventResolver> eventResolverMap = Collections + .unmodifiableMap(Stream.of(new Object[][] { + { SERVICE.getName(), SERVICE_RESOLVER }, + { FUNCTION_NAME.getName(), FUNCTION_NAME_RESOLVER }, + { FUNCTION_VERSION.getName(), FUNCTION_VERSION_RESOLVER }, + { FUNCTION_ARN.getName(), FUNCTION_ARN_RESOLVER }, + { FUNCTION_MEMORY_SIZE.getName(), FUNCTION_MEMORY_RESOLVER }, + { FUNCTION_REQUEST_ID.getName(), FUNCTION_REQ_RESOLVER }, + { FUNCTION_COLD_START.getName(), COLD_START_RESOLVER }, + { FUNCTION_TRACE_ID.getName(), XRAY_TRACE_RESOLVER }, + { CORRELATION_ID.getName(), CORRELATION_ID_RESOLVER }, + { SAMPLING_RATE.getName(), SAMPLING_RATE_RESOLVER }, + { "region", REGION_RESOLVER }, + { "account_id", ACCOUNT_ID_RESOLVER } + }).collect(Collectors.toMap(data -> (String) data[0], data -> (EventResolver) data[1]))); + + + PowertoolsResolver(final TemplateResolverConfig config) { + final String fieldName = config.getString("field"); + if (fieldName == null) { + internalResolver = NON_POWERTOOLS_FIELD_RESOLVER; + } else { + internalResolver = eventResolverMap.get(fieldName); + if (internalResolver == null) { + throw new IllegalArgumentException("Unknown field: " + fieldName); + } + } + } + + @Override + public void resolve(LogEvent value, JsonWriter jsonWriter) { + internalResolver.resolve(value, jsonWriter); + } + + @Override + public boolean isResolvable(LogEvent value) { + return value != null && value.getContextData() != null && internalResolver.isResolvable(value); + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverFactory.java b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverFactory.java new file mode 100644 index 000000000..297c00691 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.apache.logging.log4j.layout.template.json.resolver; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +/** + * Factory for {@link PowertoolsResolver}. Log4j plugin to process powertools fields in the layout.json + */ +@Plugin(name = "PowertoolsResolverFactory", category = TemplateResolverFactory.CATEGORY) +public final class PowertoolsResolverFactory implements EventResolverFactory { + + private static final PowertoolsResolverFactory INSTANCE = new PowertoolsResolverFactory(); + private static final String RESOLVER_NAME = "powertools"; + + private PowertoolsResolverFactory() { + } + + @PluginFactory + public static PowertoolsResolverFactory getInstance() { + return INSTANCE; + } + + @Override + public String getName() { + return RESOLVER_NAME; + } + + @Override + public TemplateResolver<LogEvent> create(EventResolverContext context, + TemplateResolverConfig config) { + return new PowertoolsResolver(config); + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4j/BufferingAppender.java b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4j/BufferingAppender.java new file mode 100644 index 000000000..fcf4a2040 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4j/BufferingAppender.java @@ -0,0 +1,201 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.log4j; + +import java.io.Serializable; +import java.util.Deque; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.AppenderRef; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; + +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.logging.internal.BufferManager; +import software.amazon.lambda.powertools.logging.internal.KeyBuffer; + +/** + * A Log4j2 appender that buffers log events by AWS X-Ray trace ID for optimized Lambda logging. + * + * <p>This appender is designed specifically for AWS Lambda functions to reduce log ingestion + * by buffering lower-level logs and only outputting them when errors occur, preserving + * full context for troubleshooting while minimizing routine log volume. + * + * <h3>Key Features:</h3> + * <ul> + * <li><strong>Trace-based buffering:</strong> Groups logs by AWS X-Ray trace ID</li> + * <li><strong>Selective output:</strong> Only buffers logs at or below configured verbosity level</li> + * <li><strong>Auto-flush on errors:</strong> Automatically outputs buffered logs when ERROR/FATAL events occur</li> + * <li><strong>Memory management:</strong> Prevents memory leaks with configurable buffer size limits</li> + * <li><strong>Overflow protection:</strong> Warns when logs are discarded due to buffer limits</li> + * </ul> + * + * <h3>Configuration Example:</h3> + * <pre>{@code + * <BufferingAppender name="BufferedAppender" + * bufferAtVerbosity="DEBUG" + * maxBytes="20480" + * flushOnErrorLog="true"> + * <AppenderRef ref="ConsoleAppender"/> + * </BufferingAppender> + * }</pre> + * + * <h3>Configuration Parameters:</h3> + * <ul> + * <li><strong>bufferAtVerbosity:</strong> Log level to buffer (default: DEBUG). Logs at this level and below are buffered</li> + * <li><strong>maxBytes:</strong> Maximum buffer size in bytes per trace ID (default: 20480)</li> + * <li><strong>flushOnErrorLog:</strong> Whether to flush buffer on ERROR/FATAL logs (default: true)</li> + * </ul> + * + * <h3>Behavior:</h3> + * <ul> + * <li>During Lambda INIT phase (no trace ID): logs are output directly</li> + * <li>During Lambda execution (with trace ID): logs are buffered or output based on level</li> + * <li>When buffer overflows: oldest logs are discarded and a warning is logged</li> + * <li>On Lambda completion: buffer is auto-cleared when used with {@code @Logging} annotation</li> + * </ul> + * + * @see software.amazon.lambda.powertools.logging.PowertoolsLogging#flushBuffer() + */ +@Plugin(name = "BufferingAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE) +public class BufferingAppender extends AbstractAppender implements BufferManager { + + private final AppenderRef[] appenderRefs; + private final Configuration configuration; + private final Level bufferAtVerbosity; + private final boolean flushOnErrorLog; + private final KeyBuffer<String, LogEvent> buffer; + + @SuppressWarnings("java:S107") // Constructor has too many parameters, which is OK for a Log4j2 plugin + protected BufferingAppender(String name, Filter filter, Layout<? extends Serializable> layout, + AppenderRef[] appenderRefs, Configuration configuration, Level bufferAtVerbosity, int maxBytes, + boolean flushOnErrorLog) { + super(name, filter, layout, false, null); + this.appenderRefs = appenderRefs; + this.configuration = configuration; + this.bufferAtVerbosity = bufferAtVerbosity; + this.flushOnErrorLog = flushOnErrorLog; + this.buffer = new KeyBuffer<>(maxBytes, event -> event.getMessage().getFormattedMessage().length(), + this::logOverflowWarning); + } + + @Override + public void append(LogEvent event) { + if (appenderRefs == null || appenderRefs.length == 0) { + return; + } + + LambdaHandlerProcessor.getXrayTraceId().ifPresentOrElse( + traceId -> { + if (shouldBuffer(event.getLevel())) { + bufferEvent(traceId, event); + } else { + callAppenders(event); + } + + // Flush buffer on error logs if configured + if (flushOnErrorLog && event.getLevel().isMoreSpecificThan(Level.WARN)) { + flushBuffer(traceId); + } + }, + () -> callAppenders(event) // No trace ID (INIT phase), log directly + ); + } + + private void callAppenders(LogEvent event) { + for (AppenderRef ref : appenderRefs) { + Appender appender = configuration.getAppender(ref.getRef()); + if (appender != null) { + appender.append(event); + } + } + } + + private boolean shouldBuffer(Level level) { + return level.isLessSpecificThan(bufferAtVerbosity) || level.equals(bufferAtVerbosity); + } + + private void bufferEvent(String traceId, LogEvent event) { + LogEvent immutableEvent = Log4jLogEvent.createMemento(event); + buffer.add(traceId, immutableEvent); + } + + public void clearBuffer() { + LambdaHandlerProcessor.getXrayTraceId().ifPresent(buffer::clear); + } + + public void flushBuffer() { + LambdaHandlerProcessor.getXrayTraceId().ifPresent(this::flushBuffer); + } + + private void flushBuffer(String traceId) { + Deque<LogEvent> events = buffer.removeAll(traceId); + if (events != null) { + events.forEach(this::callAppenders); + } + } + + @PluginFactory + @SuppressWarnings("java:S107") // Method has too many parameters, which is OK for a Log4j2 plugin factory + public static BufferingAppender createAppender( + @PluginAttribute("name") String name, + @PluginElement("Filter") Filter filter, + @PluginElement("Layout") Layout<? extends Serializable> layout, + @PluginElement("AppenderRef") AppenderRef[] appenderRefs, + @PluginConfiguration Configuration configuration, + @PluginAttribute(value = "bufferAtVerbosity", defaultString = "DEBUG") String bufferAtVerbosity, + @PluginAttribute(value = "maxBytes", defaultInt = 20480) int maxBytes, + @PluginAttribute(value = "flushOnErrorLog", defaultBoolean = true) boolean flushOnErrorLog) { + + if (name == null) { + LOGGER.error("No name provided for BufferingAppender"); + return null; + } + + Level level = Level.getLevel(bufferAtVerbosity); + if (level == null) { + level = Level.DEBUG; + } + + return new BufferingAppender(name, filter, layout, appenderRefs, configuration, level, maxBytes, + flushOnErrorLog); + } + + private void logOverflowWarning() { + // Create a properly formatted warning event and send directly to child appenders. Used to avoid circular + // dependency between KeyBuffer and BufferingAppender. + SimpleMessage message = new SimpleMessage( + "Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer."); + LogEvent warningEvent = Log4jLogEvent.newBuilder() + .setLoggerName(BufferingAppender.class.getName()) + .setLevel(Level.WARN) + .setMessage(message) + .setTimeMillis(System.currentTimeMillis()) + .build(); + callAppenders(warningEvent); + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4j/internal/Log4jLoggingManager.java b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4j/internal/Log4jLoggingManager.java new file mode 100644 index 000000000..90bbe1d32 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4j/internal/Log4jLoggingManager.java @@ -0,0 +1,80 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.log4j.internal; + +import java.util.Collection; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configurator; +import org.slf4j.Logger; + +import software.amazon.lambda.powertools.logging.internal.BufferManager; +import software.amazon.lambda.powertools.logging.internal.LoggingManager; +import software.amazon.lambda.powertools.logging.log4j.BufferingAppender; + +/** + * LoggingManager for Log4j2 that provides log level management and buffer operations. + * Implements both {@link LoggingManager} and {@link BufferManager} interfaces. + */ +public class Log4jLoggingManager implements LoggingManager, BufferManager { + + /** + * @inheritDoc + */ + @Override + @SuppressWarnings("java:S4792") + public void setLogLevel(org.slf4j.event.Level logLevel) { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configurator.setAllLevels(LogManager.getRootLogger().getName(), Level.getLevel(logLevel.toString())); + ctx.updateLoggers(); + } + + /** + * @inheritDoc + */ + @Override + public org.slf4j.event.Level getLogLevel(Logger logger) { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + return org.slf4j.event.Level.valueOf(ctx.getLogger(logger.getName()).getLevel().toString()); + } + + /** + * @inheritDoc + */ + @Override + public void flushBuffer() { + getBufferingAppenders().forEach(BufferingAppender::flushBuffer); + } + + /** + * @inheritDoc + */ + @Override + public void clearBuffer() { + getBufferingAppenders().forEach(BufferingAppender::clearBuffer); + } + + private Collection<BufferingAppender> getBufferingAppenders() { + // Search all buffering appenders to avoid relying on the appender name given by the user + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + return ctx.getConfiguration().getAppenders().values().stream() + .filter(BufferingAppender.class::isInstance) + .map(BufferingAppender.class::cast) + .collect(Collectors.toList()); + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4j/internal/Log4jUserAgentInterceptor.java b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4j/internal/Log4jUserAgentInterceptor.java new file mode 100644 index 000000000..510ee858c --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4j/internal/Log4jUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.logging.log4j.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-logging-log4j module is on the classpath. + */ +public final class Log4jUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("logging-log4j"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json new file mode 100644 index 000000000..58b30f60e --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json @@ -0,0 +1,93 @@ +{ + "@timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "timeZone": "UTC" + } + }, + "ecs.version": "1.2.0", + "log.level": { + "$resolver": "level", + "field": "name" + }, + "message": { + "$resolver": "message" + }, + "error.type": { + "$resolver": "exception", + "field": "className" + }, + "error.message": { + "$resolver": "exception", + "field": "message" + }, + "error.stack_trace": { + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": true + } + }, + "service.name": { + "$resolver": "powertools", + "field": "service" + }, + "service.version": { + "$resolver": "powertools", + "field": "function_version" + }, + "log.logger": { + "$resolver": "logger", + "field": "name" + }, + "process.thread.name": { + "$resolver": "thread", + "field": "name" + }, + "cloud.provider": "aws", + "cloud.service.name": "lambda", + "cloud.region": { + "$resolver": "powertools", + "field": "region" + }, + "cloud.account.id": { + "$resolver": "powertools", + "field": "account_id" + }, + "faas.id": { + "$resolver": "powertools", + "field": "function_arn" + }, + "faas.name": { + "$resolver": "powertools", + "field": "function_name" + }, + "faas.version": { + "$resolver": "powertools", + "field": "function_version" + }, + "faas.memory": { + "$resolver": "powertools", + "field": "function_memory_size" + }, + "faas.execution": { + "$resolver": "powertools", + "field": "function_request_id" + }, + "faas.coldstart": { + "$resolver": "powertools", + "field": "cold_start" + }, + "trace.id": { + "$resolver": "powertools", + "field": "xray_trace_id" + }, + "correlation.id": { + "$resolver": "powertools", + "field": "correlation_id" + }, + "": { + "$resolver": "powertools" + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json new file mode 100644 index 000000000..793006502 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json @@ -0,0 +1,75 @@ +{ + "level": { + "$resolver": "level", + "field": "name" + }, + "message": { + "$resolver": "message" + }, + "error": { + "message": { + "$resolver": "exception", + "field": "message" + }, + "name": { + "$resolver": "exception", + "field": "className" + }, + "stack": { + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": true + } + } + }, + "cold_start": { + "$resolver": "powertools", + "field": "cold_start" + }, + "function_arn": { + "$resolver": "powertools", + "field": "function_arn" + }, + "function_memory_size": { + "$resolver": "powertools", + "field": "function_memory_size" + }, + "function_name": { + "$resolver": "powertools", + "field": "function_name" + }, + "function_request_id": { + "$resolver": "powertools", + "field": "function_request_id" + }, + "function_version": { + "$resolver": "powertools", + "field": "function_version" + }, + "sampling_rate": { + "$resolver": "powertools", + "field": "sampling_rate" + }, + "service": { + "$resolver": "powertools", + "field": "service" + }, + "timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + } + }, + "xray_trace_id": { + "$resolver": "powertools", + "field": "xray_trace_id" + }, + "correlation_id": { + "$resolver": "powertools", + "field": "correlation_id" + }, + "": { + "$resolver": "powertools" + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-log4j/jni-config.json b/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-log4j/jni-config.json new file mode 100644 index 000000000..c8b081385 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-log4j/jni-config.json @@ -0,0 +1,10 @@ +[ +{ + "name":"java.lang.Boolean", + "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"sun.management.VMManagementImpl", + "fields":[{"name":"compTimeMonitoringSupport"}, {"name":"currentThreadCpuTimeSupport"}, {"name":"objectMonitorUsageSupport"}, {"name":"otherThreadCpuTimeSupport"}, {"name":"remoteDiagnosticCommandsSupport"}, {"name":"synchronizerUsageSupport"}, {"name":"threadAllocatedMemorySupport"}, {"name":"threadContentionMonitoringSupport"}] +} +] diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-log4j/reflect-config.json b/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-log4j/reflect-config.json new file mode 100644 index 000000000..43084dad2 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-log4j/reflect-config.json @@ -0,0 +1,1091 @@ +[ +{ + "name":"[Ljava.lang.Object;" +}, +{ + "name":"[Ljava.lang.String;" +}, +{ + "name":"[Lorg.apache.logging.log4j.core.Appender;" +}, +{ + "name":"[Lorg.apache.logging.log4j.core.config.AppenderRef;" +}, +{ + "name":"[Lorg.apache.logging.log4j.core.config.LoggerConfig;" +}, +{ + "name":"[Lorg.apache.logging.log4j.core.config.Property;" +}, +{ + "name":"[Lorg.apache.logging.log4j.layout.template.json.JsonTemplateLayout$EventTemplateAdditionalField;" +}, +{ + "name":"com.amazonaws.services.lambda.runtime.Context" +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.SQSEvent$MessageAttribute", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getBinaryListValues","parameterTypes":[] }, {"name":"getBinaryValue","parameterTypes":[] }, {"name":"getDataType","parameterTypes":[] }, {"name":"getStringListValues","parameterTypes":[] }, {"name":"getStringValue","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getAttributes","parameterTypes":[] }, {"name":"getAwsRegion","parameterTypes":[] }, {"name":"getBody","parameterTypes":[] }, {"name":"getEventSource","parameterTypes":[] }, {"name":"getEventSourceArn","parameterTypes":[] }, {"name":"getMd5OfBody","parameterTypes":[] }, {"name":"getMd5OfMessageAttributes","parameterTypes":[] }, {"name":"getMessageAttributes","parameterTypes":[] }, {"name":"getMessageId","parameterTypes":[] }, {"name":"getReceiptHandle","parameterTypes":[] }] +}, +{ + "name":"com.fasterxml.jackson.core.JsonParser" +}, +{ + "name":"com.fasterxml.jackson.databind.JsonNode" +}, +{ + "name":"com.fasterxml.jackson.databind.ObjectMapper" +}, +{ + "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.fasterxml.jackson.dataformat.yaml.YAMLFactory" +}, +{ + "name":"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"jakarta.servlet.Servlet" +}, +{ + "name":"java.io.Serializable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Cloneable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Iterable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Object", + "allDeclaredFields":true +}, +{ + "name":"java.lang.ProcessEnvironment", + "fields":[{"name":"theCaseInsensitiveEnvironment"}, {"name":"theEnvironment"}] +}, +{ + "name":"java.lang.String" +}, +{ + "name":"java.lang.Thread", + "fields":[{"name":"threadLocalRandomProbe"}] +}, +{ + "name":"java.sql.Date" +}, +{ + "name":"java.sql.Time" +}, +{ + "name":"java.util.AbstractCollection", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.AbstractList", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.AbstractMap", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.Arrays$ArrayList", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.Collection", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.Collections$SingletonMap", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.Collections$UnmodifiableMap", + "fields":[{"name":"m"}] +}, +{ + "name":"java.util.List", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.Map", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.RandomAccess", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.Striped64", + "fields":[{"name":"base"}, {"name":"cellsBusy"}] +}, +{ + "name":"java.util.function.Consumer", + "queryAllPublicMethods":true +}, +{ + "name":"javax.servlet.Servlet" +}, +{ + "name":"kotlin.Metadata" +}, +{ + "name":"org.apache.logging.log4j.core.appender.AbstractAppender$Builder", + "allDeclaredFields":true +}, +{ + "name":"org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender$Builder", + "allDeclaredFields":true +}, +{ + "name":"org.apache.logging.log4j.core.appender.AppenderSet" +}, +{ + "name":"org.apache.logging.log4j.core.appender.AsyncAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.ConsoleAppender", + "queryAllDeclaredMethods":true, + "methods":[{"name":"newBuilder","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.appender.ConsoleAppender$Builder", + "allDeclaredFields":true +}, +{ + "name":"org.apache.logging.log4j.core.appender.CountingNoOpAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.FailoverAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.FailoversPlugin" +}, +{ + "name":"org.apache.logging.log4j.core.appender.FileAppender", + "queryAllDeclaredMethods":true, + "methods":[{"name":"newBuilder","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.appender.FileAppender$Builder", + "allDeclaredFields":true +}, +{ + "name":"org.apache.logging.log4j.core.appender.HttpAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.MemoryMappedFileAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.NullAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.OutputStreamAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.RandomAccessFileAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.RollingFileAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.ScriptAppenderSelector" +}, +{ + "name":"org.apache.logging.log4j.core.appender.SmtpAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.SocketAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.SyslogAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.WriterAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.db.ColumnMapping" +}, +{ + "name":"org.apache.logging.log4j.core.appender.db.jdbc.ColumnConfig" +}, +{ + "name":"org.apache.logging.log4j.core.appender.db.jdbc.DataSourceConnectionSource" +}, +{ + "name":"org.apache.logging.log4j.core.appender.db.jdbc.DriverManagerConnectionSource" +}, +{ + "name":"org.apache.logging.log4j.core.appender.db.jdbc.FactoryMethodConnectionSource" +}, +{ + "name":"org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.mom.JmsAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.mom.jeromq.JeroMqAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.mom.kafka.KafkaAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.nosql.NoSqlAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rewrite.LoggerNameLevelRewritePolicy" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rewrite.MapRewritePolicy" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rewrite.PropertiesRewritePolicy" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rewrite.RewriteAppender" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.CronTriggeringPolicy" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.DirectWriteRolloverStrategy" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.NoOpTriggeringPolicy" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.OnStartupTriggeringPolicy" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.action.DeleteAction" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.action.IfAccumulatedFileCount" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.action.IfAccumulatedFileSize" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.action.IfAll" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.action.IfAny" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.action.IfFileName" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.action.IfLastModified" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.action.IfNot" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.action.PathSortByModificationTime" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction" +}, +{ + "name":"org.apache.logging.log4j.core.appender.rolling.action.ScriptCondition" +}, +{ + "name":"org.apache.logging.log4j.core.appender.routing.IdlePurgePolicy" +}, +{ + "name":"org.apache.logging.log4j.core.appender.routing.Route" +}, +{ + "name":"org.apache.logging.log4j.core.appender.routing.Routes" +}, +{ + "name":"org.apache.logging.log4j.core.appender.routing.RoutingAppender" +}, +{ + "name":"org.apache.logging.log4j.core.async.ArrayBlockingQueueFactory" +}, +{ + "name":"org.apache.logging.log4j.core.async.AsyncLoggerConfig" +}, +{ + "name":"org.apache.logging.log4j.core.async.AsyncLoggerConfig$RootLogger" +}, +{ + "name":"org.apache.logging.log4j.core.async.AsyncWaitStrategyFactoryConfig" +}, +{ + "name":"org.apache.logging.log4j.core.async.DisruptorBlockingQueueFactory" +}, +{ + "name":"org.apache.logging.log4j.core.async.JCToolsBlockingQueueFactory" +}, +{ + "name":"org.apache.logging.log4j.core.async.LinkedTransferQueueFactory" +}, +{ + "name":"org.apache.logging.log4j.core.config.AppenderControlArraySet", + "fields":[{"name":"appenderArray"}] +}, +{ + "name":"org.apache.logging.log4j.core.config.AppenderRef", + "queryAllDeclaredMethods":true, + "methods":[{"name":"createAppenderRef","parameterTypes":["java.lang.String","org.apache.logging.log4j.Level","org.apache.logging.log4j.core.Filter"] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.AppendersPlugin", + "queryAllDeclaredMethods":true, + "methods":[{"name":"createAppenders","parameterTypes":["org.apache.logging.log4j.core.Appender[]"] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.CustomLevelConfig" +}, +{ + "name":"org.apache.logging.log4j.core.config.CustomLevels" +}, +{ + "name":"org.apache.logging.log4j.core.config.DefaultAdvertiser" +}, +{ + "name":"org.apache.logging.log4j.core.config.HttpWatcher" +}, +{ + "name":"org.apache.logging.log4j.core.config.LoggerConfig", + "queryAllDeclaredMethods":true, + "methods":[{"name":"newBuilder","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.LoggerConfig$Builder", + "allDeclaredFields":true +}, +{ + "name":"org.apache.logging.log4j.core.config.LoggerConfig$RootLogger", + "queryAllDeclaredMethods":true, + "methods":[{"name":"newRootBuilder","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.LoggerConfig$RootLogger$Builder", + "allDeclaredFields":true +}, +{ + "name":"org.apache.logging.log4j.core.config.LoggersPlugin", + "queryAllDeclaredMethods":true, + "methods":[{"name":"createLoggers","parameterTypes":["org.apache.logging.log4j.core.config.LoggerConfig[]"] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.MonitorResource" +}, +{ + "name":"org.apache.logging.log4j.core.config.MonitorResources" +}, +{ + "name":"org.apache.logging.log4j.core.config.PropertiesPlugin" +}, +{ + "name":"org.apache.logging.log4j.core.config.Property" +}, +{ + "name":"org.apache.logging.log4j.core.config.ScriptsPlugin" +}, +{ + "name":"org.apache.logging.log4j.core.config.arbiters.ClassArbiter" +}, +{ + "name":"org.apache.logging.log4j.core.config.arbiters.DefaultArbiter" +}, +{ + "name":"org.apache.logging.log4j.core.config.arbiters.EnvironmentArbiter" +}, +{ + "name":"org.apache.logging.log4j.core.config.arbiters.ScriptArbiter" +}, +{ + "name":"org.apache.logging.log4j.core.config.arbiters.SelectArbiter" +}, +{ + "name":"org.apache.logging.log4j.core.config.arbiters.SystemPropertyArbiter" +}, +{ + "name":"org.apache.logging.log4j.core.config.json.JsonConfigurationFactory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$BigDecimalConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$BigIntegerConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$BooleanConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$ByteArrayConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$ByteConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$CharArrayConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$CharacterConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$CharsetConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$ClassConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$CronExpressionConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$DoubleConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$DurationConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$FileConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$FloatConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$InetAddressConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$IntegerConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$LevelConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$LongConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$PathConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$PatternConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$SecurityProviderConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$ShortConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$StringConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$UriConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$UrlConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$UuidConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.validation.validators.RequiredValidator", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.visitors.PluginAttributeVisitor", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.visitors.PluginBuilderAttributeVisitor", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.visitors.PluginConfigurationVisitor", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.plugins.visitors.PluginElementVisitor", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.config.yaml.YamlConfigurationFactory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.filter.AbstractFilterable$Builder", + "allDeclaredFields":true +}, +{ + "name":"org.apache.logging.log4j.core.filter.BurstFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.CompositeFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.DenyAllFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.DynamicThresholdFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.LevelMatchFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.LevelRangeFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.MapFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.MarkerFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.MutableThreadContextMapFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.NoMarkerFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.RegexFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.ScriptFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.StringMatchFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.StructuredDataFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.ThreadContextMapFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.ThresholdFilter" +}, +{ + "name":"org.apache.logging.log4j.core.filter.TimeFilter" +}, +{ + "name":"org.apache.logging.log4j.core.layout.CsvLogEventLayout" +}, +{ + "name":"org.apache.logging.log4j.core.layout.CsvParameterLayout" +}, +{ + "name":"org.apache.logging.log4j.core.layout.GelfLayout" +}, +{ + "name":"org.apache.logging.log4j.core.layout.HtmlLayout" +}, +{ + "name":"org.apache.logging.log4j.core.layout.JsonLayout" +}, +{ + "name":"org.apache.logging.log4j.core.layout.LevelPatternSelector" +}, +{ + "name":"org.apache.logging.log4j.core.layout.LoggerFields" +}, +{ + "name":"org.apache.logging.log4j.core.layout.MarkerPatternSelector" +}, +{ + "name":"org.apache.logging.log4j.core.layout.MessageLayout" +}, +{ + "name":"org.apache.logging.log4j.core.layout.PatternLayout", + "queryAllDeclaredMethods":true, + "methods":[{"name":"newBuilder","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.layout.PatternLayout$Builder", + "allDeclaredFields":true +}, +{ + "name":"org.apache.logging.log4j.core.layout.PatternMatch" +}, +{ + "name":"org.apache.logging.log4j.core.layout.Rfc5424Layout" +}, +{ + "name":"org.apache.logging.log4j.core.layout.ScriptPatternSelector" +}, +{ + "name":"org.apache.logging.log4j.core.layout.SerializedLayout" +}, +{ + "name":"org.apache.logging.log4j.core.layout.SyslogLayout" +}, +{ + "name":"org.apache.logging.log4j.core.layout.XmlLayout" +}, +{ + "name":"org.apache.logging.log4j.core.layout.YamlLayout" +}, +{ + "name":"org.apache.logging.log4j.core.lookup.ContextMapLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.DateLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.EnvironmentLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.EventLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.JavaLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.JmxRuntimeInputArgumentsLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.JndiLookup" +}, +{ + "name":"org.apache.logging.log4j.core.lookup.Log4jLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.LowerLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.MainMapLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.MapLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.MarkerLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.ResourceBundleLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.StructuredDataLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.SystemPropertiesLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.lookup.UpperLookup", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.core.net.MulticastDnsAdvertiser" +}, +{ + "name":"org.apache.logging.log4j.core.net.SocketAddress" +}, +{ + "name":"org.apache.logging.log4j.core.net.SocketOptions" +}, +{ + "name":"org.apache.logging.log4j.core.net.SocketPerformancePreferences" +}, +{ + "name":"org.apache.logging.log4j.core.net.ssl.KeyStoreConfiguration" +}, +{ + "name":"org.apache.logging.log4j.core.net.ssl.SslConfiguration" +}, +{ + "name":"org.apache.logging.log4j.core.net.ssl.TrustStoreConfiguration" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Black" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Blue" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Cyan" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Green" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Magenta" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Red" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$White" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Yellow" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.ClassNamePatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.DatePatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.EncodingPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.EndOfBatchPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.EqualsIgnoreCaseReplacementConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.EqualsReplacementConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.ExtendedThrowablePatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.FileDatePatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.FileLocationPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.FullLocationPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.HighlightConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.IntegerPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.LevelPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.LineLocationPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.LineSeparatorPatternConverter", + "queryAllDeclaredMethods":true, + "methods":[{"name":"newInstance","parameterTypes":["java.lang.String[]"] }] +}, +{ + "name":"org.apache.logging.log4j.core.pattern.LoggerFqcnPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.LoggerPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.MapPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.MarkerPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.MarkerSimpleNamePatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.MaxLengthConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.MdcPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.MessagePatternConverter", + "queryAllDeclaredMethods":true, + "methods":[{"name":"newInstance","parameterTypes":["org.apache.logging.log4j.core.config.Configuration","java.lang.String[]"] }] +}, +{ + "name":"org.apache.logging.log4j.core.pattern.MethodLocationPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.NanoTimePatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.NdcPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.ProcessIdPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.RegexReplacement" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.RegexReplacementConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.RelativeTimePatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.RepeatPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.RootThrowablePatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.SequenceNumberPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.StyleConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.ThreadIdPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.ThreadNamePatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.ThreadPriorityPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.ThrowablePatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.UuidPatternConverter" +}, +{ + "name":"org.apache.logging.log4j.core.pattern.VariablesNotEmptyReplacementConverter" +}, +{ + "name":"org.apache.logging.log4j.core.script.Script" +}, +{ + "name":"org.apache.logging.log4j.core.script.ScriptFile" +}, +{ + "name":"org.apache.logging.log4j.core.script.ScriptRef" +}, +{ + "name":"org.apache.logging.log4j.core.util.KeyValuePair" +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.JsonTemplateLayout", + "queryAllDeclaredMethods":true, + "methods":[{"name":"newBuilder","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.JsonTemplateLayout$Builder", + "allDeclaredFields":true +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.JsonTemplateLayout$EventTemplateAdditionalField" +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.CaseConverterResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.CounterResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.EndOfBatchResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.EventAdditionalFieldInterceptor", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.EventRootObjectKeyInterceptor", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.ExceptionResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.ExceptionRootCauseResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.LevelResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.LoggerResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.MainMapResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.MapResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.MarkerResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.MessageParameterResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.MessageResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.PatternResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.SourceResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.ThreadContextDataResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.ThreadContextStackResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.ThreadResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.resolver.TimestampResolverFactory", + "queryAllDeclaredMethods":true, + "methods":[{"name":"getInstance","parameterTypes":[] }] +}, +{ + "name":"org.apache.logging.log4j.layout.template.json.util.RecyclerFactoryConverter", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apiguardian.api.API", + "queryAllPublicMethods":true +}, +{ + "name":"org.jctools.queues.MpmcArrayQueue" +}, +{ + "name":"org.osgi.framework.FrameworkUtil" +}, +{ + "name":"software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor", + "fields":[{"name":"isColdStart"}] +}, +{ + "name":"software.amazon.lambda.powertools.logging.log4j.BufferingAppender", + "queryAllDeclaredMethods":true, + "methods":[{"name":"createAppender","parameterTypes":["java.lang.String","org.apache.logging.log4j.core.Filter","org.apache.logging.log4j.core.Layout","org.apache.logging.log4j.core.config.AppenderRef[]","org.apache.logging.log4j.core.config.Configuration","java.lang.String","int","boolean"] }] +}, +{ + "name":"software.amazon.lambda.powertools.logging.log4j.internal.Log4jUserAgentInterceptor", + "methods":[{"name":"<init>","parameterTypes":[] }] +} +] diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-log4j/resource-config.json b/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-log4j/resource-config.json new file mode 100644 index 000000000..a4bcd55fa --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-log4j/resource-config.json @@ -0,0 +1,41 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QLambdaEcsLayout.json\\E" + }, { + "pattern":"\\QLambdaJsonLayout.json\\E" + }, { + "pattern":"\\QMETA-INF/log4j-provider.properties\\E" + }, { + "pattern":"\\QMETA-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat\\E" + }, { + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.InetAddressResolverProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/javax.xml.parsers.DocumentBuilderFactory\\E" + }, { + "pattern":"\\QMETA-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.apache.logging.log4j.core.util.WatchEventService\\E" + }, { + "pattern":"\\QMETA-INF/services/org.apache.logging.log4j.spi.Provider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.apache.logging.log4j.util.PropertySource\\E" + }, { + "pattern":"\\QMETA-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory\\E" + }, { + "pattern":"\\QMETA-INF/services/org.assertj.core.configuration.Configuration\\E" + }, { + "pattern":"\\QMETA-INF/services/org.assertj.core.presentation.Representation\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager\\E" + }, { + "pattern":"\\QStackTraceElementLayout.json\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/global/handlers/execution.interceptors\\E" + }]}, + "bundles":[] +} diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager b/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager new file mode 100644 index 000000000..d4b2a72a0 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager @@ -0,0 +1 @@ +software.amazon.lambda.powertools.logging.log4j.internal.Log4jLoggingManager diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-logging/powertools-logging-log4j/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..22150f4b7 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.logging.log4j.internal.Log4jUserAgentInterceptor diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java new file mode 100644 index 000000000..46b5b65d4 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.apache.logging.log4j.layout.template.json.resolver; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; + +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.slf4j.MDC; + +import com.amazonaws.services.lambda.runtime.Context; + +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; +import software.amazon.lambda.powertools.logging.PowertoolsLogging; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; + +@Order(1) +class PowerToolsResolverFactoryTest { + + private Context context; + + @BeforeEach + void setUp() throws IllegalAccessException, IOException { + MDC.clear(); + // Reset cold start state + writeStaticField(LambdaHandlerProcessor.class, "isColdStart", null, true); + writeStaticField(PowertoolsLogging.class, "hasBeenInitialized", new AtomicBoolean(false), true); + + context = new TestLambdaContext(); + // Make sure file is cleaned up before running tests + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // file may not exist on the first launch + } + } + + @AfterEach + void cleanUp() throws IOException { + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // file may not exist on the first launch + } + } + + @Test + void shouldLogInJsonFormat() { + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains( + "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"function_memory_size\":128,\"function_name\":\"test-function\",\"function_request_id\":\"test-request-id\",\"function_version\":\"1\",\"service\":\"testLog4j\",\"timestamp\":") + .contains("\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); + } + + @Test + void shouldLogInEcsFormat() { + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/ecslogfile.json"); + assertThat(contentOf(logFile)).contains( + "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLog4j\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-central-1\",\"cloud.account.id\":\"123456789012\",\"faas.id\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"faas.name\":\"test-function\",\"faas.version\":\"1\",\"faas.memory\":128,\"faas.execution\":\"test-request-id\",\"faas.coldstart\":true,\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); + } + +} diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverArgumentsTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverArgumentsTest.java new file mode 100644 index 000000000..573eaddbf --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverArgumentsTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.apache.logging.log4j.layout.template.json.resolver; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; + +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.slf4j.MDC; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; + +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsArguments; + +@Order(2) +class PowertoolsResolverArgumentsTest { + + private Context context; + + @BeforeEach + void setUp() throws IOException { + MDC.clear(); + context = new TestLambdaContext(); + + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + } + + @AfterEach + void cleanUp() throws IOException { + try { + // Make sure file is cleaned up before running full stack logging regression + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + } + + @Test + void shouldLogArgumentsAsJsonWhenUsingRawJson() { + // GIVEN + PowertoolsArguments requestHandler = new PowertoolsArguments(PowertoolsArguments.ArgumentFormat.JSON); + SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage(); + msg.setMessageId("1212abcd"); + msg.setBody("plop"); + msg.setEventSource("eb"); + msg.setAwsRegion("us-east-1"); + SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute(); + attribute.setStringListValues(Arrays.asList("val1", "val2", "val3")); + msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute)); + + // WHEN + requestHandler.handleRequest(msg, context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains( + "\"input\":{\"awsRegion\":\"us-east-1\",\"body\":\"plop\",\"eventSource\":\"eb\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}},\"messageId\":\"1212abcd\"}") + .contains("\"message\":\"1212abcd\"") + .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\"") + .contains("\"correlation_id\":\"1212abcd\""); + // Reserved keys should be ignored + PowertoolsLoggedFields.stringValues().stream().forEach(reservedKey -> { + assertThat(contentOf(logFile)).doesNotContain("\"" + reservedKey + "\":\"shouldBeIgnored\""); + assertThat(contentOf(logFile)).contains( + "\"message\":\"Attempted to use reserved key '" + reservedKey + + "' in structured argument. This key will be ignored.\""); + }); + } + + @Test + void shouldLogArgumentsAsJsonWhenUsingKeyValue() { + // GIVEN + PowertoolsArguments requestHandler = new PowertoolsArguments(PowertoolsArguments.ArgumentFormat.ENTRY); + SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage(); + msg.setMessageId("1212abcd"); + msg.setBody("plop"); + msg.setEventSource("eb"); + msg.setAwsRegion("us-east-1"); + SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute(); + attribute.setStringListValues(Arrays.asList("val1", "val2", "val3")); + msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute)); + + // WHEN + requestHandler.handleRequest(msg, context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains( + "\"input\":{\"awsRegion\":\"us-east-1\",\"body\":\"plop\",\"eventSource\":\"eb\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}},\"messageId\":\"1212abcd\"}") + .contains("\"message\":\"1212abcd\"") + .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\"") + .contains("\"correlation_id\":\"1212abcd\""); + + // Reserved keys should be ignored + PowertoolsLoggedFields.stringValues().stream().forEach(reservedKey -> { + assertThat(contentOf(logFile)).doesNotContain("\"" + reservedKey + "\":\"shouldBeIgnored\""); + assertThat(contentOf(logFile)).contains( + "\"message\":\"Attempted to use reserved key '" + reservedKey + + "' in structured argument. This key will be ignored.\""); + }); + } + +} diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java new file mode 100644 index 000000000..d1b9fec83 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java @@ -0,0 +1,111 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package org.apache.logging.log4j.layout.template.json.resolver; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_ARN; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SAMPLING_RATE; + +import java.util.HashMap; +import java.util.Map; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.layout.template.json.util.JsonWriter; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.apache.logging.log4j.util.StringMap; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junitpioneer.jupiter.SetEnvironmentVariable; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; + +class PowertoolsResolverTest { + + @ParameterizedTest + @EnumSource(value = PowertoolsLoggedFields.class, + mode = EnumSource.Mode.EXCLUDE, + names = {"FUNCTION_MEMORY_SIZE", "SAMPLING_RATE", "FUNCTION_COLD_START", "CORRELATION_ID"}) + void shouldResolveFunctionStringInfo(PowertoolsLoggedFields field) { + String result = resolveField(field.getName(), "value"); + assertThat(result).isEqualTo("\"value\""); + } + + @Test + void shouldResolveMemorySize() { + String result = resolveField(FUNCTION_MEMORY_SIZE.getName(), "42"); + assertThat(result).isEqualTo("42"); + } + + @Test + void shouldResolveSamplingRate() { + String result = resolveField(SAMPLING_RATE.getName(), "0.4"); + assertThat(result).isEqualTo("0.4"); + } + + @Test + void shouldResolveColdStart() { + String result = resolveField(FUNCTION_COLD_START.getName(), "true"); + assertThat(result).isEqualTo("true"); + } + + @Test + void shouldResolveAccountId() { + String result = resolveField(FUNCTION_ARN.getName(), "account_id", "arn:aws:lambda:us-east-2:123456789012:function:my-function"); + assertThat(result).isEqualTo("\"123456789012\""); + } + + @Test + void unknownField_shouldThrowException() { + assertThatThrownBy(() -> resolveField("custom-random-unknown-field", "custom-random-unknown-field", "Once apon a time in Switzerland...")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unknown field: custom-random-unknown-field"); + } + + @Test + @SetEnvironmentVariable(key = "AWS_REGION", value = "eu-central-2") + void shouldResolveRegion() { + String result = resolveField("region", "dummy, will use the env var"); + assertThat(result).isEqualTo("\"eu-central-2\""); + } + + private static String resolveField(String field, String value) { + return resolveField(field, field, value); + } + + private static String resolveField(String data, String field, String value) { + Map<String, Object> configMap = new HashMap<>(); + configMap.put("field", field); + + TemplateResolverConfig config = new TemplateResolverConfig(configMap); + PowertoolsResolver resolver = new PowertoolsResolver(config); + JsonWriter writer = JsonWriter + .newBuilder() + .setMaxStringLength(1000) + .setTruncatedStringSuffix("") + .build(); + + StringMap contextMap = new SortedArrayStringMap(); + contextMap.putValue(data, value); + + Log4jLogEvent logEvent = Log4jLogEvent.newBuilder().setContextData(contextMap).build(); + if (resolver.isResolvable(logEvent)) { + resolver.resolve(logEvent, writer); + } + + return writer.getStringBuilder().toString(); + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsArguments.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsArguments.java new file mode 100644 index 000000000..0d95f29fa --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsArguments.java @@ -0,0 +1,82 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal.handler; + +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.CORRELATION_ID; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.fasterxml.jackson.core.JsonProcessingException; + +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.argument.StructuredArguments; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +public class PowertoolsArguments implements RequestHandler<SQSEvent.SQSMessage, String> { + private static final Logger LOG = LoggerFactory.getLogger(PowertoolsArguments.class); + private final ArgumentFormat argumentFormat; + + public PowertoolsArguments(ArgumentFormat argumentFormat) { + this.argumentFormat = argumentFormat; + } + + @Override + @Logging(clearState = true) + public String handleRequest(SQSEvent.SQSMessage input, Context context) { + try { + MDC.put(CORRELATION_ID.getName(), input.getMessageId()); + if (argumentFormat == ArgumentFormat.JSON) { + LOG.debug("SQS Event", + StructuredArguments.json("input", + JsonConfig.get().getObjectMapper().writeValueAsString(input)), + // function_name is a reserved key by PowertoolsLoggedFields and should be omitted + StructuredArguments.entry("function_name", "shouldBeIgnored")); + } else { + LOG.debug("SQS Event", + StructuredArguments.entry("input", input), + // function_name is a reserved key by PowertoolsLoggedFields and should be omitted + StructuredArguments.entry("function_name", "shouldBeIgnored")); + } + + // Attempt logging all reserved keys, the values should not be overwritten by "shouldBeIgnored" + final Map<String, String> reservedKeysMap = new HashMap<>(); + for (String field : PowertoolsLoggedFields.stringValues()) { + reservedKeysMap.put(field, "shouldBeIgnored"); + } + reservedKeysMap.put("message", "shouldBeIgnored"); + reservedKeysMap.put("level", "shouldBeIgnored"); + reservedKeysMap.put("timestamp", "shouldBeIgnored"); + LOG.debug("Reserved keys", StructuredArguments.entries(reservedKeysMap)); + LOG.debug("{}", input.getMessageId()); + LOG.warn("Message body = {} and id = \"{}\"", input.getBody(), input.getMessageId()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return input.getMessageId(); + } + + public enum ArgumentFormat { + JSON, ENTRY + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java new file mode 100644 index 000000000..0ee7f14fa --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal.handler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogEnabled implements RequestHandler<Object, Object> { + private static final Logger LOG = LoggerFactory.getLogger(PowertoolsLogEnabled.class); + + @Override + @Logging(clearState = true) + public Object handleRequest(Object input, Context context) { + MDC.put("myKey", "myValue"); + LOG.debug("Test debug event"); + return "Bonjour le monde"; + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/log4j/BufferingAppenderTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/log4j/BufferingAppenderTest.java new file mode 100644 index 000000000..a0fac8d7a --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/log4j/BufferingAppenderTest.java @@ -0,0 +1,145 @@ +package software.amazon.lambda.powertools.logging.log4j; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; + +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ClearEnvironmentVariable; + +class BufferingAppenderTest { + + private Logger logger; + + @BeforeEach + void setUp() throws IOException { + logger = LogManager.getLogger(BufferingAppenderTest.class); + + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + } + + @AfterEach + void cleanUp() throws IOException { + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + } + + @Test + void shouldBufferDebugLogsAndFlushOnError() { + // When - log debug messages (should be buffered) + logger.debug("Debug message 1"); + logger.debug("Debug message 2"); + + // Then - no logs written yet + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).isEmpty(); + + // When - log error (should flush buffer) + logger.error("Error message"); + + // Then - all logs written + assertThat(contentOf(logFile)) + .contains("Debug message 1") + .contains("Debug message 2") + .contains("Error message"); + } + + @Test + @ClearEnvironmentVariable(key = "_X_AMZN_TRACE_ID") + void shouldLogDirectlyWhenNoTraceId() { + // When + logger.debug("Debug without trace"); + + // Then - log written directly + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("Debug without trace"); + } + + @Test + void shouldNotBufferInfoLogs() { + // When - log info message (above buffer level) + logger.info("Info message"); + + // Then - log written directly + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("Info message"); + } + + @Test + void shouldFlushBufferManually() { + // When - buffer debug logs + logger.debug("Buffered message"); + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).isEmpty(); + + // When - manual flush + BufferingAppender appender = getBufferingAppender(); + appender.flushBuffer(); + + // Then - logs written + assertThat(contentOf(logFile)).contains("Buffered message"); + } + + @Test + void shouldClearBufferManually() { + // When - buffer debug logs then clear + logger.debug("Buffered message"); + BufferingAppender appender = getBufferingAppender(); + appender.clearBuffer(); + + // When - log error (should not flush cleared buffer) + logger.error("Error after clear"); + + // Then - only error logged + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("Error after clear") + .doesNotContain("Buffered message"); + } + + @Test + void shouldLogOverflowWarningWhenBufferOverflows() { + // When - fill buffer beyond capacity to trigger overflow + for (int i = 0; i < 100; i++) { + logger.debug("Debug message {}", i); + } + + // When - flush buffer to trigger overflow warning + BufferingAppender appender = getBufferingAppender(); + appender.flushBuffer(); + + // Then - overflow warning should be logged + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("Some logs are not displayed because they were evicted from the buffer"); + } + + private BufferingAppender getBufferingAppender() { + LoggerContext context = (LoggerContext) LogManager.getContext(false); + Appender appender = context.getConfiguration().getAppender("BufferingAppender"); + if (appender == null) { + throw new IllegalStateException("BufferingAppender not found in configuration. Available appenders: " + + context.getConfiguration().getAppenders().keySet()); + } + return (BufferingAppender) appender; + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/log4j/internal/Log4jLoggingManagerTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/log4j/internal/Log4jLoggingManagerTest.java new file mode 100644 index 000000000..16036fe3e --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/log4j/internal/Log4jLoggingManagerTest.java @@ -0,0 +1,118 @@ +package software.amazon.lambda.powertools.logging.log4j.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; +import static org.slf4j.event.Level.DEBUG; +import static org.slf4j.event.Level.ERROR; +import static org.slf4j.event.Level.WARN; + +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; + +class Log4jLoggingManagerTest { + + private static final Logger LOG = LoggerFactory.getLogger(Log4jLoggingManagerTest.class); + private static final Logger ROOT = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + + @BeforeEach + void setUp() { + // Force reconfiguration from XML to ensure clean state + Configurator.reconfigure(); + } + + @Test + void getLogLevel_shouldReturnConfiguredLogLevel() { + // Given log4j2.xml in resources + + // When + Log4jLoggingManager manager = new Log4jLoggingManager(); + Level logLevel = manager.getLogLevel(LOG); + Level rootLevel = manager.getLogLevel(ROOT); + + // Then + assertThat(logLevel).isEqualTo(DEBUG); + assertThat(rootLevel).isEqualTo(WARN); + } + + @Test + void resetLogLevel() { + // Given log4j2.xml in resources + + // When + Log4jLoggingManager manager = new Log4jLoggingManager(); + manager.setLogLevel(ERROR); + + Level rootLevel = manager.getLogLevel(ROOT); + Level logLevel = manager.getLogLevel(LOG); + + // Then + assertThat(rootLevel).isEqualTo(ERROR); + assertThat(logLevel).isEqualTo(ERROR); + } + + @Test + void shouldDetectMultipleBufferingAppendersRegardlessOfName() throws IOException { + // Given - configuration with multiple BufferingAppenders with different names + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + ConfigurationFactory factory = new XmlConfigurationFactory(); + ConfigurationSource source = new ConfigurationSource( + getClass().getResourceAsStream("/log4j2-multiple-buffering.xml")); + Configuration config = factory.getConfiguration(null, source); + + ctx.setConfiguration(config); + ctx.updateLoggers(); + + org.apache.logging.log4j.Logger logger = LogManager.getLogger("test.multiple.appenders"); + + // When - log messages and flush buffers + logger.debug("Test message 1"); + logger.debug("Test message 2"); + + Log4jLoggingManager manager = new Log4jLoggingManager(); + manager.flushBuffer(); + + // Then - both appenders should have flushed their buffers + File logFile = new File("target/logfile.json"); + assertThat(logFile).exists(); + String content = contentOf(logFile); + // Each message should appear twice (once from each BufferingAppender) + assertThat(content.split("Test message 1", -1)).hasSize(3); // 2 occurrences = 3 parts + assertThat(content.split("Test message 2", -1)).hasSize(3); // 2 occurrences = 3 parts + } + + @AfterEach + void cleanUp() throws IOException { + // Reset to original configuration from XML + Configurator.reconfigure(); + + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there + } + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/log4j/internal/Log4jUserAgentInterceptorTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/log4j/internal/Log4jUserAgentInterceptorTest.java new file mode 100644 index 000000000..77d5b06e3 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/log4j/internal/Log4jUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.logging.log4j.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class Log4jUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/LOGGING-LOG4J/"); + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/test/resources/junit-platform.properties b/powertools-logging/powertools-logging-log4j/src/test/resources/junit-platform.properties new file mode 100644 index 000000000..80a2481d7 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/resources/junit-platform.properties @@ -0,0 +1,17 @@ +# +# Copyright 2023 Amazon.com, Inc. or its affiliates. +# 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 +# http://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. +# +# + +# because of LambdaLoggingAspect static initialization of the LoggingManager, we need to +# set an order in the unit tests, especially LambdaLoggingAspectTest needs to be first +junit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer$OrderAnnotation \ No newline at end of file diff --git a/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2-multiple-buffering.xml b/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2-multiple-buffering.xml new file mode 100644 index 000000000..53995e4f5 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2-multiple-buffering.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <File name="testFile" fileName="target/logfile.json"> + <PatternLayout pattern="%msg%n" /> + </File> + <BufferingAppender name="FirstBufferingAppender" + bufferAtVerbosity="DEBUG" + maxBytes="1024" + flushOnErrorLog="true"> + <AppenderRef ref="testFile" /> + </BufferingAppender> + <BufferingAppender name="SecondBufferingAppender" + bufferAtVerbosity="DEBUG" + maxBytes="1024" + flushOnErrorLog="true"> + <AppenderRef ref="testFile" /> + </BufferingAppender> + </Appenders> + <Loggers> + <Logger name="test.multiple.appenders" level="DEBUG" additivity="false"> + <AppenderRef ref="FirstBufferingAppender" /> + <AppenderRef ref="SecondBufferingAppender" /> + </Logger> + </Loggers> +</Configuration> diff --git a/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml b/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml new file mode 100644 index 000000000..870e3a803 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="console" target="SYSTEM_OUT"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </Console> + <File name="logFile" fileName="target/logfile.json"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> + </File> + <File name="logFileWithEcs" fileName="target/ecslogfile.json"> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaEcsLayout.json" /> + </File> + <BufferingAppender name="BufferingAppender" + bufferAtVerbosity="DEBUG" + maxBytes="1024" + flushOnErrorLog="true"> + <AppenderRef ref="logFile"/> + </BufferingAppender> + </Appenders> + <Loggers> + <Logger name="software.amazon.lambda.powertools.logging.log4j.BufferingAppenderTest" level="DEBUG" additivity="false"> + <AppenderRef ref="BufferingAppender"/> + </Logger> + <Logger name="software.amazon.lambda.powertools" level="DEBUG" additivity="false"> + <AppenderRef ref="logFileWithEcs"/> + <AppenderRef ref="logFile"/> + </Logger> + <Root level="WARN"> + <AppenderRef ref="logFileWithEcs"/> + <AppenderRef ref="logFile"/> + </Root> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/powertools-logging/powertools-logging-logback/pom.xml b/powertools-logging/powertools-logging-logback/pom.xml new file mode 100644 index 000000000..dbf7f5207 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/pom.xml @@ -0,0 +1,216 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>powertools-parent</artifactId> + <groupId>software.amazon.lambda</groupId> + <version>2.9.0</version> + <relativePath>../../pom.xml</relativePath> + </parent> + + <artifactId>powertools-logging-logback</artifactId> + <name>Powertools for AWS Lambda (Java) - Logging with LogBack</name> + <description>Set of utility for better logging with logback</description> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <version>1.5.18</version> + <exclusions> + <exclusion> + <groupId>com.sun.mail</groupId> + <artifactId>javax.mail</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjweaver</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-tests</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.skyscreamer</groupId> + <artifactId>jsonassert</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit-pioneer</groupId> + <artifactId>junit-pioneer</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>generate-graalvm-files</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-logback,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-logging-logback</imageName> + <buildArgs> + <buildArg> + --initialize-at-build-time=org.junit.platform.launcher.core.DiscoveryIssueNotifier$1 + </buildArg> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + + <build> + <resources> + <!-- GraalVM Native Image Configuration Files --> + <resource> + <directory>src/main/resources</directory> + </resource> + </resources> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>1.14.1</version> + <configuration> + <source>${maven.compiler.source}</source> + <target>${maven.compiler.target}</target> + <complianceLevel>${maven.compiler.target}</complianceLevel> + <aspectLibraries> + <aspectLibrary> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-logging</artifactId> + </aspectLibrary> + </aspectLibraries> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjtools</artifactId> + <version>${aspectj.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <POWERTOOLS_SERVICE_NAME>testLogback</POWERTOOLS_SERVICE_NAME> + <AWS_REGION>eu-central-1</AWS_REGION> + <_X_AMZN_TRACE_ID>Root=1-63441c4a-abcdef012345678912345678</_X_AMZN_TRACE_ID> + <AWS_LAMBDA_INITIALIZATION_TYPE>on-demand</AWS_LAMBDA_INITIALIZATION_TYPE> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/BufferingAppender.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/BufferingAppender.java new file mode 100644 index 000000000..8f323ff46 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/BufferingAppender.java @@ -0,0 +1,196 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.logback; + +import java.util.Deque; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.LoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.AppenderBase; +import ch.qos.logback.core.spi.AppenderAttachable; +import ch.qos.logback.core.spi.AppenderAttachableImpl; +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.logging.internal.BufferManager; +import software.amazon.lambda.powertools.logging.internal.KeyBuffer; + +/** + * A Logback appender that buffers log events by AWS X-Ray trace ID for optimized Lambda logging. + * + * <p>This appender is designed specifically for AWS Lambda functions to reduce log ingestion + * by buffering lower-level logs and only outputting them when errors occur, preserving + * full context for troubleshooting while minimizing routine log volume. + * + * <h3>Key Features:</h3> + * <ul> + * <li><strong>Trace-based buffering:</strong> Groups logs by AWS X-Ray trace ID</li> + * <li><strong>Selective output:</strong> Only buffers logs at or below configured verbosity level</li> + * <li><strong>Auto-flush on errors:</strong> Automatically outputs buffered logs when ERROR/FATAL events occur</li> + * <li><strong>Memory management:</strong> Prevents memory leaks with configurable buffer size limits</li> + * <li><strong>Overflow protection:</strong> Warns when logs are discarded due to buffer limits</li> + * </ul> + * + * <h3>Configuration Example:</h3> + * <pre>{@code + * <appender name="BufferedAppender" class="software.amazon.lambda.powertools.logging.logback.BufferingAppender"> + * <bufferAtVerbosity>DEBUG</bufferAtVerbosity> + * <maxBytes>20480</maxBytes> + * <flushOnErrorLog>true</flushOnErrorLog> + * <appender-ref ref="ConsoleAppender"/> + * </appender> + * }</pre> + * + * <h3>Configuration Parameters:</h3> + * <ul> + * <li><strong>bufferAtVerbosity:</strong> Log level to buffer (default: DEBUG). Logs at this level and below are buffered</li> + * <li><strong>maxBytes:</strong> Maximum buffer size in bytes per trace ID (default: 20480)</li> + * <li><strong>flushOnErrorLog:</strong> Whether to flush buffer on ERROR/FATAL logs (default: true)</li> + * </ul> + * + * <h3>Behavior:</h3> + * <ul> + * <li>During Lambda INIT phase (no trace ID): logs are output directly</li> + * <li>During Lambda execution (with trace ID): logs are buffered or output based on level</li> + * <li>When buffer overflows: oldest logs are discarded and a warning is logged</li> + * <li>On Lambda completion: buffer is auto-cleared when used with {@code @Logging} annotation</li> + * </ul> + * + * @see software.amazon.lambda.powertools.logging.PowertoolsLogging#flushBuffer() + */ +public class BufferingAppender extends AppenderBase<ILoggingEvent> + implements AppenderAttachable<ILoggingEvent>, BufferManager { + + private static final int DEFAULT_BUFFER_SIZE = 20480; + + private final AppenderAttachableImpl<ILoggingEvent> aai = new AppenderAttachableImpl<>(); + private Level bufferAtVerbosity = Level.DEBUG; + private boolean flushOnErrorLog = true; + private int maxBytes = DEFAULT_BUFFER_SIZE; + private KeyBuffer<String, ILoggingEvent> buffer; + + @Override + public void start() { + // Initialize lazily to ensure configuration properties are set first. + if (buffer == null) { + buffer = new KeyBuffer<>(maxBytes, event -> event.getFormattedMessage().length(), this::logOverflowWarning); + } + super.start(); + } + + @Override + protected void append(ILoggingEvent event) { + LambdaHandlerProcessor.getXrayTraceId().ifPresentOrElse( + traceId -> { + if (shouldBuffer(event.getLevel())) { + buffer.add(traceId, event); + } else { + aai.appendLoopOnAppenders(event); + } + + // Flush buffer on error logs if configured + if (flushOnErrorLog && event.getLevel().isGreaterOrEqual(Level.ERROR)) { + flushBuffer(traceId); + } + }, + () -> aai.appendLoopOnAppenders(event) // No trace ID (INIT phase), log directly + ); + } + + private boolean shouldBuffer(Level level) { + return level.levelInt <= bufferAtVerbosity.levelInt; + } + + public void clearBuffer() { + LambdaHandlerProcessor.getXrayTraceId().ifPresent(buffer::clear); + } + + public void flushBuffer() { + LambdaHandlerProcessor.getXrayTraceId().ifPresent(this::flushBuffer); + } + + private void flushBuffer(String traceId) { + Deque<ILoggingEvent> events = buffer.removeAll(traceId); + if (events != null) { + events.forEach(aai::appendLoopOnAppenders); + } + } + + // Configuration setters. These will be inspected as JavaBean properties by Logback + // when configuring the appender via XML or programmatically. They run before start(). + public void setBufferAtVerbosity(String level) { + this.bufferAtVerbosity = Level.toLevel(level, Level.DEBUG); + } + + public void setMaxBytes(int maxBytes) { + this.maxBytes = maxBytes; + } + + public void setFlushOnErrorLog(boolean flushOnErrorLog) { + this.flushOnErrorLog = flushOnErrorLog; + } + + // AppenderAttachable implementation. We simply delegate to the internal logback AppenderAttachableImpl. This is + // needed to be able to attach other appenders to this appender so that customers can wrap existing appenders with + // this buffering appender. + @Override + public void addAppender(Appender<ILoggingEvent> newAppender) { + aai.addAppender(newAppender); + } + + @Override + public java.util.Iterator<Appender<ILoggingEvent>> iteratorForAppenders() { + return aai.iteratorForAppenders(); + } + + @Override + public Appender<ILoggingEvent> getAppender(String name) { + return aai.getAppender(name); + } + + @Override + public boolean isAttached(Appender<ILoggingEvent> appender) { + return aai.isAttached(appender); + } + + @Override + public void detachAndStopAllAppenders() { + aai.detachAndStopAllAppenders(); + } + + @Override + public boolean detachAppender(Appender<ILoggingEvent> appender) { + return aai.detachAppender(appender); + } + + @Override + public boolean detachAppender(String name) { + return aai.detachAppender(name); + } + + private void logOverflowWarning() { + // Create a properly formatted warning event and send directly to child appenders. Used to avoid circular + // dependency between KeyBuffer and BufferingAppender. + Logger logbackLogger = (Logger) org.slf4j.LoggerFactory + .getLogger(BufferingAppender.class); + LoggingEvent warningEvent = new LoggingEvent( + BufferingAppender.class.getName(), logbackLogger, Level.WARN, + "Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer.", + null, null); + warningEvent.setTimeStamp(System.currentTimeMillis()); + aai.appendLoopOnAppenders(warningEvent); + } +} diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/JsonUtils.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/JsonUtils.java new file mode 100644 index 000000000..67d6b268d --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/JsonUtils.java @@ -0,0 +1,140 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.logback; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.TimeZone; +import java.util.TreeMap; + +import software.amazon.lambda.powertools.logging.argument.StructuredArgument; +import software.amazon.lambda.powertools.logging.internal.JsonSerializer; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; + +/** + * Json tools to serialize common fields + */ +final class JsonUtils { + + private JsonUtils() { + // static utils + } + + static void serializeTimestamp(JsonSerializer generator, long timestamp, String timestampFormat, + String timestampFormatTimezoneId, String timestampAttributeName) { + String formattedTimestamp; + if (timestampFormat == null || timestamp < 0) { + formattedTimestamp = String.valueOf(timestamp); + } else { + Date date = new Date(timestamp); + DateFormat format = new SimpleDateFormat(timestampFormat); + + if (timestampFormatTimezoneId != null) { + TimeZone tz = TimeZone.getTimeZone(timestampFormatTimezoneId); + format.setTimeZone(tz); + } + formattedTimestamp = format.format(date); + } + generator.writeStringField(timestampAttributeName, formattedTimestamp); + } + + static void serializeMDCEntries(Map<String, String> mdcPropertyMap, JsonSerializer serializer) { + TreeMap<String, String> sortedMap = new TreeMap<>(mdcPropertyMap); + for (Map.Entry<String, String> entry : sortedMap.entrySet()) { + if (!PowertoolsLoggedFields.stringValues().contains(entry.getKey())) { + serializeMDCEntry(entry, serializer); + } + } + } + + static void serializeMDCEntry(Map.Entry<String, String> entry, JsonSerializer serializer) { + serializer.writeRaw(','); + serializer.writeFieldName(entry.getKey()); + if (isString(entry.getValue())) { + serializer.writeString(entry.getValue()); + } else { + serializer.writeRaw(entry.getValue()); + } + } + + static void serializeArguments(ILoggingEvent event, JsonSerializer serializer) throws IOException { + Object[] arguments = event.getArgumentArray(); + if (arguments != null) { + for (Object argument : arguments) { + if (argument instanceof StructuredArgument) { + serializer.writeRaw(','); + ((StructuredArgument) argument).writeTo(serializer); + } + } + } + } + + /** + * As MDC is a {@code Map<String, String>}, we need to check the type + * to output numbers and booleans correctly (without quotes) + */ + private static boolean isString(String str) { + if (str == null) { + return true; + } + if ("true".equals(str) || "false".equals(str)) { + return false; // boolean + } + return !isNumeric(str); // number + } + + /** + * Taken from commons-lang3 NumberUtils to avoid include the library + */ + private static boolean isNumeric(final String str) { + if (str == null || str.isEmpty()) { + return false; + } + if (str.charAt(str.length() - 1) == '.') { + return false; + } + if (str.charAt(0) == '-') { + if (str.length() == 1) { + return false; + } + return withDecimalsParsing(str, 1); + } + return withDecimalsParsing(str, 0); + } + + /** + * Taken from commons-lang3 NumberUtils + */ + private static boolean withDecimalsParsing(final String str, final int beginIdx) { + int decimalPoints = 0; + for (int i = beginIdx; i < str.length(); i++) { + final boolean isDecimalPoint = str.charAt(i) == '.'; + if (isDecimalPoint) { + decimalPoints++; + } + if (decimalPoints > 1) { + return false; + } + if (!isDecimalPoint && !Character.isDigit(str.charAt(i))) { + return false; + } + } + return true; + } +} diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java new file mode 100644 index 000000000..6a82d8e67 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java @@ -0,0 +1,289 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.logback; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.CORRELATION_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_ARN; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_NAME; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_REQUEST_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_TRACE_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_VERSION; +import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeArguments; +import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeMDCEntries; +import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeTimestamp; + +import ch.qos.logback.classic.pattern.ThrowableHandlingConverter; +import ch.qos.logback.classic.pattern.ThrowableProxyConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.ThrowableProxy; +import ch.qos.logback.core.encoder.EncoderBase; +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.logging.internal.JsonSerializer; + + +/** + * This class will encode the logback event into the format expected by the Elastic Common Schema (ECS) service (for Elasticsearch). + * <br/> + * Inspired from <code>co.elastic.logging.logback.EcsEncoder</code>, this class doesn't use + * any JSON (de)serialization library (Jackson, Gson, etc.) or Elastic library to avoid the dependency. + * <br/> + * This encoder also adds cloud information (see <a href="https://www.elastic.co/guide/en/ecs/current/ecs-cloud.html">doc</a>) + * and Lambda function information (see <a href="https://www.elastic.co/guide/en/ecs/current/ecs-faas.html">doc</a>, currently in beta). + */ +public class LambdaEcsEncoder extends EncoderBase<ILoggingEvent> { + + protected static final String TIMESTAMP_ATTR_NAME = "@timestamp"; + protected static final String ECS_VERSION_ATTR_NAME = "ecs.version"; + protected static final String LOGGER_ATTR_NAME = "log.logger"; + protected static final String LEVEL_ATTR_NAME = "log.level"; + protected static final String SERVICE_NAME_ATTR_NAME = "service.name"; + protected static final String SERVICE_VERSION_ATTR_NAME = "service.version"; + protected static final String FORMATTED_MESSAGE_ATTR_NAME = "message"; + protected static final String THREAD_ATTR_NAME = "process.thread.name"; + protected static final String EXCEPTION_MSG_ATTR_NAME = "error.message"; + protected static final String EXCEPTION_CLASS_ATTR_NAME = "error.type"; + protected static final String EXCEPTION_STACK_ATTR_NAME = "error.stack_trace"; + protected static final String CLOUD_PROVIDER_ATTR_NAME = "cloud.provider"; + protected static final String CLOUD_REGION_ATTR_NAME = "cloud.region"; + protected static final String CLOUD_ACCOUNT_ATTR_NAME = "cloud.account.id"; + protected static final String CLOUD_SERVICE_ATTR_NAME = "cloud.service.name"; + protected static final String FUNCTION_COLD_START_ATTR_NAME = "faas.coldstart"; + protected static final String FUNCTION_REQUEST_ID_ATTR_NAME = "faas.execution"; + protected static final String FUNCTION_ARN_ATTR_NAME = "faas.id"; + protected static final String FUNCTION_NAME_ATTR_NAME = "faas.name"; + protected static final String FUNCTION_VERSION_ATTR_NAME = "faas.version"; + protected static final String FUNCTION_MEMORY_ATTR_NAME = "faas.memory"; + protected static final String FUNCTION_TRACE_ID_ATTR_NAME = "trace.id"; + protected static final String CORRELATION_ID_ATTR_NAME = "correlation.id"; + + protected static final String ECS_VERSION = "1.2.0"; + protected static final String CLOUD_PROVIDER = "aws"; + protected static final String CLOUD_SERVICE = "lambda"; + + private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter(); + protected ThrowableHandlingConverter throwableConverter = null; + private boolean includeCloudInfo = true; + private boolean includeFaasInfo = true; + + @Override + public byte[] headerBytes() { + return new byte[0]; + } + + /** + * Main method of the encoder. Encode a logging event into Json format (with Elastic Search fields) + * + * @param event the logging event + * @return the encoded bytes + */ + + @SuppressWarnings("java:S106") + @Override + public byte[] encode(ILoggingEvent event) { + final Map<String, String> mdcPropertyMap = event.getMDCPropertyMap(); + + StringBuilder builder = new StringBuilder(); + try (JsonSerializer serializer = new JsonSerializer(builder)) { + serializer.writeStartObject(); + serializeTimestamp(serializer, event.getTimeStamp(), "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "UTC", TIMESTAMP_ATTR_NAME); + serializer.writeRaw(','); + serializer.writeStringField(ECS_VERSION_ATTR_NAME, ECS_VERSION); + serializer.writeRaw(','); + serializer.writeStringField(LEVEL_ATTR_NAME, event.getLevel().toString()); + serializer.writeRaw(','); + serializer.writeStringField(FORMATTED_MESSAGE_ATTR_NAME, event.getFormattedMessage()); + + serializeException(event, serializer); + + serializer.writeRaw(','); + serializer.writeStringField(SERVICE_NAME_ATTR_NAME, LambdaHandlerProcessor.serviceName()); + serializer.writeRaw(','); + serializer.writeStringField(SERVICE_VERSION_ATTR_NAME, mdcPropertyMap.get(FUNCTION_VERSION.getName())); + serializer.writeRaw(','); + serializer.writeStringField(LOGGER_ATTR_NAME, event.getLoggerName()); + serializer.writeRaw(','); + serializer.writeStringField(THREAD_ATTR_NAME, event.getThreadName()); + + String arn = mdcPropertyMap.get(FUNCTION_ARN.getName()); + + serializeCloudInfo(serializer, arn); + + serializeFunctionInfo(serializer, arn, mdcPropertyMap); + + serializeMDCEntries(mdcPropertyMap, serializer); + + serializeArguments(event, serializer); + + serializer.writeEndObject(); + serializer.writeRaw('\n'); + } catch (IOException e) { + System.err.printf("Failed to encode log event, error: %s.%n", e.getMessage()); + } + return builder.toString().getBytes(UTF_8); + } + + private void serializeFunctionInfo(JsonSerializer serializer, String arn, Map<String, String> mdcPropertyMap) { + if (includeFaasInfo) { + serializer.writeRaw(','); + serializer.writeStringField(FUNCTION_ARN_ATTR_NAME, arn); + serializer.writeRaw(','); + serializer.writeStringField(FUNCTION_NAME_ATTR_NAME, mdcPropertyMap.get(FUNCTION_NAME.getName())); + serializer.writeRaw(','); + serializer.writeStringField(FUNCTION_VERSION_ATTR_NAME, mdcPropertyMap.get(FUNCTION_VERSION.getName())); + serializer.writeRaw(','); + serializer.writeStringField(FUNCTION_MEMORY_ATTR_NAME, mdcPropertyMap.get(FUNCTION_MEMORY_SIZE.getName())); + serializer.writeRaw(','); + serializer.writeStringField(FUNCTION_REQUEST_ID_ATTR_NAME, mdcPropertyMap.get(FUNCTION_REQUEST_ID.getName())); + serializer.writeRaw(','); + serializer.writeStringField(FUNCTION_COLD_START_ATTR_NAME, mdcPropertyMap.get(FUNCTION_COLD_START.getName())); + serializer.writeRaw(','); + serializer.writeStringField(FUNCTION_TRACE_ID_ATTR_NAME, mdcPropertyMap.get(FUNCTION_TRACE_ID.getName())); + String correlationId = mdcPropertyMap.get(CORRELATION_ID.getName()); + if (correlationId != null) { + serializer.writeRaw(','); + serializer.writeStringField(CORRELATION_ID_ATTR_NAME, correlationId); + } + } + } + + private void serializeCloudInfo(JsonSerializer serializer, String arn) { + if (includeCloudInfo) { + serializer.writeRaw(','); + serializer.writeStringField(CLOUD_PROVIDER_ATTR_NAME, CLOUD_PROVIDER); + serializer.writeRaw(','); + serializer.writeStringField(CLOUD_SERVICE_ATTR_NAME, CLOUD_SERVICE); + if (arn != null) { + String[] arnParts = arn.split(":"); + serializer.writeRaw(','); + serializer.writeStringField(CLOUD_REGION_ATTR_NAME, arnParts[3]); + serializer.writeRaw(','); + serializer.writeStringField(CLOUD_ACCOUNT_ATTR_NAME, arnParts[4]); + } + } + } + + private void serializeException(ILoggingEvent event, JsonSerializer serializer) { + IThrowableProxy throwableProxy = event.getThrowableProxy(); + if (throwableProxy != null) { + if (throwableConverter != null) { + serializeException(serializer, throwableProxy.getClassName(), + throwableProxy.getMessage(), throwableConverter.convert(event)); + } else if (throwableProxy instanceof ThrowableProxy) { + Throwable throwable = ((ThrowableProxy) throwableProxy).getThrowable(); + serializeException(serializer, throwable.getClass().getName(), throwable.getMessage(), + Arrays.toString(throwable.getStackTrace())); + } else { + serializeException(serializer, throwableProxy.getClassName(), + throwableProxy.getMessage(), throwableProxyConverter.convert(event)); + } + } + } + + @Override + public byte[] footerBytes() { + return new byte[0]; + } + + /** + * Specify a throwable converter to format the stacktrace according to your need + * (default is <b>null</b>, no throwableConverter): + * <br/> + * <pre>{@code + * <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaEcsEncoder"> + * <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter"> + * <maxDepthPerThrowable>30</maxDepthPerThrowable> + * <maxLength>2048</maxLength> + * <shortenedClassNameLength>20</shortenedClassNameLength> + * <exclude>sun\.reflect\..*\.invoke.*</exclude> + * <exclude>net\.sf\.cglib\.proxy\.MethodProxy\.invoke</exclude> + * <evaluator class="myorg.MyCustomEvaluator"/> + * <rootCauseFirst>true</rootCauseFirst> + * <inlineHash>true</inlineHash> + * </throwableConverter> + * </encoder> + * }</pre> + * + * @param throwableConverter converter for the throwable + */ + public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) { + this.throwableConverter = throwableConverter; + } + + /** + * Specify if cloud information should be logged (default is <b>true</b>): + * <ul> + * <li>cloud.provider</li> + * <li>cloud.service.name</li> + * <li>cloud.region</li> + * <li>cloud.account.id</li> + * </ul> + * <br/> + * We strongly recommend to keep these information. + * <br/> + * <pre>{@code + * <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaEcsEncoder"> + * <includeCloudInfo>false</includeCloudInfo> + * </encoder> + * }</pre> + * + * @param includeCloudInfo if thread information should be logged + */ + public void setIncludeCloudInfo(boolean includeCloudInfo) { + this.includeCloudInfo = includeCloudInfo; + } + + /** + * Specify if Lambda function information should be logged (default is <b>true</b>): + * <ul> + * <li>faas.id</li> + * <li>faas.name</li> + * <li>faas.version</li> + * <li>faas.memory</li> + * <li>faas.execution</li> + * <li>faas.coldstart</li> + * <li>trace.id</li> + * </ul> + * <br/> + * We strongly recommend to keep these information. + * <br/> + * <pre>{@code + * <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaEcsEncoder"> + * <includeFaasInfo>false</includeFaasInfo> + * </encoder> + * }</pre> + * + * @param includeFaasInfo if function information should be logged + */ + public void setIncludeFaasInfo(boolean includeFaasInfo) { + this.includeFaasInfo = includeFaasInfo; + } + + private void serializeException(JsonSerializer serializer, String className, String message, String stackTrace) { + serializer.writeRaw(','); + serializer.writeObjectField(EXCEPTION_MSG_ATTR_NAME, message); + serializer.writeRaw(','); + serializer.writeObjectField(EXCEPTION_CLASS_ATTR_NAME, className); + serializer.writeRaw(','); + serializer.writeObjectField(EXCEPTION_STACK_ATTR_NAME, stackTrace); + } +} diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java new file mode 100644 index 000000000..9afaf0ab7 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java @@ -0,0 +1,260 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.logback; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeArguments; +import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeMDCEntries; +import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeMDCEntry; +import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeTimestamp; + +import ch.qos.logback.classic.pattern.ThrowableHandlingConverter; +import ch.qos.logback.classic.pattern.ThrowableProxyConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.ThrowableProxy; +import ch.qos.logback.core.encoder.EncoderBase; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import software.amazon.lambda.powertools.logging.internal.JsonSerializer; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; + +/** + * Custom encoder for logback that encodes logs in JSON format. + */ +public class LambdaJsonEncoder extends EncoderBase<ILoggingEvent> { + + protected static final String TIMESTAMP_ATTR_NAME = "timestamp"; + protected static final String LEVEL_ATTR_NAME = "level"; + protected static final String FORMATTED_MESSAGE_ATTR_NAME = "message"; + protected static final String THREAD_ATTR_NAME = "thread"; + protected static final String THREAD_ID_ATTR_NAME = "thread_id"; + protected static final String THREAD_PRIORITY_ATTR_NAME = "thread_priority"; + protected static final String EXCEPTION_MSG_ATTR_NAME = "message"; + protected static final String EXCEPTION_CLASS_ATTR_NAME = "name"; + protected static final String EXCEPTION_STACK_ATTR_NAME = "stack"; + protected static final String EXCEPTION_ATTR_NAME = "error"; + + private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter(); + protected ThrowableHandlingConverter throwableConverter = null; + protected String timestampFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + protected String timestampFormatTimezoneId = null; + private boolean includeThreadInfo = false; + private boolean includePowertoolsInfo = true; + + @Override + public byte[] headerBytes() { + return new byte[0]; + } + + @Override + public void start() { + super.start(); + throwableProxyConverter.start(); + if (throwableConverter != null) { + throwableConverter.start(); + } + } + + @SuppressWarnings("java:S106") + @Override + public byte[] encode(ILoggingEvent event) { + StringBuilder builder = new StringBuilder(); + try (JsonSerializer serializer = new JsonSerializer(builder)) { + serializer.writeStartObject(); + serializer.writeStringField(LEVEL_ATTR_NAME, event.getLevel().toString()); + serializer.writeRaw(','); + serializer.writeStringField(FORMATTED_MESSAGE_ATTR_NAME, event.getFormattedMessage()); + + serializeException(event, serializer); + + TreeMap<String, String> sortedMap = new TreeMap<>(event.getMDCPropertyMap()); + serializePowertools(sortedMap, serializer); + + serializeMDCEntries(sortedMap, serializer); + + serializeArguments(event, serializer); + + serializeThreadInfo(event, serializer); + + serializer.writeRaw(','); + serializeTimestamp(serializer, event.getTimeStamp(), + timestampFormat, timestampFormatTimezoneId, TIMESTAMP_ATTR_NAME); + + serializer.writeEndObject(); + serializer.writeRaw('\n'); + } catch (IOException e) { + System.err.printf("Failed to encode log event, error: %s.%n", e.getMessage()); + } + return builder.toString().getBytes(UTF_8); + } + + private void serializeThreadInfo(ILoggingEvent event, JsonSerializer serializer) { + if (includeThreadInfo) { + if (event.getThreadName() != null) { + serializer.writeRaw(','); + serializer.writeStringField(THREAD_ATTR_NAME, event.getThreadName()); + } + serializer.writeRaw(','); + serializer.writeNumberField(THREAD_ID_ATTR_NAME, Thread.currentThread().getId()); + serializer.writeRaw(','); + serializer.writeNumberField(THREAD_PRIORITY_ATTR_NAME, Thread.currentThread().getPriority()); + } + } + + private void serializePowertools(TreeMap<String, String> sortedMap, JsonSerializer serializer) { + if (includePowertoolsInfo) { + for (Map.Entry<String, String> entry : sortedMap.entrySet()) { + if (PowertoolsLoggedFields.stringValues().contains(entry.getKey()) + && !(entry.getKey().equals(PowertoolsLoggedFields.SAMPLING_RATE.getName()) && entry.getValue().equals("0.0"))) { + serializeMDCEntry(entry, serializer); + } + } + } + } + + private void serializeException(ILoggingEvent event, JsonSerializer serializer) { + IThrowableProxy throwableProxy = event.getThrowableProxy(); + if (throwableProxy != null) { + if (throwableConverter != null) { + serializeException(serializer, throwableProxy.getClassName(), + throwableProxy.getMessage(), throwableConverter.convert(event)); + } else if (throwableProxy instanceof ThrowableProxy) { + Throwable throwable = ((ThrowableProxy) throwableProxy).getThrowable(); + serializeException(serializer, throwable.getClass().getName(), throwable.getMessage(), + Arrays.toString(throwable.getStackTrace())); + } else { + serializeException(serializer, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableProxyConverter.convert( + event)); + } + } + } + + @Override + public byte[] footerBytes() { + return new byte[0]; + } + + /** + * Specify the format of the timestamp (default is <b>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'</b>). + * Note that if you use the Lambda Advanced Logging Configuration, you should keep the default format. + * <br/> + * <pre>{@code + * <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + * <timestampFormat>yyyy-MM-dd'T'HH:mm:ss.SSSZz</timestampFormat> + * </encoder> + * }</pre> + * + * @param timestampFormat format of the timestamp (compatible with {@link java.text.SimpleDateFormat}) + */ + public void setTimestampFormat(String timestampFormat) { + this.timestampFormat = timestampFormat; + } + + /** + * Specify the format of the time zone id for timestamp (default is <b>null</b>, no timezone): + * <br/> + * <pre>{@code + * <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + * <timestampFormatTimezoneId>Europe/Paris</timestampFormatTimezoneId> + * </encoder> + * }</pre> + * + * @param timestampFormatTimezoneId Zone Id (see {@link java.util.TimeZone}) + */ + public void setTimestampFormatTimezoneId(String timestampFormatTimezoneId) { + this.timestampFormatTimezoneId = timestampFormatTimezoneId; + } + + /** + * Specify a throwable converter to format the stacktrace according to your need + * (default is <b>null</b>, no throwableConverter): + * <br/> + * <pre>{@code + * <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + * <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter"> + * <maxDepthPerThrowable>30</maxDepthPerThrowable> + * <maxLength>2048</maxLength> + * <shortenedClassNameLength>20</shortenedClassNameLength> + * <exclude>sun\.reflect\..*\.invoke.*</exclude> + * <exclude>net\.sf\.cglib\.proxy\.MethodProxy\.invoke</exclude> + * <evaluator class="myorg.MyCustomEvaluator"/> + * <rootCauseFirst>true</rootCauseFirst> + * <inlineHash>true</inlineHash> + * </throwableConverter> + * </encoder> + * }</pre> + * + * @param throwableConverter converter for the throwable + */ + public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) { + this.throwableConverter = throwableConverter; + } + + /** + * Specify if thread information should be logged (default is <b>false</b>) + * <br/> + * <pre>{@code + * <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + * <includeThreadInfo>true</includeThreadInfo> + * </encoder> + * }</pre> + * + * @param includeThreadInfo if thread information should be logged + */ + public void setIncludeThreadInfo(boolean includeThreadInfo) { + this.includeThreadInfo = includeThreadInfo; + } + + /** + * Specify if Lambda function information should be logged (default is <b>true</b>): + * <ul> + * <li>function_name</li> + * <li>function_version</li> + * <li>function_arn</li> + * <li>function_memory_size</li> + * <li>function_request_id</li> + * <li>cold_start</li> + * <li>xray_trace_id</li> + * <li>sampling_rate</li> + * <li>service</li> + * </ul> + * <br/> + * We strongly recommend to keep these information. + * <br/> + * <pre>{@code + * <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + * <includePowertoolsInfo>false</includePowertoolsInfo> + * </encoder> + * }</pre> + * + * @param includePowertoolsInfo if function information should be logged + */ + public void setIncludePowertoolsInfo(boolean includePowertoolsInfo) { + this.includePowertoolsInfo = includePowertoolsInfo; + } + + private void serializeException(JsonSerializer serializer, String className, String message, String stackTrace) { + Map<Object, Object> map = new HashMap<>(); + map.put(EXCEPTION_MSG_ATTR_NAME, message); + map.put(EXCEPTION_CLASS_ATTR_NAME, className); + map.put(EXCEPTION_STACK_ATTR_NAME, stackTrace); + serializer.writeRaw(','); + serializer.writeObjectField(EXCEPTION_ATTR_NAME, map); + } +} diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/LogbackLoggingManager.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/LogbackLoggingManager.java new file mode 100644 index 000000000..906ebdad5 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/LogbackLoggingManager.java @@ -0,0 +1,100 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.logback.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import org.slf4j.ILoggerFactory; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import software.amazon.lambda.powertools.logging.internal.BufferManager; +import software.amazon.lambda.powertools.logging.internal.LoggingManager; +import software.amazon.lambda.powertools.logging.logback.BufferingAppender; + +/** + * LoggingManager for Logback that provides log level management and buffer operations. + * Implements both {@link LoggingManager} and {@link BufferManager} interfaces. + */ +public class LogbackLoggingManager implements LoggingManager, BufferManager { + + private final LoggerContext loggerContext; + + public LogbackLoggingManager() { + ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); + if (!(loggerFactory instanceof LoggerContext)) { + throw new RuntimeException("LoggerFactory does not match required type: " + LoggerContext.class.getName()); + } + loggerContext = (LoggerContext) loggerFactory; + } + + /** + * @inheritDoc + */ + @Override + @SuppressWarnings("java:S4792") + public void setLogLevel(org.slf4j.event.Level logLevel) { + List<Logger> loggers = loggerContext.getLoggerList(); + for (Logger logger : loggers) { + logger.setLevel(Level.convertAnSLF4JLevel(logLevel)); + } + } + + /** + * @inheritDoc + */ + @Override + public org.slf4j.event.Level getLogLevel(org.slf4j.Logger logger) { + return org.slf4j.event.Level.valueOf(loggerContext.getLogger(logger.getName()).getEffectiveLevel().toString()); + } + + /** + * @inheritDoc + */ + @Override + public void flushBuffer() { + getBufferingAppenders().forEach(BufferingAppender::flushBuffer); + } + + /** + * @inheritDoc + */ + @Override + public void clearBuffer() { + getBufferingAppenders().forEach(BufferingAppender::clearBuffer); + } + + private Collection<BufferingAppender> getBufferingAppenders() { + // Search all buffering appenders to avoid relying on the appender name given by the user + return loggerContext.getLoggerList().stream() + .flatMap(logger -> { + Iterator<Appender<ILoggingEvent>> iterator = logger.iteratorForAppenders(); + List<Appender<ILoggingEvent>> appenders = new ArrayList<>(); + iterator.forEachRemaining(appenders::add); + return appenders.stream(); + }) + .filter(BufferingAppender.class::isInstance) + .map(BufferingAppender.class::cast) + .collect(Collectors.toList()); + } +} diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/LogbackUserAgentInterceptor.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/LogbackUserAgentInterceptor.java new file mode 100644 index 000000000..c35171a01 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/LogbackUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.logging.logback.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-logging-logback module is on the classpath. + */ +public final class LogbackUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("logging-logback"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-logback/jni-config.json b/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-logback/jni-config.json new file mode 100644 index 000000000..c8b081385 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-logback/jni-config.json @@ -0,0 +1,10 @@ +[ +{ + "name":"java.lang.Boolean", + "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"sun.management.VMManagementImpl", + "fields":[{"name":"compTimeMonitoringSupport"}, {"name":"currentThreadCpuTimeSupport"}, {"name":"objectMonitorUsageSupport"}, {"name":"otherThreadCpuTimeSupport"}, {"name":"remoteDiagnosticCommandsSupport"}, {"name":"synchronizerUsageSupport"}, {"name":"threadAllocatedMemorySupport"}, {"name":"threadContentionMonitoringSupport"}] +} +] diff --git a/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-logback/reflect-config.json b/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-logback/reflect-config.json new file mode 100644 index 000000000..5c5eb83fa --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-logback/reflect-config.json @@ -0,0 +1,187 @@ +[ +{ + "name":"[Ljava.lang.Object;" +}, +{ + "name":"[Ljava.lang.String;" +}, +{ + "name":"ch.qos.logback.classic.encoder.PatternLayoutEncoder", + "queryAllPublicMethods":true, + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.joran.SerializedModelConfigurator", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.util.DefaultJoranConfigurator", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.core.FileAppender", + "queryAllPublicMethods":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setFile","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"ch.qos.logback.core.OutputStreamAppender", + "methods":[{"name":"setEncoder","parameterTypes":["ch.qos.logback.core.encoder.Encoder"] }] +}, +{ + "name":"ch.qos.logback.core.encoder.Encoder", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"ch.qos.logback.core.encoder.LayoutWrappingEncoder", + "methods":[{"name":"setParent","parameterTypes":["ch.qos.logback.core.spi.ContextAware"] }] +}, +{ + "name":"ch.qos.logback.core.pattern.PatternLayoutEncoderBase", + "methods":[{"name":"setPattern","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"ch.qos.logback.core.spi.ContextAware", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.Context" +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.SQSEvent$MessageAttribute", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getBinaryListValues","parameterTypes":[] }, {"name":"getBinaryValue","parameterTypes":[] }, {"name":"getDataType","parameterTypes":[] }, {"name":"getStringListValues","parameterTypes":[] }, {"name":"getStringValue","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getAttributes","parameterTypes":[] }, {"name":"getAwsRegion","parameterTypes":[] }, {"name":"getBody","parameterTypes":[] }, {"name":"getEventSource","parameterTypes":[] }, {"name":"getEventSourceArn","parameterTypes":[] }, {"name":"getMd5OfBody","parameterTypes":[] }, {"name":"getMd5OfMessageAttributes","parameterTypes":[] }, {"name":"getMessageAttributes","parameterTypes":[] }, {"name":"getMessageId","parameterTypes":[] }, {"name":"getReceiptHandle","parameterTypes":[] }] +}, +{ + "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"java.io.IOException" +}, +{ + "name":"java.io.InputStream" +}, +{ + "name":"java.io.OutputStream" +}, +{ + "name":"java.io.Serializable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Cloneable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Iterable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Object" +}, +{ + "name":"java.lang.ProcessEnvironment", + "fields":[{"name":"theCaseInsensitiveEnvironment"}, {"name":"theEnvironment"}] +}, +{ + "name":"java.lang.String" +}, +{ + "name":"java.util.AbstractCollection", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.AbstractList", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.AbstractMap", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.Arrays$ArrayList", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.Collection", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.Collections$SingletonMap", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.Collections$UnmodifiableMap", + "fields":[{"name":"m"}] +}, +{ + "name":"java.util.List", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.Map", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.RandomAccess", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"kotlin.Metadata" +}, +{ + "name":"org.apiguardian.api.API", + "queryAllPublicMethods":true +}, +{ + "name":"software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor", + "fields":[{"name":"isColdStart"}] +}, +{ + "name":"software.amazon.lambda.powertools.logging.logback.BufferingAppender", + "queryAllPublicMethods":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setBufferAtVerbosity","parameterTypes":["java.lang.String"] }, {"name":"setFlushOnErrorLog","parameterTypes":["boolean"] }, {"name":"setMaxBytes","parameterTypes":["int"] }] +}, +{ + "name":"software.amazon.lambda.powertools.logging.logback.LambdaEcsEncoder", + "queryAllPublicMethods":true, + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder", + "queryAllPublicMethods":true, + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.logging.logback.internal.LogbackUserAgentInterceptor", + "methods":[{"name":"<init>","parameterTypes":[] }] +} +] diff --git a/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-logback/resource-config.json b/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-logback/resource-config.json new file mode 100644 index 000000000..b60a39ad5 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging-logback/resource-config.json @@ -0,0 +1,17 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E" + }, { + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/javax.xml.parsers.SAXParserFactory\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/global/handlers/execution.interceptors\\E" + }]}, + "bundles":[] +} diff --git a/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager b/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager new file mode 100644 index 000000000..958f2e459 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager @@ -0,0 +1 @@ +software.amazon.lambda.powertools.logging.logback.internal.LogbackLoggingManager \ No newline at end of file diff --git a/powertools-logging/powertools-logging-logback/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-logging/powertools-logging-logback/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..1ce49119f --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.logging.logback.internal.LogbackUserAgentInterceptor diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/LogbackLoggingManagerTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/LogbackLoggingManagerTest.java new file mode 100644 index 000000000..60f158739 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/LogbackLoggingManagerTest.java @@ -0,0 +1,106 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; +import static org.slf4j.event.Level.DEBUG; +import static org.slf4j.event.Level.ERROR; +import static org.slf4j.event.Level.WARN; + +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; +import software.amazon.lambda.powertools.logging.logback.internal.LogbackLoggingManager; + +class LogbackLoggingManagerTest { + + private static final Logger LOG = LoggerFactory.getLogger(LogbackLoggingManagerTest.class); + private static final Logger ROOT = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + + @BeforeEach + void setUp() throws JoranException, IOException { + resetLogbackConfig("/logback-test.xml"); + + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + } + + @Test + void getLogLevel_shouldReturnConfiguredLogLevel() { + LogbackLoggingManager manager = new LogbackLoggingManager(); + Level logLevel = manager.getLogLevel(LOG); + assertThat(logLevel).isEqualTo(DEBUG); + + logLevel = manager.getLogLevel(ROOT); + assertThat(logLevel).isEqualTo(WARN); + } + + @Test + void resetLogLevel() { + LogbackLoggingManager manager = new LogbackLoggingManager(); + manager.setLogLevel(ERROR); + + Level logLevel = manager.getLogLevel(LOG); + assertThat(logLevel).isEqualTo(ERROR); + } + + @Test + void shouldDetectMultipleBufferingAppendersRegardlessOfName() throws JoranException { + // Given - configuration with multiple BufferingAppenders with different names + resetLogbackConfig("/logback-multiple-buffering.xml"); + + Logger logger = LoggerFactory.getLogger("test.multiple.appenders"); + + // When - log messages and flush buffers + logger.debug("Test message 1"); + logger.debug("Test message 2"); + + LogbackLoggingManager manager = new LogbackLoggingManager(); + manager.flushBuffer(); + + // Then - both appenders should have flushed their buffers + File logFile = new File("target/logfile.json"); + assertThat(logFile).exists(); + String content = contentOf(logFile); + // Each message should appear twice (once from each BufferingAppender) + assertThat(content.split("Test message 1", -1)).hasSize(3); // 2 occurrences = 3 parts + assertThat(content.split("Test message 2", -1)).hasSize(3); // 2 occurrences = 3 parts + } + + private void resetLogbackConfig(String configFileName) throws JoranException { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + context.reset(); + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + configurator.doConfigure(getClass().getResourceAsStream(configFileName)); + } +} diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java new file mode 100644 index 000000000..30ede8ba8 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java @@ -0,0 +1,182 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; + +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import com.amazonaws.services.lambda.runtime.Context; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.pattern.RootCauseFirstThrowableProxyConverter; +import ch.qos.logback.classic.spi.LoggingEvent; +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; +import software.amazon.lambda.powertools.logging.PowertoolsLogging; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; +import software.amazon.lambda.powertools.logging.logback.LambdaEcsEncoder; + +@Order(3) +class LambdaEcsEncoderTest { + + private static final Logger logger = (Logger) LoggerFactory.getLogger(LambdaEcsEncoderTest.class.getName()); + + private Context context; + + @BeforeEach + void setUp() throws IllegalAccessException, IOException { + MDC.clear(); + // Reset cold start state + writeStaticField(LambdaHandlerProcessor.class, "isColdStart", null, true); + writeStaticField(PowertoolsLogging.class, "hasBeenInitialized", new AtomicBoolean(false), true); + + context = new TestLambdaContext(); + // Make sure file is cleaned up before running tests + try { + FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // file may not exist on the first launch + } + } + + @AfterEach + void cleanUp() throws IOException { + try { + FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // file may not exist on the first launch + } + } + + @Test + void shouldLogInEcsFormat() { + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/ecslogfile.json"); + assertThat(contentOf(logFile)).contains( + "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLogback\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"us-east-1\",\"cloud.account.id\":\"123456789012\",\"faas.id\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"faas.name\":\"test-function\",\"faas.version\":\"1\",\"faas.memory\":\"128\",\"faas.execution\":\"test-request-id\",\"faas.coldstart\":\"true\",\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); + } + + private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null); + + @Test + void shouldNotLogFunctionInfo() { + // GIVEN + LambdaEcsEncoder encoder = new LambdaEcsEncoder(); + setMDC(); + + // WHEN + byte[] encoded = encoder.encode(loggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains( + "\"faas.id\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"faas.name\":\"test-function\",\"faas.version\":\"1\",\"faas.memory\":\"128\",\"faas.execution\":\"test-request-id\",\"faas.coldstart\":\"false\"") + .contains("\"correlation.id\":\"test-correlation-id\""); + + // WHEN (includeFaasInfo = false) + encoder.setIncludeFaasInfo(false); + encoded = encoder.encode(loggingEvent); + result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (no faas info in logs) + assertThat(result).doesNotContain("faas"); + } + + @Test + void shouldNotLogCloudInfo() { + // GIVEN + LambdaEcsEncoder encoder = new LambdaEcsEncoder(); + setMDC(); + + // WHEN + byte[] encoded = encoder.encode(loggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains( + "\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"us-east-1\",\"cloud.account.id\":\"123456789012\""); + + // WHEN (includeCloudInfo = false) + encoder.setIncludeCloudInfo(false); + encoded = encoder.encode(loggingEvent); + result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (no faas info in logs) + assertThat(result).doesNotContain("cloud"); + } + + @Test + void shouldLogException() { + // GIVEN + LambdaEcsEncoder encoder = new LambdaEcsEncoder(); + encoder.start(); + LoggingEvent errorloggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Error", + new IllegalStateException("Unexpected value"), null); + + // WHEN + byte[] encoded = encoder.encode(errorloggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains( + "\"message\":\"Error\",\"error.message\":\"Unexpected value\",\"error.type\":\"java.lang.IllegalStateException\",\"error.stack_trace\":\"[software.amazon.lambda.powertools.logging.internal.LambdaEcsEncoderTest.shouldLogException"); + + // WHEN (configure a custom throwableConverter) + encoder = new LambdaEcsEncoder(); + RootCauseFirstThrowableProxyConverter throwableConverter = new RootCauseFirstThrowableProxyConverter(); + encoder.setThrowableConverter(throwableConverter); + encoder.start(); + encoded = encoder.encode(errorloggingEvent); + result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (stack is logged with root cause first) + assertThat(result).contains( + "\"message\":\"Error\",\"error.message\":\"Unexpected value\",\"error.type\":\"java.lang.IllegalStateException\",\"error.stack_trace\":\"java.lang.IllegalStateException: Unexpected value\\n"); + } + + private void setMDC() { + MDC.put(PowertoolsLoggedFields.FUNCTION_NAME.getName(), context.getFunctionName()); + MDC.put(PowertoolsLoggedFields.FUNCTION_ARN.getName(), context.getInvokedFunctionArn()); + MDC.put(PowertoolsLoggedFields.FUNCTION_VERSION.getName(), context.getFunctionVersion()); + MDC.put(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName(), + String.valueOf(context.getMemoryLimitInMB())); + MDC.put(PowertoolsLoggedFields.FUNCTION_REQUEST_ID.getName(), context.getAwsRequestId()); + MDC.put(PowertoolsLoggedFields.FUNCTION_COLD_START.getName(), "false"); + MDC.put(PowertoolsLoggedFields.SAMPLING_RATE.getName(), "0.2"); + MDC.put(PowertoolsLoggedFields.SERVICE.getName(), "Service"); + MDC.put(PowertoolsLoggedFields.CORRELATION_ID.getName(), "test-correlation-id"); + } + +} diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java new file mode 100644 index 000000000..16bd9e92a --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java @@ -0,0 +1,445 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal; + +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.joining; +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.pattern.RootCauseFirstThrowableProxyConverter; +import ch.qos.logback.classic.spi.LoggingEvent; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.TimeZone; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.logging.PowertoolsLogging; +import software.amazon.lambda.powertools.logging.argument.StructuredArgument; +import software.amazon.lambda.powertools.logging.argument.StructuredArguments; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsArguments; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEvent; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEventDisabled; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEventForStream; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogResponse; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogResponseForStream; +import software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder; + +@Order(2) +class LambdaJsonEncoderTest { + private static final Logger logger = (Logger) LoggerFactory.getLogger(LambdaJsonEncoderTest.class.getName()); + private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null); + + private Context context; + + @BeforeEach + void setUp() throws IllegalAccessException, IOException { + MDC.clear(); + // Reset cold start state + writeStaticField(LambdaHandlerProcessor.class, "isColdStart", null, true); + writeStaticField(PowertoolsLogging.class, "hasBeenInitialized", new AtomicBoolean(false), true); + + context = new TestLambdaContext(); + // Make sure file is cleaned up before running tests + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // file may not exist on the first launch + } + } + + @AfterEach + void cleanUp() throws IOException { + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // file may not exist on the first launch + } + } + + @Test + void shouldLogInJsonFormat() { + // GIVEN + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + + // WHEN + handler.handleRequest("Input", context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains( + "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"function_memory_size\":128,\"function_name\":\"test-function\",\"function_request_id\":\"test-request-id\",\"function_version\":1,\"service\":\"testLogback\",\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\",\"timestamp\":"); + } + + @Test + void shouldLogArgumentsAsJsonWhenUsingRawJson() { + // GIVEN + PowertoolsArguments requestHandler = new PowertoolsArguments(PowertoolsArguments.ArgumentFormat.JSON); + SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage(); + msg.setMessageId("1212abcd"); + msg.setBody("plop"); + msg.setEventSource("eb"); + msg.setAwsRegion("eu-central-1"); + SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute(); + attribute.setStringListValues(Arrays.asList("val1", "val2", "val3")); + msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute)); + + // WHEN + requestHandler.handleRequest(msg, context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains( + "\"input\":{\"awsRegion\":\"eu-central-1\",\"body\":\"plop\",\"eventSource\":\"eb\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}},\"messageId\":\"1212abcd\"}") + .contains("\"message\":\"1212abcd\"") + // Should auto-escape double quotes around id + .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\"") + .contains("\"correlation_id\":\"1212abcd\""); + // Reserved keys should be ignored + PowertoolsLoggedFields.stringValues().stream().forEach(reservedKey -> { + assertThat(contentOf(logFile)).doesNotContain("\"" + reservedKey + "\":\"shouldBeIgnored\""); + assertThat(contentOf(logFile)).contains( + "\"message\":\"Attempted to use reserved key '" + reservedKey + + "' in structured argument. This key will be ignored.\""); + }); + } + + @Test + void shouldLogArgumentsAsJsonWhenUsingKeyValue() { + // GIVEN + PowertoolsArguments requestHandler = new PowertoolsArguments(PowertoolsArguments.ArgumentFormat.ENTRY); + SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage(); + msg.setMessageId("1212abcd"); + msg.setBody("plop"); + msg.setEventSource("eb"); + msg.setAwsRegion("eu-central-1"); + SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute(); + attribute.setStringListValues(Arrays.asList("val1", "val2", "val3")); + msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute)); + + // WHEN + requestHandler.handleRequest(msg, context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains( + "\"input\":{\"awsRegion\":\"eu-central-1\",\"body\":\"plop\",\"eventSource\":\"eb\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}},\"messageId\":\"1212abcd\"}") + .contains("\"message\":\"1212abcd\"") + // Should auto-escape double quotes around id + .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\"") + .contains("\"correlation_id\":\"1212abcd\""); + // Reserved keys should be ignored + PowertoolsLoggedFields.stringValues().stream().forEach(reservedKey -> { + assertThat(contentOf(logFile)).doesNotContain("\"" + reservedKey + "\":\"shouldBeIgnored\""); + assertThat(contentOf(logFile)).contains( + "\"message\":\"Attempted to use reserved key '" + reservedKey + + "' in structured argument. This key will be ignored.\""); + }); + } + + @Test + void shouldNotLogPowertoolsInfo() { + // GIVEN + LambdaJsonEncoder encoder = new LambdaJsonEncoder(); + + MDC.put(PowertoolsLoggedFields.FUNCTION_NAME.getName(), context.getFunctionName()); + MDC.put(PowertoolsLoggedFields.FUNCTION_ARN.getName(), context.getInvokedFunctionArn()); + MDC.put(PowertoolsLoggedFields.FUNCTION_VERSION.getName(), context.getFunctionVersion()); + MDC.put(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName(), + String.valueOf(context.getMemoryLimitInMB())); + MDC.put(PowertoolsLoggedFields.FUNCTION_REQUEST_ID.getName(), context.getAwsRequestId()); + MDC.put(PowertoolsLoggedFields.FUNCTION_COLD_START.getName(), "false"); + MDC.put(PowertoolsLoggedFields.SAMPLING_RATE.getName(), "0.2"); + MDC.put(PowertoolsLoggedFields.SERVICE.getName(), "Service"); + + // WHEN + byte[] encoded = encoder.encode(loggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains( + "{\"level\":\"INFO\",\"message\":\"message\",\"cold_start\":false,\"function_arn\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"function_memory_size\":128,\"function_name\":\"test-function\",\"function_request_id\":\"test-request-id\",\"function_version\":1,\"sampling_rate\":0.2,\"service\":\"Service\",\"timestamp\":"); + + // WHEN (powertoolsInfo = false) + encoder.setIncludePowertoolsInfo(false); + encoded = encoder.encode(loggingEvent); + result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (no powertools info in logs) + assertThat(result).doesNotContain("cold_start", "function_arn", "function_memory_size", "function_name", + "function_request_id", "function_version", "sampling_rate", "service"); + } + + @Test + void shouldLogStructuredArgumentsAsNewEntries() { + // GIVEN + LambdaJsonEncoder encoder = new LambdaJsonEncoder(); + + SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage(); + msg.setMessageId("1212abcd"); + msg.setBody("plop"); + msg.setEventSource("eb"); + msg.setAwsRegion("eu-central-1"); + SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute(); + attribute.setStringListValues(Arrays.asList("val1", "val2", "val3")); + msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute)); + StructuredArgument argument = StructuredArguments.entry("msg", msg); + + // WHEN + LoggingEvent structuredLoggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "A message", null, + new Object[] { argument }); + byte[] encoded = encoder.encode(structuredLoggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (logged as JSON) + assertThat(result) + .contains( + "\"message\":\"A message\",\"msg\":{\"awsRegion\":\"eu-central-1\",\"body\":\"plop\",\"eventSource\":\"eb\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}},\"messageId\":\"1212abcd\"}"); + } + + @Test + void shouldLogEventForHandlerWithLogEventAnnotation() { + // GIVEN + PowertoolsLogEvent requestHandler = new PowertoolsLogEvent(); + + // WHEN + requestHandler.handleRequest(singletonList("ListOfOneElement"), context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("\"event\":[\"ListOfOneElement\"]"); + } + + @Test + void shouldLogEventForHandlerWhenEnvVariableSetToTrue() { + try { + // GIVEN + LoggingConstants.POWERTOOLS_LOG_EVENT = true; + + PowertoolsLogEnabled requestHandler = new PowertoolsLogEnabled(); + + SQSEvent.SQSMessage message = new SQSEvent.SQSMessage(); + message.setBody("body"); + message.setMessageId("1234abcd"); + message.setAwsRegion("eu-central-1"); + + // WHEN + requestHandler.handleRequest(message, context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("\"message\":\"Handler Event\"") + .contains( + "\"event\":{\"awsRegion\":\"eu-central-1\",\"body\":\"body\",\"messageId\":\"1234abcd\"}"); + } finally { + LoggingConstants.POWERTOOLS_LOG_EVENT = false; + } + } + + @Test + void shouldNotLogEventForHandlerWhenEnvVariableSetToFalse() throws IOException { + // GIVEN + LoggingConstants.POWERTOOLS_LOG_EVENT = false; + + // WHEN + PowertoolsLogEventDisabled requestHandler = new PowertoolsLogEventDisabled(); + requestHandler.handleRequest(singletonList("ListOfOneElement"), context); + + // THEN + Assertions.assertEquals(0, + Files.lines(Paths.get("target/logfile.json")).collect(joining()).length()); + } + + @Test + void shouldLogEventAsStringForStreamHandler() throws IOException { + // GIVEN + PowertoolsLogEventForStream requestStreamHandler = new PowertoolsLogEventForStream(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + // WHEN + requestStreamHandler.handleRequest( + new ByteArrayInputStream( + new ObjectMapper().writeValueAsBytes(Collections.singletonMap("key", "value"))), + output, context); + + // THEN + assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8)) + .isNotEmpty(); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("\"message\":\"Handler Event\"") + // logged as String for StreamHandler (should auto-escape double-quotes to avoid breaking JSON format) + .contains("\"event\":\"{\\\"key\\\":\\\"value\\\"}\""); + } + + @Test + void shouldLogResponseForHandlerWithLogResponseAnnotation() { + // GIVEN + PowertoolsLogResponse requestHandler = new PowertoolsLogResponse(); + + // WHEN + requestHandler.handleRequest("input", context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("\"message\":\"Handler Response\"") + .contains("\"response\":\"Hola mundo\""); + } + + @Test + void shouldLogResponseForHandlerWhenEnvVariableSetToTrue() { + try { + // GIVEN + LoggingConstants.POWERTOOLS_LOG_RESPONSE = true; + + PowertoolsLogEnabled requestHandler = new PowertoolsLogEnabled(); + + // WHEN + requestHandler.handleRequest("input", context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("\"message\":\"Handler Response\"") + .contains("\"response\":\"Bonjour le monde\""); + } finally { + LoggingConstants.POWERTOOLS_LOG_RESPONSE = false; + } + } + + @Test + void shouldLogResponseForStreamHandler() throws IOException { + // GIVEN + PowertoolsLogResponseForStream requestStreamHandler = new PowertoolsLogResponseForStream(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + String input = "<user><firstName>Bob</firstName><lastName>The Sponge</lastName></user>"; + + // WHEN + requestStreamHandler.handleRequest(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), output, + context); + + // THEN + assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8)) + .isEqualTo(input); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("\"message\":\"Handler Response\"") + .contains("\"response\":\"" + input + "\""); + } + + @Test + void shouldLogThreadInfo() { + // GIVEN + LambdaJsonEncoder encoder = new LambdaJsonEncoder(); + encoder.setIncludeThreadInfo(true); + + // WHEN + byte[] encoded = encoder.encode(loggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains( + "\"thread\":\"main\",\"thread_id\":" + Thread.currentThread().getId() + ",\"thread_priority\":5"); + } + + @Test + void shouldLogTimestampDifferently() { + // GIVEN + LambdaJsonEncoder encoder = new LambdaJsonEncoder(); + String pattern = "yyyy-MM-dd_HH"; + String timeZone = "Europe/Paris"; + encoder.setTimestampFormat(pattern); + encoder.setTimestampFormatTimezoneId(timeZone); + + // WHEN + Date date = new Date(); + byte[] encoded = encoder.encode(loggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern); + simpleDateFormat.setTimeZone(TimeZone.getTimeZone(timeZone)); + assertThat(result).contains("\"timestamp\":\"" + simpleDateFormat.format(date) + "\""); + } + + @Test + void shouldLogException() { + // GIVEN + LambdaJsonEncoder encoder = new LambdaJsonEncoder(); + encoder.start(); + LoggingEvent errorloggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Error", + new IllegalStateException("Unexpected value"), null); + + // WHEN + byte[] encoded = encoder.encode(errorloggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains("\"message\":\"Error\",\"error\":{") + .contains("\"message\":\"Unexpected value\"") + .contains("\"name\":\"java.lang.IllegalStateException\"") + .contains( + "\"stack\":\"[software.amazon.lambda.powertools.logging.internal.LambdaJsonEncoderTest.shouldLogException"); + + // WHEN (configure a custom throwableConverter) + encoder = new LambdaJsonEncoder(); + RootCauseFirstThrowableProxyConverter throwableConverter = new RootCauseFirstThrowableProxyConverter(); + encoder.setThrowableConverter(throwableConverter); + encoder.start(); + encoded = encoder.encode(errorloggingEvent); + result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (stack is logged with root cause first) + assertThat(result).contains("\"message\":\"Unexpected value\"") + .contains("\"name\":\"java.lang.IllegalStateException\"") + .contains("\"stack\":\"java.lang.IllegalStateException: Unexpected value\\n"); + } + +} diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsArguments.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsArguments.java new file mode 100644 index 000000000..1fc235ff7 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsArguments.java @@ -0,0 +1,82 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal.handler; + +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.CORRELATION_ID; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.fasterxml.jackson.core.JsonProcessingException; + +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.argument.StructuredArguments; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +public class PowertoolsArguments implements RequestHandler<SQSEvent.SQSMessage, String> { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsArguments.class); + private final ArgumentFormat argumentFormat; + + public PowertoolsArguments(ArgumentFormat argumentFormat) { + this.argumentFormat = argumentFormat; + } + + @Override + @Logging(clearState = true) + public String handleRequest(SQSEvent.SQSMessage input, Context context) { + try { + MDC.put(CORRELATION_ID.getName(), input.getMessageId()); + if (argumentFormat == ArgumentFormat.JSON) { + LOG.debug("SQS Event", + StructuredArguments.json("input", + JsonConfig.get().getObjectMapper().writeValueAsString(input)), + // function_name is a reserved key by PowertoolsLoggedFields and should be omitted + StructuredArguments.entry("function_name", "shouldBeIgnored")); + } else { + LOG.debug("SQS Event", + StructuredArguments.entry("input", input), + // function_name is a reserved key by PowertoolsLoggedFields and should be omitted + StructuredArguments.entry("function_name", "shouldBeIgnored")); + } + + // Attempt logging all reserved keys, the values should not be overwritten by "shouldBeIgnored" + final Map<String, String> reservedKeysMap = new HashMap<>(); + for (String field : PowertoolsLoggedFields.stringValues()) { + reservedKeysMap.put(field, "shouldBeIgnored"); + } + reservedKeysMap.put("message", "shouldBeIgnored"); + reservedKeysMap.put("level", "shouldBeIgnored"); + reservedKeysMap.put("timestamp", "shouldBeIgnored"); + LOG.debug("Reserved keys", StructuredArguments.entries(reservedKeysMap)); + LOG.debug("{}", input.getMessageId()); + LOG.warn("Message body = {} and id = \"{}\"", input.getBody(), input.getMessageId()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return input.getMessageId(); + } + + public enum ArgumentFormat { + JSON, ENTRY + } +} diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java new file mode 100644 index 000000000..e8c0c5851 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal.handler; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogEnabled implements RequestHandler<Object, Object> { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogEnabled.class); + + @Override + @Logging(clearState = true) + public Object handleRequest(Object input, Context context) { + MDC.put("myKey", "myValue"); + LOG.debug("Test debug event"); + return "Bonjour le monde"; + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabled.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEvent.java similarity index 81% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabled.java rename to powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEvent.java index 152eb284d..14a7874cb 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabled.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,16 +11,17 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.logging.handlers; + +package software.amazon.lambda.powertools.logging.internal.handler; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import software.amazon.lambda.powertools.logging.Logging; -public class PowerToolLogEventEnabled implements RequestHandler<Object, Object> { +public class PowertoolsLogEvent implements RequestHandler<Object, Object> { - @Logging(logEvent = true) @Override + @Logging(logEvent = true) public Object handleRequest(Object input, Context context) { return null; } diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledWithNoMetaDataDeprecated.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEventDisabled.java similarity index 65% rename from powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledWithNoMetaDataDeprecated.java rename to powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEventDisabled.java index 2ded5e69f..8171bee3e 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledWithNoMetaDataDeprecated.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEventDisabled.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,18 +11,17 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.tracing.handlers; + +package software.amazon.lambda.powertools.logging.internal.handler; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -import software.amazon.lambda.powertools.tracing.Tracing; - -import static software.amazon.lambda.powertools.tracing.CaptureMode.DISABLED; +import software.amazon.lambda.powertools.logging.Logging; -public class PowerTracerToolEnabledWithNoMetaDataDeprecated implements RequestHandler<Object, Object> { +public class PowertoolsLogEventDisabled implements RequestHandler<Object, Object> { @Override - @Tracing(captureResponse = false, captureError = false) + @Logging(logEvent = false) public Object handleRequest(Object input, Context context) { return null; } diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledForStream.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEventForStream.java similarity index 71% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledForStream.java rename to powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEventForStream.java index 473042e6c..443051204 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledForStream.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEventForStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,24 +11,24 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.logging.handlers; + +package software.amazon.lambda.powertools.logging.internal.handler; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; import com.fasterxml.jackson.databind.ObjectMapper; -import software.amazon.lambda.powertools.logging.Logging; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Map; +import software.amazon.lambda.powertools.logging.Logging; -public class PowerToolLogEventEnabledForStream implements RequestStreamHandler { +public class PowertoolsLogEventForStream implements RequestStreamHandler { - @Logging(logEvent = true) @Override - public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { + @Logging(logEvent = true) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { ObjectMapper mapper = new ObjectMapper(); - mapper.writeValue(output, mapper.readValue(input, Map.class)); + mapper.writeValue(outputStream, mapper.readValue(inputStream, Map.class)); } } diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogResponse.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogResponse.java new file mode 100644 index 000000000..5cbeef487 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogResponse.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal.handler; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogResponse implements RequestHandler<Object, Object> { + + @Override + @Logging(logResponse = true) + public Object handleRequest(Object input, Context context) { + return "Hola mundo"; + } +} diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogResponseForStream.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogResponseForStream.java new file mode 100644 index 000000000..3378b9421 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogResponseForStream.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal.handler; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogResponseForStream implements RequestStreamHandler { + + @Override + @Logging(logResponse = true) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + byte[] buf = new byte[1024]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + } +} diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/logback/BufferingAppenderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/logback/BufferingAppenderTest.java new file mode 100644 index 000000000..62d2e3056 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/logback/BufferingAppenderTest.java @@ -0,0 +1,150 @@ +package software.amazon.lambda.powertools.logging.logback; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; + +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ClearEnvironmentVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; + +class BufferingAppenderTest { + + private Logger logger; + + @BeforeEach + void setUp() throws IOException, JoranException { + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + + // Configure Logback with BufferingAppender + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + context.reset(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + configurator.doConfigure(getClass().getResourceAsStream("/logback-buffering-test.xml")); + + logger = LoggerFactory.getLogger(BufferingAppenderTest.class); + } + + @AfterEach + void cleanUp() throws IOException { + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there + } + } + + @Test + void shouldBufferDebugLogsAndFlushOnError() { + // When - log debug messages (should be buffered) + logger.debug("Debug message 1"); + logger.debug("Debug message 2"); + + // Then - no logs written yet + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).isEmpty(); + + // When - log error (should flush buffer) + logger.error("Error message"); + + // Then - all logs written + assertThat(contentOf(logFile)) + .contains("Debug message 1") + .contains("Debug message 2") + .contains("Error message"); + } + + @Test + @ClearEnvironmentVariable(key = "_X_AMZN_TRACE_ID") + void shouldLogDirectlyWhenNoTraceId() { + // When + logger.debug("Debug without trace"); + + // Then - log written directly + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("Debug without trace"); + } + + @Test + void shouldNotBufferInfoLogs() { + // When - log info message (above buffer level) + logger.info("Info message"); + + // Then - log written directly + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("Info message"); + } + + @Test + void shouldFlushBufferManually() { + // When - buffer debug logs + logger.debug("Buffered message"); + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).isEmpty(); + + // When - manual flush + BufferingAppender appender = getBufferingAppender(); + appender.flushBuffer(); + + // Then - logs written + assertThat(contentOf(logFile)).contains("Buffered message"); + } + + @Test + void shouldClearBufferManually() { + // When - buffer debug logs then clear + logger.debug("Buffered message"); + BufferingAppender appender = getBufferingAppender(); + appender.clearBuffer(); + + // When - log error (should not flush cleared buffer) + logger.error("Error after clear"); + + // Then - only error logged + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("Error after clear") + .doesNotContain("Buffered message"); + } + + @Test + void shouldLogOverflowWarningWhenBufferOverflows() { + // When - fill buffer beyond capacity to trigger overflow + for (int i = 0; i < 100; i++) { + logger.debug("Debug message {}", i); + } + + // When - flush buffer to trigger overflow warning + BufferingAppender appender = getBufferingAppender(); + appender.flushBuffer(); + + // Then - overflow warning should be logged + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("Some logs are not displayed because they were evicted from the buffer"); + } + + private BufferingAppender getBufferingAppender() { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + return (BufferingAppender) context.getLogger(BufferingAppenderTest.class).getAppender("TestBufferingAppender"); + } +} diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/logback/internal/LogbackUserAgentInterceptorTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/logback/internal/LogbackUserAgentInterceptorTest.java new file mode 100644 index 000000000..622343668 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/logback/internal/LogbackUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.logging.logback.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class LogbackUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/LOGGING-LOGBACK/"); + } +} diff --git a/powertools-logging/powertools-logging-logback/src/test/resources/junit-platform.properties b/powertools-logging/powertools-logging-logback/src/test/resources/junit-platform.properties new file mode 100644 index 000000000..80a2481d7 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/resources/junit-platform.properties @@ -0,0 +1,17 @@ +# +# Copyright 2023 Amazon.com, Inc. or its affiliates. +# 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 +# http://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. +# +# + +# because of LambdaLoggingAspect static initialization of the LoggingManager, we need to +# set an order in the unit tests, especially LambdaLoggingAspectTest needs to be first +junit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer$OrderAnnotation \ No newline at end of file diff --git a/powertools-logging/powertools-logging-logback/src/test/resources/logback-buffering-test.xml b/powertools-logging/powertools-logging-logback/src/test/resources/logback-buffering-test.xml new file mode 100644 index 000000000..b9ec3917f --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/resources/logback-buffering-test.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + <appender name="testFile" class="ch.qos.logback.core.FileAppender"> + <file>target/logfile.json</file> + <encoder> + <pattern>%msg%n</pattern> + </encoder> + </appender> + + <appender name="TestBufferingAppender" class="software.amazon.lambda.powertools.logging.logback.BufferingAppender"> + <bufferAtVerbosity>DEBUG</bufferAtVerbosity> + <maxBytes>1024</maxBytes> + <flushOnErrorLog>true</flushOnErrorLog> + <appender-ref ref="testFile" /> + </appender> + + <logger name="software.amazon.lambda.powertools.logging.logback.BufferingAppenderTest" level="DEBUG" + additivity="false"> + <appender-ref ref="TestBufferingAppender" /> + </logger> +</configuration> diff --git a/powertools-logging/powertools-logging-logback/src/test/resources/logback-multiple-buffering.xml b/powertools-logging/powertools-logging-logback/src/test/resources/logback-multiple-buffering.xml new file mode 100644 index 000000000..84c348d61 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/resources/logback-multiple-buffering.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + <appender name="testFile" class="ch.qos.logback.core.FileAppender"> + <file>target/logfile.json</file> + <encoder> + <pattern>%msg%n</pattern> + </encoder> + </appender> + + <appender name="FirstBufferingAppender" class="software.amazon.lambda.powertools.logging.logback.BufferingAppender"> + <bufferAtVerbosity>DEBUG</bufferAtVerbosity> + <maxBytes>1024</maxBytes> + <flushOnErrorLog>true</flushOnErrorLog> + <appender-ref ref="testFile" /> + </appender> + + <appender name="SecondBufferingAppender" class="software.amazon.lambda.powertools.logging.logback.BufferingAppender"> + <bufferAtVerbosity>DEBUG</bufferAtVerbosity> + <maxBytes>1024</maxBytes> + <flushOnErrorLog>true</flushOnErrorLog> + <appender-ref ref="testFile" /> + </appender> + + <logger name="test.multiple.appenders" level="DEBUG" additivity="false"> + <appender-ref ref="FirstBufferingAppender" /> + <appender-ref ref="SecondBufferingAppender" /> + </logger> +</configuration> diff --git a/powertools-logging/powertools-logging-logback/src/test/resources/logback-test.xml b/powertools-logging/powertools-logging-logback/src/test/resources/logback-test.xml new file mode 100644 index 000000000..e118a03de --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/resources/logback-test.xml @@ -0,0 +1,27 @@ +<configuration> + <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + </encoder> + </appender> + + <appender name="logFile" class="ch.qos.logback.core.FileAppender"> + <file>target/logfile.json</file> + <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"> + </encoder> + </appender> + + <appender name="logFileWithEcs" class="ch.qos.logback.core.FileAppender"> + <file>target/ecslogfile.json</file> + <encoder class="software.amazon.lambda.powertools.logging.logback.LambdaEcsEncoder"> + </encoder> + </appender> + + <logger name="software.amazon.lambda.powertools" level="DEBUG" additivity="false"> + <appender-ref ref="logFile" /> + <appender-ref ref="logFileWithEcs" /> + </logger> + <root level="WARN"> + <appender-ref ref="logFile" /> + <appender-ref ref="logFileWithEcs" /> + </root> +</configuration> \ No newline at end of file diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/CorrelationIdPathConstants.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/CorrelationIdPathConstants.java deleted file mode 100644 index e0d24b8a5..000000000 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/CorrelationIdPathConstants.java +++ /dev/null @@ -1,23 +0,0 @@ -package software.amazon.lambda.powertools.logging; - -/** - * Supported Event types from which Correlation ID can be extracted - */ -public class CorrelationIdPathConstants { - /** - * To use when function is expecting API Gateway Rest API Request event - */ - public static final String API_GATEWAY_REST = "/requestContext/requestId"; - /** - * To use when function is expecting API Gateway HTTP API Request event - */ - public static final String API_GATEWAY_HTTP = "/requestContext/requestId"; - /** - * To use when function is expecting Application Load balancer Request event - */ - public static final String APPLICATION_LOAD_BALANCER = "/headers/x-amzn-trace-id"; - /** - * To use when function is expecting Event Bridge Request event - */ - public static final String EVENT_BRIDGE = "/id"; -} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/CorrelationIdPaths.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/CorrelationIdPaths.java new file mode 100644 index 000000000..6fb38502f --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/CorrelationIdPaths.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging; + +/** + * Supported Event types from which Correlation ID can be extracted + */ +public class CorrelationIdPaths { + /** + * To use when function is expecting API Gateway Rest API Request event + */ + public static final String API_GATEWAY_REST = "requestContext.requestId"; + /** + * To use when function is expecting API Gateway HTTP API Request event + */ + public static final String API_GATEWAY_HTTP = "requestContext.requestId"; + /** + * To use when function is expecting Application Load balancer Request event + */ + public static final String APPLICATION_LOAD_BALANCER = "headers.\"x-amzn-trace-id\""; + /** + * To use when function is expecting Event Bridge Request event + */ + public static final String EVENT_BRIDGE = "id"; + /** + * To use when function is expecting an AppSync request + */ + public static final String APPSYNC_RESOLVER = "request.headers.\"x-amzn-trace-id\""; +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java index b86b800b7..79d1a95fd 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.logging; import java.lang.annotation.ElementType; @@ -33,18 +34,19 @@ * {@code com.amazonaws.services.lambda.runtime.Context}</p> * * <ul> - * <li>FunctionName</li> - * <li>FunctionVersion</li> - * <li>InvokedFunctionArn</li> - * <li>MemoryLimitInMB</li> + * <li>function_name</li> + * <li>function_version</li> + * <li>function_arn</li> + * <li>function_memory_size</li> + * <li>function_request_id</li> * </ul> * * <p>By default {@code Logging} will also create keys for:</p> * * <ul> - * <li>coldStart - True if this is the first invocation of this Lambda execution environment; else False</li> + * <li>cold_start - True if this is the first invocation of this Lambda execution environment; else False</li> * <li>service - The value of the 'POWER_TOOLS_SERVICE_NAME' environment variable or 'service_undefined'</li> - * <li>samplingRate - The value of the 'POWERTOOLS_LOGGER_SAMPLE_RATE' environment variable or value of samplingRate field or 0. + * <li>sampling_rate - The value of the 'POWERTOOLS_LOGGER_SAMPLE_RATE' environment variable or value of sampling_rate field or 0. * Valid value is from 0.0 to 1.0. Value outside this range is silently ignored.</li> * </ul> * @@ -55,22 +57,42 @@ * <p>By default {@code Logging} will not log the event which has trigger the invoke of the Lambda function. * This can be enabled using {@code @Logging(logEvent = true)}.</p> * - * <p>By default {@code Logging} all debug logs will follow log4j2 configuration unless configured via - * POWERTOOLS_LOGGER_SAMPLE_RATE environment variable {@code @Logging(samplingRate = <0.0-1.0>)}.</p> - * - * <p>To append additional keys to each log entry you can use {@link LoggingUtils#appendKey(String, String)}</p> + * <p>To append additional keys to each log entry you can either use {@link org.slf4j.MDC#put(String, String)} + * or {@link software.amazon.lambda.powertools.logging.argument.StructuredArguments}</p> */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Logging { + /** + * Set to true if you want to log the event received by the Lambda function handler.<br/> + * Can also be configured with the 'POWERTOOLS_LOGGER_LOG_EVENT' environment variable + */ boolean logEvent() default false; + /** + * Set to true if you want to log the response sent by the Lambda function handler.<br/> + * Can also be configured with the 'POWERTOOLS_LOGGER_LOG_RESPONSE' environment variable + */ + boolean logResponse() default false; + + /** + * Set to true if you want to log the exception thrown by the Lambda function handler. + * It is already logged by AWS Lambda but with no context information. Setting this to true + * will log the exception and all the powertools additional fields, for more context.<br/> + * Can also be configured with the 'POWERTOOLS_LOGGER_LOG_ERROR' environment variable + */ + boolean logError() default false; + + /** + * Sampling rate to change log level to DEBUG. (values must be >=0.0, <=1.0) + */ double samplingRate() default 0; /** * Json Pointer path to extract correlation id from. - * @see <a href=https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03/> + * + * @see <a href=https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03/> */ String correlationIdPath() default ""; @@ -80,4 +102,10 @@ * Set this attribute to true if you want all custom keys to be deleted on each request. */ boolean clearState() default false; + + /** + * Set to true if you want to flush the log buffer when an uncaught exception occurs. + * This ensures that buffered logs are output when errors happen. + */ + boolean flushBufferOnUncaughtError() default true; } diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java deleted file mode 100644 index f23e274d4..000000000 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. - * 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 - * http://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. - * - */ -package software.amazon.lambda.powertools.logging; - -import java.util.Map; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.logging.log4j.ThreadContext; - -import static java.util.Arrays.asList; - -/** - * A class of helper functions to add additional functionality to Logging. - * - * {@see Logging} - */ -public final class LoggingUtils { - private static ObjectMapper objectMapper; - - private LoggingUtils() { - } - - /** - * Appends an additional key and value to each log entry made. Duplicate values - * for the same key will be replaced with the latest. - * - * @param key The name of the key to be logged - * @param value The value to be logged - */ - public static void appendKey(String key, String value) { - ThreadContext.put(key, value); - } - - - - /** - * Appends additional key and value to each log entry made. Duplicate values - * for the same key will be replaced with the latest. - * - * @param customKeys Map of custom keys values to be appended to logs - */ - public static void appendKeys(Map<String, String> customKeys) { - ThreadContext.putAll(customKeys); - } - - /** - * Remove an additional key from log entry. - * - * @param customKey The name of the key to be logged - */ - public static void removeKey(String customKey) { - ThreadContext.remove(customKey); - } - - - /** - * Removes additional keys from log entry. - * - * @param keys Map of custom keys values to be appended to logs - */ - public static void removeKeys(String... keys) { - ThreadContext.removeAll(asList(keys)); - } - - /** - * Sets correlation id attribute on the logs. - * - * @param value The value of the correlation id - */ - public static void setCorrelationId(String value) { - ThreadContext.put("correlation_id", value); - } - - /** - * Sets the instance of ObjectMapper object which is used for serialising event when - * {@code @Logging(logEvent = true)}. - * - * @param objectMapper Custom implementation of object mapper to be used for logging serialised event - */ - public static void defaultObjectMapper(ObjectMapper objectMapper) { - LoggingUtils.objectMapper = objectMapper; - } - - public static ObjectMapper objectMapper() { - if(null == objectMapper) { - objectMapper = new ObjectMapper(); - } - - return objectMapper; - } -} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/PowertoolsLogging.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/PowertoolsLogging.java new file mode 100644 index 000000000..f4c18af64 --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/PowertoolsLogging.java @@ -0,0 +1,369 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging; + +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.coldStartDone; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.getXrayTraceId; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.isColdStart; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.serviceName; +import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.LAMBDA_LOG_LEVEL; +import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_LOG_LEVEL; +import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_SAMPLING_RATE; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_TRACE_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SAMPLING_RATE; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SERVICE; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Random; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.slf4j.event.Level; + +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.databind.JsonNode; + +import io.burt.jmespath.Expression; +import software.amazon.lambda.powertools.logging.internal.BufferManager; +import software.amazon.lambda.powertools.logging.internal.LoggingManager; +import software.amazon.lambda.powertools.logging.internal.LoggingManagerRegistry; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +/** + * PowertoolsLogging provides a logging backend-agnostic API for managing Powertools logging functionality. + * This class abstracts away the underlying logging framework (Log4j2, Logback) and provides a unified + * interface for Lambda context extraction, correlation ID handling, sampling rate configuration, + * log buffering operations, and other Lambda-specific logging features. + * + * <p>This class serves as a programmatic alternative to AspectJ-based {@code @Logging} annotation, + * allowing developers to integrate Powertools logging capabilities without AspectJ dependencies.</p> + * + * Key features: + * <ul> + * <li>Lambda context initialization with function metadata, trace ID, and service name</li> + * <li>Sampling rate configuration for DEBUG logging</li> + * <li>Backend-independent log buffer management (flush/clear operations)</li> + * <li>MDC state management for structured logging</li> + * </ul> + */ +public final class PowertoolsLogging { + private static final Logger LOG = LoggerFactory.getLogger(PowertoolsLogging.class); + private static final ThreadLocal<Random> SAMPLER = ThreadLocal.withInitial(Random::new); + private static AtomicBoolean hasBeenInitialized = new AtomicBoolean(false); + + static { + initializeLogLevel(); + } + + private PowertoolsLogging() { + // Utility class + } + + private static void initializeLogLevel() { + if (POWERTOOLS_LOG_LEVEL != null) { + Level powertoolsLevel = getLevelFromString(POWERTOOLS_LOG_LEVEL); + if (LAMBDA_LOG_LEVEL != null) { + Level lambdaLevel = getLevelFromString(LAMBDA_LOG_LEVEL); + if (powertoolsLevel.toInt() < lambdaLevel.toInt()) { + LOG.warn( + "Current log level ({}) does not match AWS Lambda Advanced Logging Controls minimum log level ({}). This can lead to data loss, consider adjusting them.", + POWERTOOLS_LOG_LEVEL, LAMBDA_LOG_LEVEL); + } + } + setLogLevel(powertoolsLevel); + } else if (LAMBDA_LOG_LEVEL != null) { + setLogLevel(getLevelFromString(LAMBDA_LOG_LEVEL)); + } + } + + private static Level getLevelFromString(String level) { + if (Arrays.stream(Level.values()).anyMatch(slf4jLevel -> slf4jLevel.name().equalsIgnoreCase(level))) { + return Level.valueOf(level.toUpperCase(Locale.ROOT)); + } else { + // FATAL does not exist in slf4j + if ("FATAL".equalsIgnoreCase(level)) { + return Level.ERROR; + } + } + // default to INFO if incorrect value + return Level.INFO; + } + + private static void setLogLevel(Level logLevel) { + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + loggingManager.setLogLevel(logLevel); + } + + /** + * Flushes the log buffer for the current Lambda execution. + * This method will flush any buffered logs to the output stream. + * The operation is backend-independent and works with both Log4j2 and Logback. + */ + public static void flushBuffer() { + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + if (loggingManager instanceof BufferManager) { + ((BufferManager) loggingManager).flushBuffer(); + } + } + + /** + * Clears the log buffer for the current Lambda execution. + * This method will discard any buffered logs without outputting them. + * The operation is backend-independent and works with both Log4j2 and Logback. + */ + public static void clearBuffer() { + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + if (loggingManager instanceof BufferManager) { + ((BufferManager) loggingManager).clearBuffer(); + } + } + + /** + * Initializes Lambda logging context with standard Powertools fields. + * This method should be called at the beginning of your Lambda handler to set up + * logging context with Lambda function information, trace ID, and service name. + * + * <p>Important: Call {@link #clearState(boolean)} at the end of your handler or use + * {@link #withLogging(Context, Supplier)} to handle cleanup automatically.</p> + * + * @param context the Lambda context provided by AWS Lambda runtime + */ + public static void initializeLogging(Context context) { + initializeLogging(context, 0.0, null, null); + } + + /** + * Initializes Lambda logging context with sampling rate configuration. + * This method sets up logging context and optionally enables DEBUG logging + * based on the provided sampling rate. + * + * <p>Important: Call {@link #clearState(boolean)} at the end of your handler or use + * {@link #withLogging(Context, double, Supplier)} to handle cleanup automatically.</p> + * + * @param context the Lambda context provided by AWS Lambda runtime + * @param samplingRate sampling rate for DEBUG logging (0.0 to 1.0) + */ + public static void initializeLogging(Context context, double samplingRate) { + initializeLogging(context, samplingRate, null, null); + } + + /** + * Initializes Lambda logging context with correlation ID extraction. + * This method sets up logging context and extracts correlation ID from the event + * using the provided JSON path. + * + * <p>Important: Call {@link #clearState(boolean)} at the end of your handler or use + * {@link #withLogging(Context, String, Object, Supplier)} to handle cleanup automatically.</p> + * + * @param context the Lambda context provided by AWS Lambda runtime + * @param correlationIdPath JSON path to extract correlation ID from event + * @param event the Lambda event object + */ + public static void initializeLogging(Context context, String correlationIdPath, Object event) { + initializeLogging(context, 0.0, correlationIdPath, event); + } + + /** + * Initializes Lambda logging context with full configuration. + * This method sets up logging context with Lambda function information, + * configures sampling rate for DEBUG logging, and optionally extracts + * correlation ID from the event. + * + * <p>Important: Call {@link #clearState(boolean)} at the end of your handler or use + * {@link #withLogging(Context, double, String, Object, Supplier)} to handle cleanup automatically.</p> + * + * <p>This method is thread-safe.</p> + * + * @param context the Lambda context provided by AWS Lambda runtime + * @param samplingRate sampling rate for DEBUG logging (0.0 to 1.0) + * @param correlationIdPath JSON path to extract correlation ID from event (can be null) + * @param event the Lambda event object (required if correlationIdPath is provided) + */ + public static void initializeLogging(Context context, double samplingRate, String correlationIdPath, Object event) { + + addLambdaContextToLoggingContext(context); + setLogLevelBasedOnSamplingRate(samplingRate); + getXrayTraceId().ifPresent(xRayTraceId -> MDC.put(FUNCTION_TRACE_ID.getName(), xRayTraceId)); + + if (correlationIdPath != null && !correlationIdPath.isEmpty() && event != null) { + captureCorrelationId(correlationIdPath, event); + } + } + + // Synchronized since isColdStart() is a globally managed constant in LambdaHandlerProcessor + private static synchronized void addLambdaContextToLoggingContext(Context context) { + if (context != null) { + PowertoolsLoggedFields.setValuesFromLambdaContext(context).forEach(MDC::put); + } + + MDC.put(FUNCTION_COLD_START.getName(), isColdStart() ? "true" : "false"); + if (hasBeenInitialized.compareAndSet(false, true)) { + coldStartDone(); + } + MDC.put(SERVICE.getName(), serviceName()); + } + + private static void setLogLevelBasedOnSamplingRate(double samplingRate) { + double effectiveSamplingRate = getEffectiveSamplingRate(samplingRate); + + if (effectiveSamplingRate < 0 || effectiveSamplingRate > 1) { + LOG.warn("Skipping sampling rate configuration because of invalid value. Sampling rate: {}", + effectiveSamplingRate); + return; + } + + MDC.put(SAMPLING_RATE.getName(), String.valueOf(effectiveSamplingRate)); + + if (effectiveSamplingRate == 0) { + return; + } + + float sample = SAMPLER.get().nextFloat(); + if (effectiveSamplingRate > sample) { + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + loggingManager.setLogLevel(Level.DEBUG); + LOG.debug( + "Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {}, Sampler Value: {}.", + effectiveSamplingRate, sample); + } + } + + // The environment variable takes precedence over manually set sampling rate + private static double getEffectiveSamplingRate(double samplingRate) { + String envSampleRate = POWERTOOLS_SAMPLING_RATE; + if (envSampleRate != null) { + try { + return Double.parseDouble(envSampleRate); + } catch (NumberFormatException e) { + LOG.warn( + "Skipping sampling rate on environment variable configuration because of invalid value. Sampling rate: {}", + envSampleRate); + } + } + + return samplingRate; + } + + private static void captureCorrelationId(String correlationIdPath, Object event) { + try { + JsonNode jsonNode = JsonConfig.get().getObjectMapper().valueToTree(event); + Expression<JsonNode> jmesExpression = JsonConfig.get().getJmesPath().compile(correlationIdPath); + JsonNode node = jmesExpression.search(jsonNode); + + String asText = node.asText(); + if (asText != null && !asText.isEmpty()) { + MDC.put(PowertoolsLoggedFields.CORRELATION_ID.getName(), asText); + } else { + LOG.warn("Unable to extract any correlation id. Is your function expecting supported event type?"); + } + } catch (Exception e) { + LOG.warn("Failed to capture correlation id from event.", e); + } + } + + /** + * Clears MDC state and log buffer. + * + * @param clearMdcState whether to clear MDC state + */ + public static void clearState(boolean clearMdcState) { + if (clearMdcState) { + MDC.clear(); + } + clearBuffer(); + SAMPLER.remove(); + } + + /** + * Executes code with logging context initialized and automatically clears state. + * + * @param context the Lambda context provided by AWS Lambda runtime + * @param supplier the code to execute with logging context + * @param <T> the return type + * @return the result of the supplier execution + */ + public static <T> T withLogging(Context context, Supplier<T> supplier) { + initializeLogging(context); + try { + return supplier.get(); + } finally { + clearState(true); + } + } + + /** + * Executes code with logging context initialized with sampling rate and automatically clears state. + * + * @param context the Lambda context provided by AWS Lambda runtime + * @param samplingRate sampling rate for DEBUG logging (0.0 to 1.0) + * @param supplier the code to execute with logging context + * @param <T> the return type + * @return the result of the supplier execution + */ + public static <T> T withLogging(Context context, double samplingRate, Supplier<T> supplier) { + initializeLogging(context, samplingRate); + try { + return supplier.get(); + } finally { + clearState(true); + } + } + + /** + * Executes code with logging context initialized with correlation ID extraction and automatically clears state. + * + * @param context the Lambda context provided by AWS Lambda runtime + * @param correlationIdPath JSON path to extract correlation ID from event + * @param event the Lambda event object + * @param supplier the code to execute with logging context + * @param <T> the return type + * @return the result of the supplier execution + */ + public static <T> T withLogging(Context context, String correlationIdPath, Object event, Supplier<T> supplier) { + initializeLogging(context, correlationIdPath, event); + try { + return supplier.get(); + } finally { + clearState(true); + } + } + + /** + * Executes code with logging context initialized with full configuration and automatically clears state. + * + * @param context the Lambda context provided by AWS Lambda runtime + * @param samplingRate sampling rate for DEBUG logging (0.0 to 1.0) + * @param correlationIdPath JSON path to extract correlation ID from event (can be null) + * @param event the Lambda event object (required if correlationIdPath is provided) + * @param supplier the code to execute with logging context + * @param <T> the return type + * @return the result of the supplier execution + */ + public static <T> T withLogging(Context context, double samplingRate, String correlationIdPath, Object event, + Supplier<T> supplier) { + initializeLogging(context, samplingRate, correlationIdPath, event); + try { + return supplier.get(); + } finally { + clearState(true); + } + } +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/ArrayArgument.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/ArrayArgument.java new file mode 100644 index 000000000..cbedbdc0f --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/ArrayArgument.java @@ -0,0 +1,45 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.argument; + +import java.util.Objects; + +import software.amazon.lambda.powertools.logging.internal.JsonSerializer; + +/** + * See {@link StructuredArguments#array(String, Object...)} + */ +class ArrayArgument implements StructuredArgument { + private final String key; + private final Object[] values; + + public ArrayArgument(String key, Object[] values) { + this.key = Objects.requireNonNull(key, "Key must not be null"); + this.values = new Object[values.length]; + System.arraycopy(values, 0, this.values, 0, values.length); + } + + @Override + public void writeTo(JsonSerializer serializer) { + serializer.writeFieldName(key); + serializer.writeObject(values); + } + + @Override + public String toString() { + return key + "=" + StructuredArguments.toString(values); + } + +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/JsonArgument.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/JsonArgument.java new file mode 100644 index 000000000..debea18c5 --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/JsonArgument.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.argument; + +import java.util.Objects; + +import software.amazon.lambda.powertools.logging.internal.JsonSerializer; + +/** + * See {@link StructuredArguments#json(String, String)} + */ +class JsonArgument implements StructuredArgument { + private final String key; + private final String rawJson; + + public JsonArgument(String key, String rawJson) { + this.key = Objects.requireNonNull(key, "key must not be null"); + this.rawJson = Objects.requireNonNull(rawJson, "rawJson must not be null"); + } + + @Override + public void writeTo(JsonSerializer serializer) { + serializer.writeFieldName(key); + serializer.writeRaw(rawJson); + } + + @Override + public String toString() { + return key + "=" + rawJson; + } + +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/KeyValueArgument.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/KeyValueArgument.java new file mode 100644 index 000000000..54648d99e --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/KeyValueArgument.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.argument; + +import java.util.Objects; + +import software.amazon.lambda.powertools.logging.internal.JsonSerializer; + +/** + * See {@link StructuredArguments#entry(String, Object)} + */ +class KeyValueArgument implements StructuredArgument { + private final String key; + private final Object value; + + public KeyValueArgument(String key, Object value) { + this.key = Objects.requireNonNull(key, "Key must not be null"); + this.value = value; + } + + @Override + public void writeTo(JsonSerializer serializer) { + serializer.writeObjectField(key, value); + } + + @Override + public String toString() { + return key + "=" + StructuredArguments.toString(value); + } + + public String getKey() { + return key; + } + + public Object getValue() { + return value; + } +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/MapArgument.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/MapArgument.java new file mode 100644 index 000000000..1155fa93b --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/MapArgument.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.argument; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import software.amazon.lambda.powertools.logging.internal.JsonSerializer; + +/** + * See {@link StructuredArguments#entries(Map)} + */ +class MapArgument implements StructuredArgument { + private final Map<?, ?> map; + + public MapArgument(Map<?, ?> map) { + if (map != null) { + this.map = new HashMap<>(map); + } else { + this.map = null; + } + } + + @Override + public void writeTo(JsonSerializer serializer) { + if (map != null) { + for (Iterator<? extends Map.Entry<?, ?>> entries = map.entrySet().iterator(); entries.hasNext();) { + Map.Entry<?, ?> entry = entries.next(); + serializer.writeObjectField(String.valueOf(entry.getKey()), entry.getValue()); + // If the map has more than one entry, we need to print a (comma) separator to avoid breaking the JSON + if (entries.hasNext()) { + serializer.writeSeparator(); + } + } + } + } + + @Override + public String toString() { + return String.valueOf(map); + } + +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/StructuredArgument.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/StructuredArgument.java new file mode 100644 index 000000000..00cc651eb --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/StructuredArgument.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.argument; + +import java.io.IOException; + +import org.slf4j.Logger; + +import software.amazon.lambda.powertools.logging.internal.JsonSerializer; + +/** + * A wrapper for an argument passed to a log method (e.g. {@link Logger#info(String, Object...)}) + * that adds data to the JSON event. + */ +public interface StructuredArgument { + /** + * Writes the data associated with this argument to the given {@link JsonSerializer}. + * + * @param serializer + * the {@link JsonSerializer} to produce JSON content + * @throws IOException + * if an I/O error occurs + */ + void writeTo(JsonSerializer serializer) throws IOException; + + /** + * Writes the data associated with this argument to a {@link String} to be + * included in a log event's formatted message (via parameter substitution). + * <p> + * Note that this will only be included in the log event's formatted + * message if the message format includes a parameter for this argument (using {}). + * + * @return String representation of the data associated with this argument + */ + String toString(); + +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/StructuredArguments.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/StructuredArguments.java new file mode 100644 index 000000000..f56a42ea3 --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/argument/StructuredArguments.java @@ -0,0 +1,193 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.argument; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; + +/** + * Factory for creating {@link StructuredArgument}s. + * Inspired from the StructuredArgument of logstash-logback-encoder. + */ +public class StructuredArguments { + + private static final Logger LOGGER = LoggerFactory.getLogger(StructuredArguments.class); + + /** + * Set of reserved keys that should not be used in structured arguments. + * When a reserved key is used, the method will return null. + */ + private static final Set<String> RESERVED_KEYS = Stream + .concat(PowertoolsLoggedFields.stringValues().stream(), + List.of("message", "level", "timestamp", "error").stream()) + .collect(Collectors.toSet()); + + private StructuredArguments() { + // nothing to do, use static methods only + } + + /** + * Checks if the provided key is a reserved key. + * If the key is reserved, logs a warning message. + * + * @param key the key to check + * @return true if the key is reserved, false otherwise + */ + private static boolean isReservedKey(String key) { + if (key != null && RESERVED_KEYS.contains(key)) { + LOGGER.warn( + "Attempted to use reserved key '{}' in structured argument. This key will be ignored.", key); + return true; + } + return false; + } + + /** + * Adds "key": "value" to the JSON structure and "key=value" to the formatted message. + * Returns null if the key is a reserved key. + * + * @param key the field name + * @param value the value associated with the key (can be any kind of object) + * @return a {@link StructuredArgument} populated with the data, or null if key is reserved + */ + public static StructuredArgument entry(String key, Object value) { + if (isReservedKey(key)) { + return null; + } + return new KeyValueArgument(key, value); + } + + /** + * Adds a "key": "value" to the JSON structure for each entry in the map + * and {@code map.toString()} to the formatted message. + * If the map contains any reserved keys, those entries will be filtered out. + * + * @param map {@link Map} holding the key/value pairs + * @return a {@link MapArgument} populated with the data, with reserved keys filtered out + */ + public static StructuredArgument entries(Map<?, ?> map) { + if (map == null) { + return null; + } + + // Create a new map without reserved keys + Map<Object, Object> filteredMap = new java.util.HashMap<>(); + for (Map.Entry<?, ?> entry : map.entrySet()) { + if (entry.getKey() != null) { + String keyStr = String.valueOf(entry.getKey()); + if (!isReservedKey(keyStr)) { + filteredMap.put(entry.getKey(), entry.getValue()); + } + } + } + + return new MapArgument(filteredMap); + } + + /** + * Adds a field to the JSON structure with key as the key and where value + * is a JSON array of objects AND a string version of the array to the formatted message: + * {@code "key": [value, value]} + * Returns null if the key is a reserved key. + * + * @param key the field name + * @param values elements of the array associated with the key + * @return an {@link ArrayArgument} populated with the data, or null if key is reserved + */ + public static StructuredArgument array(String key, Object... values) { + if (isReservedKey(key)) { + return null; + } + return new ArrayArgument(key, values); + } + + /** + * Adds the {@code rawJson} to the JSON structure and + * the {@code rawJson} to the formatted message. + * Returns null if the key is a reserved key. + * + * @param key the field name + * @param rawJson the raw JSON String + * @return a {@link JsonArgument} populated with the data, or null if key is reserved + */ + public static StructuredArgument json(String key, String rawJson) { + if (isReservedKey(key)) { + return null; + } + return new JsonArgument(key, rawJson); + } + + /** + * Format the argument into a string. + * + * This method mimics the slf4j behavior: + * array objects are formatted as array using {@link Arrays#toString}, + * non array object using {@link String#valueOf}. + * + * <p>See org.slf4j.helpers.MessageFormatter#deeplyAppendParameter(StringBuilder, Object, Map)} + * + * @param arg the argument to format + * @return formatted string version of the argument + */ + @SuppressWarnings("java:S106") + public static String toString(Object arg) { + + if (arg == null) { + return "null"; + } + + Class<?> argClass = arg.getClass(); + + try { + if (!argClass.isArray()) { + return String.valueOf(arg); + } else { + if (argClass == byte[].class) { + return Arrays.toString((byte[]) arg); + } else if (argClass == short[].class) { + return Arrays.toString((short[]) arg); + } else if (argClass == int[].class) { + return Arrays.toString((int[]) arg); + } else if (argClass == long[].class) { + return Arrays.toString((long[]) arg); + } else if (argClass == char[].class) { + return Arrays.toString((char[]) arg); + } else if (argClass == float[].class) { + return Arrays.toString((float[]) arg); + } else if (argClass == double[].class) { + return Arrays.toString((double[]) arg); + } else if (argClass == boolean[].class) { + return Arrays.toString((boolean[]) arg); + } else { + return Arrays.deepToString((Object[]) arg); + } + } + + } catch (Exception e) { + System.err.println("Failed toString() invocation on an object of type [" + argClass.getName() + "]"); + return "[FAILED toString()]"; + } + } + +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/AbstractJacksonLayoutCopy.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/AbstractJacksonLayoutCopy.java deleted file mode 100644 index 3ceda4b79..000000000 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/AbstractJacksonLayoutCopy.java +++ /dev/null @@ -1,494 +0,0 @@ -package software.amazon.lambda.powertools.logging.internal; - -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.impl.ThrowableProxy; -import org.apache.logging.log4j.core.jackson.XmlConstants; -import org.apache.logging.log4j.core.layout.AbstractStringLayout; -import org.apache.logging.log4j.core.lookup.StrSubstitutor; -import org.apache.logging.log4j.core.time.Instant; -import org.apache.logging.log4j.core.util.KeyValuePair; -import org.apache.logging.log4j.core.util.StringBuilderWriter; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.util.Strings; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonRootName; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import com.fasterxml.jackson.core.JsonGenerationException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectWriter; - -@Deprecated -abstract class AbstractJacksonLayoutCopy extends AbstractStringLayout { - - protected static final String DEFAULT_EOL = "\r\n"; - protected static final String COMPACT_EOL = Strings.EMPTY; - - public static abstract class Builder<B extends Builder<B>> extends AbstractStringLayout.Builder<B> { - - @PluginBuilderAttribute - private boolean eventEol; - - @PluginBuilderAttribute - private String endOfLine; - - @PluginBuilderAttribute - private boolean compact; - - @PluginBuilderAttribute - private boolean complete; - - @PluginBuilderAttribute - private boolean locationInfo; - - @PluginBuilderAttribute - private boolean properties; - - @PluginBuilderAttribute - private boolean includeStacktrace = true; - - @PluginBuilderAttribute - private boolean stacktraceAsString = false; - - @PluginBuilderAttribute - private boolean includeNullDelimiter = false; - - @PluginBuilderAttribute - private boolean includeTimeMillis = false; - - @PluginElement("AdditionalField") - private KeyValuePair[] additionalFields; - - protected String toStringOrNull(final byte[] header) { - return header == null ? null : new String(header, Charset.defaultCharset()); - } - - public boolean getEventEol() { - return eventEol; - } - - public String getEndOfLine() { - return endOfLine; - } - - public boolean isCompact() { - return compact; - } - - public boolean isComplete() { - return complete; - } - - public boolean isLocationInfo() { - return locationInfo; - } - - public boolean isProperties() { - return properties; - } - - /** - * If "true", includes the stacktrace of any Throwable in the generated data, defaults to "true". - * @return If "true", includes the stacktrace of any Throwable in the generated data, defaults to "true". - */ - public boolean isIncludeStacktrace() { - return includeStacktrace; - } - - public boolean isStacktraceAsString() { - return stacktraceAsString; - } - - public boolean isIncludeNullDelimiter() { return includeNullDelimiter; } - - public boolean isIncludeTimeMillis() { - return includeTimeMillis; - } - - public KeyValuePair[] getAdditionalFields() { - return additionalFields; - } - - public B setEventEol(final boolean eventEol) { - this.eventEol = eventEol; - return asBuilder(); - } - - public B setEndOfLine(final String endOfLine) { - this.endOfLine = endOfLine; - return asBuilder(); - } - - public B setCompact(final boolean compact) { - this.compact = compact; - return asBuilder(); - } - - public B setComplete(final boolean complete) { - this.complete = complete; - return asBuilder(); - } - - public B setLocationInfo(final boolean locationInfo) { - this.locationInfo = locationInfo; - return asBuilder(); - } - - public B setProperties(final boolean properties) { - this.properties = properties; - return asBuilder(); - } - - /** - * If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true". - * @param includeStacktrace If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true". - * @return this builder - */ - public B setIncludeStacktrace(final boolean includeStacktrace) { - this.includeStacktrace = includeStacktrace; - return asBuilder(); - } - - /** - * Whether to format the stacktrace as a string, and not a nested object (optional, defaults to false). - * - * @return this builder - */ - public B setStacktraceAsString(final boolean stacktraceAsString) { - this.stacktraceAsString = stacktraceAsString; - return asBuilder(); - } - - /** - * Whether to include NULL byte as delimiter after each event (optional, default to false). - * - * @return this builder - */ - public B setIncludeNullDelimiter(final boolean includeNullDelimiter) { - this.includeNullDelimiter = includeNullDelimiter; - return asBuilder(); - } - - /** - * Whether to include the timestamp (in addition to the Instant) (optional, default to false). - * - * @return this builder - */ - public B setIncludeTimeMillis(final boolean includeTimeMillis) { - this.includeTimeMillis = includeTimeMillis; - return asBuilder(); - } - - /** - * Additional fields to set on each log event. - * - * @return this builder - */ - public B setAdditionalFields(final KeyValuePair[] additionalFields) { - this.additionalFields = additionalFields; - return asBuilder(); - } - } - - protected final String eol; - protected final ObjectWriter objectWriter; - protected final boolean compact; - protected final boolean complete; - protected final boolean includeNullDelimiter; - protected final ResolvableKeyValuePair[] additionalFields; - - @Deprecated - protected AbstractJacksonLayoutCopy(final Configuration config, final ObjectWriter objectWriter, final Charset charset, - final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer, - final Serializer footerSerializer) { - this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer, false); - } - - @Deprecated - protected AbstractJacksonLayoutCopy(final Configuration config, final ObjectWriter objectWriter, final Charset charset, - final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer, - final Serializer footerSerializer, final boolean includeNullDelimiter) { - this(config, objectWriter, charset, compact, complete, eventEol, null, headerSerializer, footerSerializer, includeNullDelimiter, null); - } - - protected AbstractJacksonLayoutCopy(final Configuration config, final ObjectWriter objectWriter, final Charset charset, - final boolean compact, final boolean complete, final boolean eventEol, final String endOfLine, final Serializer headerSerializer, - final Serializer footerSerializer, final boolean includeNullDelimiter, - final KeyValuePair[] additionalFields) { - super(config, charset, headerSerializer, footerSerializer); - this.objectWriter = objectWriter; - this.compact = compact; - this.complete = complete; - this.eol = endOfLine != null ? endOfLine : compact && !eventEol ? COMPACT_EOL : DEFAULT_EOL; - this.includeNullDelimiter = includeNullDelimiter; - this.additionalFields = prepareAdditionalFields(config, additionalFields); - } - - protected static boolean valueNeedsLookup(final String value) { - return value != null && value.contains("${"); - } - - private static ResolvableKeyValuePair[] prepareAdditionalFields(final Configuration config, final KeyValuePair[] additionalFields) { - if (additionalFields == null || additionalFields.length == 0) { - // No fields set - return ResolvableKeyValuePair.EMPTY_ARRAY; - } - - // Convert to specific class which already determines whether values needs lookup during serialization - final ResolvableKeyValuePair[] resolvableFields = new ResolvableKeyValuePair[additionalFields.length]; - - for (int i = 0; i < additionalFields.length; i++) { - final ResolvableKeyValuePair resolvable = resolvableFields[i] = new ResolvableKeyValuePair(additionalFields[i]); - - // Validate - if (config == null && resolvable.valueNeedsLookup) { - throw new IllegalArgumentException("configuration needs to be set when there are additional fields with variables"); - } - } - - return resolvableFields; - } - - /** - * Formats a {@link org.apache.logging.log4j.core.LogEvent}. - * - * @param event The LogEvent. - * @return The XML representation of the LogEvent. - */ - @Override - public String toSerializable(final LogEvent event) { - final StringBuilderWriter writer = new StringBuilderWriter(); - try { - toSerializable(event, writer); - return writer.toString(); - } catch (final IOException e) { - // Should this be an ISE or IAE? - LOGGER.error(e); - return Strings.EMPTY; - } - } - - private static LogEvent convertMutableToLog4jEvent(final LogEvent event) { - return event instanceof Log4jLogEvent ? event : Log4jLogEvent.createMemento(event); - } - - protected Object wrapLogEvent(final LogEvent event) { - if (additionalFields.length > 0) { - // Construct map for serialization - note that we are intentionally using original LogEvent - final Map<String, String> additionalFieldsMap = resolveAdditionalFields(event); - // This class combines LogEvent with AdditionalFields during serialization - return new LogEventWithAdditionalFields(event, additionalFieldsMap); - } else if (event instanceof Message) { - // If the LogEvent implements the Messagee interface Jackson will not treat is as a LogEvent. - return new ReadOnlyLogEventWrapper(event); - } else { - // No additional fields, return original object - return event; - } - } - - private Map<String, String> resolveAdditionalFields(final LogEvent logEvent) { - // Note: LinkedHashMap retains order - final Map<String, String> additionalFieldsMap = new LinkedHashMap<>(additionalFields.length); - final StrSubstitutor strSubstitutor = configuration.getStrSubstitutor(); - - // Go over each field - for (final ResolvableKeyValuePair pair : additionalFields) { - if (pair.valueNeedsLookup) { - // Resolve value - additionalFieldsMap.put(pair.key, strSubstitutor.replace(logEvent, pair.value)); - } else { - // Plain text value - additionalFieldsMap.put(pair.key, pair.value); - } - } - - return additionalFieldsMap; - } - - public void toSerializable(final LogEvent event, final Writer writer) - throws JsonGenerationException, JsonMappingException, IOException { - objectWriter.writeValue(writer, wrapLogEvent(convertMutableToLog4jEvent(event))); - writer.write(eol); - if (includeNullDelimiter) { - writer.write('\0'); - } - markEvent(); - } - - @JsonRootName(XmlConstants.ELT_EVENT) - public static class LogEventWithAdditionalFields { - - private final Object logEvent; - private final Map<String, String> additionalFields; - - public LogEventWithAdditionalFields(final Object logEvent, final Map<String, String> additionalFields) { - this.logEvent = logEvent; - this.additionalFields = additionalFields; - } - - @JsonUnwrapped - public Object getLogEvent() { - return logEvent; - } - - @JsonAnyGetter - @SuppressWarnings("unused") - public Map<String, String> getAdditionalFields() { - return additionalFields; - } - } - - protected static class ResolvableKeyValuePair { - - /** - * The empty array. - */ - static final ResolvableKeyValuePair[] EMPTY_ARRAY = {}; - - final String key; - final String value; - final boolean valueNeedsLookup; - - ResolvableKeyValuePair(final KeyValuePair pair) { - this.key = pair.getKey(); - this.value = pair.getValue(); - this.valueNeedsLookup = AbstractJacksonLayoutCopy.valueNeedsLookup(this.value); - } - } - - private static class ReadOnlyLogEventWrapper implements LogEvent { - - @JsonIgnore - private final LogEvent event; - - public ReadOnlyLogEventWrapper(LogEvent event) { - this.event = event; - } - - @Override - public LogEvent toImmutable() { - return event.toImmutable(); - } - - @Override - public Map<String, String> getContextMap() { - return event.getContextMap(); - } - - @Override - public ReadOnlyStringMap getContextData() { - return event.getContextData(); - } - - @Override - public ThreadContext.ContextStack getContextStack() { - return event.getContextStack(); - } - - @Override - public String getLoggerFqcn() { - return event.getLoggerFqcn(); - } - - @Override - public Level getLevel() { - return event.getLevel(); - } - - @Override - public String getLoggerName() { - return event.getLoggerName(); - } - - @Override - public Marker getMarker() { - return event.getMarker(); - } - - @Override - public Message getMessage() { - return event.getMessage(); - } - - @Override - public long getTimeMillis() { - return event.getTimeMillis(); - } - - @Override - public Instant getInstant() { - return event.getInstant(); - } - - @Override - public StackTraceElement getSource() { - return event.getSource(); - } - - @Override - public String getThreadName() { - return event.getThreadName(); - } - - @Override - public long getThreadId() { - return event.getThreadId(); - } - - @Override - public int getThreadPriority() { - return event.getThreadPriority(); - } - - @Override - public Throwable getThrown() { - return event.getThrown(); - } - - @Override - public ThrowableProxy getThrownProxy() { - return event.getThrownProxy(); - } - - @Override - public boolean isEndOfBatch() { - return event.isEndOfBatch(); - } - - @Override - public boolean isIncludeLocation() { - return event.isIncludeLocation(); - } - - @Override - public void setEndOfBatch(boolean endOfBatch) { - - } - - @Override - public void setIncludeLocation(boolean locationRequired) { - - } - - @Override - public long getNanoTime() { - return event.getNanoTime(); - } - } -} \ No newline at end of file diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/BufferManager.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/BufferManager.java new file mode 100644 index 000000000..d81d1fd31 --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/BufferManager.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal; + +/** + * Interface for logging managers that support buffer operations. + * This extends the logging framework capabilities with buffer-specific functionality. + */ +public interface BufferManager { + /** + * Flushes the log buffer for the current Lambda execution. + * This method will flush any buffered logs to the target appender. + */ + void flushBuffer(); + + /** + * Clears the log buffer for the current Lambda execution. + * This method will discard any buffered logs without outputting them. + */ + void clearBuffer(); +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefaultLoggingManager.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefaultLoggingManager.java new file mode 100644 index 000000000..ed2c14c38 --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefaultLoggingManager.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal; + +import org.slf4j.Logger; +import org.slf4j.event.Level; + +/** + * When no LoggingManager is found, setting a default one with no action on logging implementation + * Powertools cannot change the log level based on the environment variable, will use the logger configuration + */ +public class DefaultLoggingManager implements LoggingManager { + + @Override + public void setLogLevel(Level logLevel) { + // do nothing + } + + @Override + public Level getLogLevel(Logger logger) { + return Level.ERROR; + } +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/JacksonFactoryCopy.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/JacksonFactoryCopy.java deleted file mode 100644 index 41247cfdb..000000000 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/JacksonFactoryCopy.java +++ /dev/null @@ -1,117 +0,0 @@ -package software.amazon.lambda.powertools.logging.internal; - -import com.fasterxml.jackson.core.PrettyPrinter; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import com.fasterxml.jackson.core.util.MinimalPrettyPrinter; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.jackson.JsonConstants; -import org.apache.logging.log4j.core.jackson.Log4jJsonObjectMapper; - -import java.util.HashSet; -import java.util.Set; - -@Deprecated -abstract class JacksonFactoryCopy { - - static class JSON extends JacksonFactoryCopy { - - private final boolean encodeThreadContextAsList; - private final boolean includeStacktrace; - private final boolean stacktraceAsString; - private final boolean objectMessageAsJsonObject; - - public JSON(final boolean encodeThreadContextAsList, final boolean includeStacktrace, final boolean stacktraceAsString, final boolean objectMessageAsJsonObject) { - this.encodeThreadContextAsList = encodeThreadContextAsList; - this.includeStacktrace = includeStacktrace; - this.stacktraceAsString = stacktraceAsString; - this.objectMessageAsJsonObject = objectMessageAsJsonObject; - } - - @Override - protected String getPropertNameForContextMap() { - return JsonConstants.ELT_CONTEXT_MAP; - } - - @Override - protected String getPropertyNameForTimeMillis() { - return JsonConstants.ELT_TIME_MILLIS; - } - - @Override - protected String getPropertyNameForInstant() { - return JsonConstants.ELT_INSTANT; - } - - @Override - protected String getPropertNameForSource() { - return JsonConstants.ELT_SOURCE; - } - - @Override - protected String getPropertNameForNanoTime() { - return JsonConstants.ELT_NANO_TIME; - } - - @Override - protected PrettyPrinter newCompactPrinter() { - return new MinimalPrettyPrinter(); - } - - @Override - protected ObjectMapper newObjectMapper() { - return new Log4jJsonObjectMapper(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, objectMessageAsJsonObject); - } - - @Override - protected PrettyPrinter newPrettyPrinter() { - return new DefaultPrettyPrinter(); - } - - } - - abstract protected String getPropertyNameForTimeMillis(); - - abstract protected String getPropertyNameForInstant(); - - abstract protected String getPropertNameForContextMap(); - - abstract protected String getPropertNameForSource(); - - abstract protected String getPropertNameForNanoTime(); - - abstract protected PrettyPrinter newCompactPrinter(); - - abstract protected ObjectMapper newObjectMapper(); - - abstract protected PrettyPrinter newPrettyPrinter(); - - ObjectWriter newWriter(final boolean locationInfo, final boolean properties, final boolean compact) { - return newWriter(locationInfo, properties, compact, false); - } - - ObjectWriter newWriter(final boolean locationInfo, final boolean properties, final boolean compact, - final boolean includeMillis) { - final SimpleFilterProvider filters = new SimpleFilterProvider(); - final Set<String> except = new HashSet<>(3); - if (!locationInfo) { - except.add(this.getPropertNameForSource()); - } - if (!properties) { - except.add(this.getPropertNameForContextMap()); - } - if (includeMillis) { - except.add(getPropertyNameForInstant()); - } else { - except.add(getPropertyNameForTimeMillis()); - } - except.add(this.getPropertNameForNanoTime()); - filters.addFilter(Log4jLogEvent.class.getName(), SimpleBeanPropertyFilter.serializeAllExcept(except)); - final ObjectWriter writer = this.newObjectMapper().writer(compact ? this.newCompactPrinter() : this.newPrettyPrinter()); - return writer.with(filters); - } - -} \ No newline at end of file diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonSerializer.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonSerializer.java new file mode 100644 index 000000000..c69789519 --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonSerializer.java @@ -0,0 +1,604 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal; + +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.BooleanNode; +import com.fasterxml.jackson.databind.node.MissingNode; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.NumericNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.POJONode; +import com.fasterxml.jackson.databind.node.TextNode; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +/** + * A simple JSON serializer. + * Used internally for json serialization, not to be used externally. + * We do not use Jackson as we need to serialize each fields of the log event individually. + * Mainly used by logback as log4j is using its own JsonWriter + */ +public class JsonSerializer implements AutoCloseable { + + private final StringBuilder builder; + + public JsonSerializer(StringBuilder builder) { + super(); + if (builder == null) { + throw new IllegalArgumentException("StringBuilder cannot be null"); + } + this.builder = builder; + } + + public void writeStartArray() { + builder.append('['); + } + + public void writeEndArray() { + builder.append(']'); + } + + public void writeStartObject() { + builder.append('{'); + } + + public void writeEndObject() { + builder.append('}'); + } + + public void writeSeparator() { + writeRaw(','); + } + + public void writeFieldName(String name) { + Objects.requireNonNull(name, "field name must not be null"); + writeString(name); + writeRaw(':'); + } + + public void writeString(String text) { + if (text == null) { + writeNull(); + } else { + // Escape special characters to avoid breaking JSON format + String escaped = text.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t") + .replace("\b", "\\b") + .replace("\f", "\\f"); + builder.append("\"").append(escaped).append("\""); + } + } + + public void writeRaw(String text) { + builder.append(text); + } + + public void writeRaw(char c) { + builder.append(c); + } + + public void writeNumber(short v) { + builder.append(v); + } + + public void writeNumber(int v) { + builder.append(v); + } + + public void writeNumber(long v) { + builder.append(v); + } + + public void writeNumber(BigInteger v) { + builder.append(v); + } + + public void writeNumber(double v) { + builder.append(v); + } + + public void writeNumber(float v) { + builder.append(v); + } + + public void writeNumber(BigDecimal v) { + builder.append(v.toPlainString()); + } + + public void writeBoolean(boolean state) { + builder.append(state); + } + + public void writeArray(final char[] items) { + if (items == null) { + writeNull(); + } else { + writeStartArray(); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + writeSeparator(); + } + builder.append('\''); + builder.append(items[itemIndex]); + builder.append('\''); + } + writeEndArray(); + } + } + + public void writeArray(final boolean[] items) { + if (items == null) { + writeNull(); + } else { + writeStartArray(); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + writeSeparator(); + } + final boolean item = items[itemIndex]; + writeBoolean(item); + } + writeEndArray(); + } + } + + public void writeArray(final byte[] items) { + if (items == null) { + writeNull(); + } else { + writeStartArray(); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + writeSeparator(); + } + final byte item = items[itemIndex]; + writeNumber(item); + } + writeEndArray(); + } + } + + public void writeArray(final short[] items) { + if (items == null) { + writeNull(); + } else { + writeStartArray(); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + writeSeparator(); + } + final short item = items[itemIndex]; + writeNumber(item); + } + writeEndArray(); + } + } + + public void writeArray(final int[] items) { + if (items == null) { + writeNull(); + } else { + writeStartArray(); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + writeSeparator(); + } + final int item = items[itemIndex]; + writeNumber(item); + } + writeEndArray(); + } + } + + public void writeArray(final long[] items) { + if (items == null) { + writeNull(); + } else { + writeStartArray(); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + writeSeparator(); + } + final long item = items[itemIndex]; + writeNumber(item); + } + writeEndArray(); + } + } + + public void writeArray(final float[] items) { + if (items == null) { + writeNull(); + } else { + writeStartArray(); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + writeSeparator(); + } + final float item = items[itemIndex]; + writeNumber(item); + } + writeEndArray(); + } + } + + public void writeArray(final double[] items) { + if (items == null) { + writeNull(); + } else { + writeStartArray(); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + writeSeparator(); + } + final double item = items[itemIndex]; + writeNumber(item); + } + writeEndArray(); + } + } + + public void writeArray(final Object[] items) { + if (items == null) { + writeNull(); + } else { + writeStartArray(); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + writeSeparator(); + } + final Object item = items[itemIndex]; + writeObject(item); + } + writeEndArray(); + } + } + + public void writeNull() { + builder.append("null"); + } + + public void writeArray(final List<?> items) { + if (items == null) { + writeNull(); + } else { + writeStartArray(); + for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) { + if (itemIndex > 0) { + writeSeparator(); + } + final Object item = items.get(itemIndex); + writeObject(item); + } + writeEndArray(); + } + } + + public void writeArray(final Collection<?> items) { + if (items == null) { + writeNull(); + } else { + writeStartArray(); + Iterator<?> iterator = items.iterator(); + while (iterator.hasNext()) { + writeObject(iterator.next()); + if (iterator.hasNext()) { + writeSeparator(); + } + } + writeEndArray(); + } + } + + public void writeMap(final Map<?, ?> map) { + if (map == null) { + writeNull(); + } else { + writeStartObject(); + for (Iterator<? extends Map.Entry<?, ?>> entries = map.entrySet().iterator(); entries.hasNext();) { + Map.Entry<?, ?> entry = entries.next(); + writeObjectField(String.valueOf(entry.getKey()), entry.getValue()); + if (entries.hasNext()) { + builder.append(','); + } + } + writeEndObject(); + } + } + + public void writeObject(Object value) { + + // null + if (value == null) { + writeNull(); + } + + else if (value instanceof String) { + writeString((String) value); + } + + // number & boolean + else if (value instanceof Number) { + Number n = (Number) value; + if (n instanceof Integer) { + writeNumber(n.intValue()); + } else if (n instanceof Long) { + writeNumber(n.longValue()); + } else if (n instanceof Double) { + writeNumber(n.doubleValue()); + } else if (n instanceof Float) { + writeNumber(n.floatValue()); + } else if (n instanceof Short) { + writeNumber(n.shortValue()); + } else if (n instanceof Byte) { + writeNumber(n.byteValue()); + } else if (n instanceof BigInteger) { + writeNumber((BigInteger) n); + } else if (n instanceof BigDecimal) { + writeNumber((BigDecimal) n); + } else if (n instanceof AtomicInteger) { + writeNumber(((AtomicInteger) n).get()); + } else if (n instanceof AtomicLong) { + writeNumber(((AtomicLong) n).get()); + } + } else if (value instanceof Boolean) { + writeBoolean((Boolean) value); + } else if (value instanceof AtomicBoolean) { + writeBoolean(((AtomicBoolean) value).get()); + } + + // list & collection + else if (value instanceof List) { + final List<?> list = (List<?>) value; + writeArray(list); + } else if (value instanceof Collection) { + final Collection<?> collection = (Collection<?>) value; + writeArray(collection); + } + + // map + else if (value instanceof Map) { + final Map<?, ?> map = (Map<?, ?>) value; + writeMap(map); + } + + // arrays + else if (value instanceof char[]) { + final char[] charValues = (char[]) value; + writeArray(charValues); + } else if (value instanceof boolean[]) { + final boolean[] booleanValues = (boolean[]) value; + writeArray(booleanValues); + } else if (value instanceof byte[]) { + final byte[] byteValues = (byte[]) value; + writeArray(byteValues); + } else if (value instanceof short[]) { + final short[] shortValues = (short[]) value; + writeArray(shortValues); + } else if (value instanceof int[]) { + final int[] intValues = (int[]) value; + writeArray(intValues); + } else if (value instanceof long[]) { + final long[] longValues = (long[]) value; + writeArray(longValues); + } else if (value instanceof float[]) { + final float[] floatValues = (float[]) value; + writeArray(floatValues); + } else if (value instanceof double[]) { + final double[] doubleValues = (double[]) value; + writeArray(doubleValues); + } else if (value instanceof Object[]) { + final Object[] values = (Object[]) value; + writeArray(values); + } + + else if (value instanceof JsonNode) { + JsonNode node = (JsonNode) value; + + switch (node.getNodeType()) { + case NULL: + case MISSING: + writeNull(); + break; + + case STRING: + writeString(node.asText()); + break; + + case BOOLEAN: + writeBoolean(node.asBoolean()); + break; + + case NUMBER: + if (node.isInt()) { + writeNumber(node.intValue()); + break; + } + if (node.isLong()) { + writeNumber(node.longValue()); + break; + } + if (node.isShort()) { + writeNumber(node.shortValue()); + break; + } + if (node.isDouble()) { + writeNumber(node.doubleValue()); + break; + } + if (node.isFloat()) { + writeNumber(node.floatValue()); + break; + } + if (node.isBigDecimal()) { + writeNumber(node.decimalValue()); + break; + } + if (node.isBigInteger()) { + writeNumber(node.bigIntegerValue()); + break; + } + break; + case OBJECT: + case POJO: + writeStartObject(); + for (Iterator<Map.Entry<String, JsonNode>> entries = node.fields(); entries.hasNext();) { + Map.Entry<String, JsonNode> entry = entries.next(); + writeObjectField(entry.getKey(), entry.getValue()); + if (entries.hasNext()) { + builder.append(','); + } + } + writeEndObject(); + return; + + case ARRAY: + ArrayNode arrayNode = (ArrayNode) node; + writeStartArray(); + for (Iterator<JsonNode> elements = arrayNode.elements(); elements.hasNext();) { + writeObject(elements.next()); + if (elements.hasNext()) { + builder.append(','); + } + } + writeEndArray(); + return; + + default: + break; + } + } else { + try { + // default: try to write object as JSON + writeRaw(JsonConfig.get().getObjectMapper().writeValueAsString(value)); + } catch (Exception e) { + // last chance: toString + writeString(value.toString()); + } + } + } + + public void writeObjectField(String key, Object value) { + writeFieldName(key); + writeObject(value); + } + + public void writeBooleanField(String key, boolean value) { + writeFieldName(key); + writeBoolean(value); + } + + public void writeNullField(String key) { + writeFieldName(key); + writeNull(); + } + + public void writeNumberField(String key, int value) { + writeFieldName(key); + writeNumber(value); + } + + public void writeNumberField(String key, float value) { + writeFieldName(key); + writeNumber(value); + } + + public void writeNumberField(String key, short value) { + writeFieldName(key); + writeNumber(value); + } + + public void writeNumberField(String key, long value) { + writeFieldName(key); + writeNumber(value); + } + + public void writeNumberField(String key, BigInteger value) { + writeFieldName(key); + writeNumber(value); + } + + public void writeNumberField(String key, double value) { + writeFieldName(key); + writeNumber(value); + } + + public void writeNumberField(String key, BigDecimal value) { + writeFieldName(key); + writeNumber(value); + } + + public void writeStringField(String key, String value) { + writeFieldName(key); + writeString(value); + } + + public void writeTree(TreeNode rootNode) { + if (rootNode == null) { + writeNull(); + } else if (rootNode instanceof TextNode) { + writeString(((TextNode) rootNode).asText()); + } else if (rootNode instanceof BooleanNode) { + writeBoolean(((BooleanNode) rootNode).asBoolean()); + } else if (rootNode instanceof NumericNode) { + NumericNode numericNode = (NumericNode) rootNode; + if (numericNode.isInt()) { + writeNumber(numericNode.intValue()); + } else if (numericNode.isLong()) { + writeNumber(numericNode.longValue()); + } else if (numericNode.isShort()) { + writeNumber(numericNode.shortValue()); + } else if (numericNode.isDouble()) { + writeNumber(numericNode.doubleValue()); + } else if (numericNode.isFloat()) { + writeNumber(numericNode.floatValue()); + } else if (numericNode.isBigDecimal()) { + writeNumber(numericNode.decimalValue()); + } else if (numericNode.isBigInteger()) { + writeNumber(numericNode.bigIntegerValue()); + } + } else if (rootNode instanceof ArrayNode) { + ArrayNode arrayNode = (ArrayNode) rootNode; + writeObject(arrayNode); + } else if (rootNode instanceof ObjectNode) { + ObjectNode objectNode = (ObjectNode) rootNode; + writeObject(objectNode); + } else if (rootNode instanceof NullNode || rootNode instanceof MissingNode) { + writeNull(); + } else if (rootNode instanceof POJONode) { + writeObject(((POJONode) rootNode).getPojo()); + } + } + + @Override + public void close() { + // nothing to do + } +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/KeyBuffer.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/KeyBuffer.java new file mode 100644 index 000000000..3510a544d --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/KeyBuffer.java @@ -0,0 +1,95 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * Thread-safe buffer that stores events by key with size-based eviction. + * + * <p>Maintains separate queues per key. When buffer size exceeds maxBytes, + * oldest events are evicted FIFO. Events larger than maxBytes are rejected. + * + * @param <K> key type for buffering + * @param <T> event type to buffer + */ +public class KeyBuffer<K, T> { + + private final Map<K, Deque<T>> keyBufferCache = new ConcurrentHashMap<>(); + private final Map<K, Boolean> overflowTriggered = new ConcurrentHashMap<>(); + private final int maxBytes; + private final Function<T, Integer> sizeCalculator; + private final Runnable overflowWarningLogger; + + @SuppressWarnings("java:S106") // Using System.err to avoid circular dependency with logging implementation + public KeyBuffer(int maxBytes, Function<T, Integer> sizeCalculator) { + this(maxBytes, sizeCalculator, () -> System.err.println("WARN [" + KeyBuffer.class.getSimpleName() + + "] - Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer.")); + } + + public KeyBuffer(int maxBytes, Function<T, Integer> sizeCalculator, Runnable overflowWarningLogger) { + this.maxBytes = maxBytes; + this.sizeCalculator = sizeCalculator; + this.overflowWarningLogger = overflowWarningLogger; + } + + public void add(K key, T event) { + int eventSize = sizeCalculator.apply(event); + // Immediately reject events larger than the whole buffer-size to avoid evicting all elements. + if (eventSize > maxBytes) { + overflowTriggered.put(key, true); + return; + } + + Deque<T> buffer = keyBufferCache.computeIfAbsent(key, k -> new ArrayDeque<>()); + synchronized (buffer) { + buffer.add(event); + while (getBufferSize(buffer) > maxBytes && !buffer.isEmpty()) { + overflowTriggered.put(key, true); + buffer.removeFirst(); + } + } + } + + public Deque<T> removeAll(K key) { + logOverflowWarningIfNeeded(key); + Deque<T> buffer = keyBufferCache.remove(key); + if (buffer != null) { + synchronized (buffer) { + return new ArrayDeque<>(buffer); + } + } + return buffer; + } + + public void clear(K key) { + keyBufferCache.remove(key); + overflowTriggered.remove(key); + } + + private void logOverflowWarningIfNeeded(K key) { + if (Boolean.TRUE.equals(overflowTriggered.remove(key))) { + overflowWarningLogger.run(); + } + } + + private int getBufferSize(Deque<T> buffer) { + return buffer.stream().mapToInt(sizeCalculator::apply).sum(); + } +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonLayout.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonLayout.java deleted file mode 100644 index 578937231..000000000 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonLayout.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. - * 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 - * http://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. - * - */ -package software.amazon.lambda.powertools.logging.internal; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonRootName; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.DefaultConfiguration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.jackson.XmlConstants; -import org.apache.logging.log4j.core.layout.PatternLayout; -import org.apache.logging.log4j.core.util.KeyValuePair; -import org.apache.logging.log4j.util.Strings; - -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; - -import static java.time.Instant.ofEpochMilli; -import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME; - -/*** - * Note: The LambdaJsonLayout should be considered to be deprecated. Please use JsonTemplateLayout instead. - */ -@Deprecated -@Plugin(name = "LambdaJsonLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) -public final class LambdaJsonLayout extends AbstractJacksonLayoutCopy { - private static final String DEFAULT_FOOTER = "]"; - - private static final String DEFAULT_HEADER = "["; - - static final String CONTENT_TYPE = "application/json"; - - public static class Builder<B extends Builder<B>> extends AbstractJacksonLayoutCopy.Builder<B> - implements org.apache.logging.log4j.core.util.Builder<LambdaJsonLayout> { - - @PluginBuilderAttribute - private boolean propertiesAsList; - - @PluginBuilderAttribute - private boolean objectMessageAsJsonObject; - - public Builder() { - super(); - setCharset(StandardCharsets.UTF_8); - } - - @Override - public LambdaJsonLayout build() { - final boolean encodeThreadContextAsList = isProperties() && propertiesAsList; - final String headerPattern = toStringOrNull(getHeader()); - final String footerPattern = toStringOrNull(getFooter()); - return new LambdaJsonLayout(getConfiguration(), isLocationInfo(), isProperties(), encodeThreadContextAsList, - isComplete(), isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(), - isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(), - getAdditionalFields(), getObjectMessageAsJsonObject()); - } - - public boolean isPropertiesAsList() { - return propertiesAsList; - } - - public B setPropertiesAsList(final boolean propertiesAsList) { - this.propertiesAsList = propertiesAsList; - return asBuilder(); - } - - public boolean getObjectMessageAsJsonObject() { - return objectMessageAsJsonObject; - } - - public B setObjectMessageAsJsonObject(final boolean objectMessageAsJsonObject) { - this.objectMessageAsJsonObject = objectMessageAsJsonObject; - return asBuilder(); - } - } - - private LambdaJsonLayout(final Configuration config, final boolean locationInfo, final boolean properties, - final boolean encodeThreadContextAsList, - final boolean complete, final boolean compact, final boolean eventEol, - final String headerPattern, final String footerPattern, final Charset charset, - final boolean includeStacktrace, final boolean stacktraceAsString, - final boolean includeNullDelimiter, - final KeyValuePair[] additionalFields, final boolean objectMessageAsJsonObject) { - super(config, new JacksonFactoryCopy.JSON(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, objectMessageAsJsonObject).newWriter( - locationInfo, properties, compact), - charset, compact, complete, eventEol, - null, - PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(), - PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(), - includeNullDelimiter, - additionalFields); - } - - /** - * Returns appropriate JSON header. - * - * @return a byte array containing the header, opening the JSON array. - */ - @Override - public byte[] getHeader() { - if (!this.complete) { - return null; - } - final StringBuilder buf = new StringBuilder(); - final String str = serializeToString(getHeaderSerializer()); - if (str != null) { - buf.append(str); - } - buf.append(this.eol); - return getBytes(buf.toString()); - } - - /** - * Returns appropriate JSON footer. - * - * @return a byte array containing the footer, closing the JSON array. - */ - @Override - public byte[] getFooter() { - if (!this.complete) { - return null; - } - final StringBuilder buf = new StringBuilder(); - buf.append(this.eol); - final String str = serializeToString(getFooterSerializer()); - if (str != null) { - buf.append(str); - } - buf.append(this.eol); - return getBytes(buf.toString()); - } - - @Override - public Map<String, String> getContentFormat() { - final Map<String, String> result = new HashMap<>(); - result.put("version", "2.0"); - return result; - } - - /** - * @return The content type. - */ - @Override - public String getContentType() { - return CONTENT_TYPE + "; charset=" + this.getCharset(); - } - - @PluginBuilderFactory - public static <B extends Builder<B>> B newBuilder() { - return new Builder<B>().asBuilder(); - } - - /** - * Creates a JSON Layout using the default settings. Useful for testing. - * - * @return A JSON Layout. - */ - public static LambdaJsonLayout createDefaultLayout() { - return new LambdaJsonLayout(new DefaultConfiguration(), false, false, false, false, false, false, - DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false, null, false); - } - - @Override - public Object wrapLogEvent(final LogEvent event) { - Map<String, Object> additionalFieldsMap = resolveAdditionalFields(event); - // This class combines LogEvent with AdditionalFields during serialization - return new LogEventWithAdditionalFields(event, additionalFieldsMap); - } - - @Override - public void toSerializable(final LogEvent event, final Writer writer) throws IOException { - if (complete && eventCount > 0) { - writer.append(", "); - } - super.toSerializable(event, writer); - } - - private Map<String, Object> resolveAdditionalFields(LogEvent logEvent) { - // Note: LinkedHashMap retains order - final Map<String, Object> additionalFieldsMap = new LinkedHashMap<>(additionalFields.length); - - // Go over MDC - logEvent.getContextData().forEach((key, value) -> { - if (Strings.isNotBlank(key) && value != null) { - additionalFieldsMap.put(key, value); - } - }); - - return additionalFieldsMap; - } - - @JsonRootName(XmlConstants.ELT_EVENT) - public static class LogEventWithAdditionalFields { - - private final LogEvent logEvent; - private final Map<String, Object> additionalFields; - - public LogEventWithAdditionalFields(LogEvent logEvent, Map<String, Object> additionalFields) { - this.logEvent = logEvent; - this.additionalFields = additionalFields; - } - - @JsonUnwrapped - public Object getLogEvent() { - return logEvent; - } - - @JsonAnyGetter - public Map<String, Object> getAdditionalFields() { - return additionalFields; - } - - @JsonGetter("timestamp") - public String getTimestamp() { - return ISO_ZONED_DATE_TIME.format(ZonedDateTime.from(ofEpochMilli(logEvent.getTimeMillis()).atZone(ZoneId.systemDefault()))); - } - } -} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index 34f3bf312..272c4bff9 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,261 +11,234 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.logging.internal; +import static java.nio.charset.StandardCharsets.UTF_8; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.extractContext; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.isHandlerMethod; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.placedOnRequestHandler; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.placedOnStreamHandler; +import static software.amazon.lambda.powertools.logging.argument.StructuredArguments.entry; +import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_LOG_ERROR; +import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_LOG_EVENT; +import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_LOG_RESPONSE; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; import java.io.OutputStreamWriter; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import com.amazonaws.services.lambda.runtime.Context; -import com.fasterxml.jackson.core.JsonPointer; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configurator; -import org.apache.logging.log4j.core.util.IOUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.DeclarePrecedence; import org.aspectj.lang.annotation.Pointcut; -import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.logging.LoggingUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MarkerFactory; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Optional.empty; -import static java.util.Optional.ofNullable; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.coldStartDone; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.extractContext; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.getXrayTraceId; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isColdStart; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isHandlerMethod; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnRequestHandler; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnStreamHandler; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.serviceName; -import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKey; -import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKeys; -import static software.amazon.lambda.powertools.logging.LoggingUtils.objectMapper; +import com.amazonaws.services.lambda.runtime.Context; + +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.PowertoolsLogging; +import software.amazon.lambda.powertools.utilities.JsonConfig; @Aspect -@DeclarePrecedence("*, SqsLargeMessageAspect, LambdaLoggingAspect") +@DeclarePrecedence("*, software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect") public final class LambdaLoggingAspect { - private static final Logger LOG = LogManager.getLogger(LambdaLoggingAspect.class); - private static final Random SAMPLER = new Random(); - - private static final String LOG_LEVEL = System.getenv("POWERTOOLS_LOG_LEVEL"); - private static final String SAMPLING_RATE = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); - - private static Level LEVEL_AT_INITIALISATION; + private static final Logger LOG = LoggerFactory.getLogger(LambdaLoggingAspect.class); + private static final LoggingManager LOGGING_MANAGER; static { - if (null != LOG_LEVEL) { - resetLogLevels(Level.getLevel(LOG_LEVEL)); - } - - LEVEL_AT_INITIALISATION = LOG.getLevel(); + LOGGING_MANAGER = LoggingManagerRegistry.getLoggingManager(); } - @SuppressWarnings({"EmptyMethod"}) + @SuppressWarnings({ "EmptyMethod" }) @Pointcut("@annotation(logging)") public void callAt(Logging logging) { + // Pointcut method - body intentionally empty } + /** + * Main method of the aspect + */ @Around(value = "callAt(logging) && execution(@Logging * *.*(..))", argNames = "pjp,logging") public Object around(ProceedingJoinPoint pjp, - Logging logging) throws Throwable { - Object[] proceedArgs = pjp.getArgs(); - - setLogLevelBasedOnSamplingRate(pjp, logging); + Logging logging) throws Throwable { - Context extractedContext = extractContext(pjp); - - if(null != extractedContext) { - appendKeys(DefaultLambdaFields.values(extractedContext)); - appendKey("coldStart", isColdStart() ? "true" : "false"); - appendKey("service", serviceName()); - } + boolean isOnRequestHandler = placedOnRequestHandler(pjp); + boolean isOnRequestStreamHandler = placedOnStreamHandler(pjp); - getXrayTraceId().ifPresent(xRayTraceId -> appendKey("xray_trace_id", xRayTraceId)); + // Initialize logging using PowertoolsLogging + Context context = extractContext(pjp); + Object[] proceedArgs = pjp.getArgs(); - if (logging.logEvent()) { - proceedArgs = logEvent(pjp); + if (isHandlerMethod(pjp) && context != null) { + Object event = extractEventForCorrelationId(logging, isOnRequestHandler, isOnRequestStreamHandler, + proceedArgs); + PowertoolsLogging.initializeLogging(context, logging.samplingRate(), + logging.correlationIdPath().isEmpty() ? null : logging.correlationIdPath(), event); } - if (!logging.correlationIdPath().isEmpty()) { - proceedArgs = captureCorrelationId(logging.correlationIdPath(), pjp); + logEvent(pjp, logging, isOnRequestHandler, isOnRequestStreamHandler, proceedArgs); + + @SuppressWarnings("PMD.CloseResource") // Lambda-owned stream, not ours to close + OutputStream backupOutputStream = null; + if (isOnRequestStreamHandler) { + // To log the result of a RequestStreamHandler (OutputStream), we need to do the following: + // 1. backup a reference to the OutputStream provided by Lambda + // 2. create a temporary OutputStream and pass it to the handler method + // 3. retrieve this temporary stream to log it (if enabled) + // 4. write it back to the OutputStream provided by Lambda + backupOutputStream = prepareOutputStreamForLogging(logging, proceedArgs); } - Object proceed = pjp.proceed(proceedArgs); - - if(logging.clearState()) { - ThreadContext.clearMap(); + Object lambdaFunctionResponse; + try { + lambdaFunctionResponse = pjp.proceed(proceedArgs); + } catch (Throwable t) { // NOPMD - AspectJ proceed() throws Throwable + handleException(pjp, logging, t); + throw t; + } finally { + PowertoolsLogging.clearState(logging.clearState()); } - coldStartDone(); - return proceed; - } + logResponse(pjp, logging, lambdaFunctionResponse, isOnRequestHandler, isOnRequestStreamHandler, + backupOutputStream, proceedArgs); - private static void resetLogLevels(Level logLevel) { - LoggerContext ctx = (LoggerContext) LogManager.getContext(false); - Configurator.setAllLevels(LogManager.getRootLogger().getName(), logLevel); - ctx.updateLoggers(); + return lambdaFunctionResponse; } - private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, - final Logging logging) { - double samplingRate = samplingRate(logging); - - if (isHandlerMethod(pjp)) { - - if (samplingRate < 0 || samplingRate > 1) { - LOG.debug("Skipping sampling rate configuration because of invalid value. Sampling rate: {}", samplingRate); - return; - } - - appendKey("samplingRate", String.valueOf(samplingRate)); + private void logEvent(ProceedingJoinPoint pjp, Logging logging, + boolean isOnRequestHandler, boolean isOnRequestStreamHandler, Object[] proceedArgs) { - if (samplingRate == 0) { - return; + if (logging.logEvent() || POWERTOOLS_LOG_EVENT) { + if (isOnRequestHandler) { + logRequestHandlerEvent(pjp, proceedArgs[0]); + } else if (isOnRequestStreamHandler) { + logRequestStreamHandlerEvent(pjp, proceedArgs); } + } + } - float sample = SAMPLER.nextFloat(); - - if (samplingRate > sample) { - resetLogLevels(Level.DEBUG); - - LOG.debug("Changed log level to DEBUG based on Sampling configuration. " + - "Sampling Rate: {}, Sampler Value: {}.", samplingRate, sample); - } else if (LEVEL_AT_INITIALISATION != LOG.getLevel()) { - resetLogLevels(LEVEL_AT_INITIALISATION); - } + @SuppressWarnings("java:S3457") + private void logRequestHandlerEvent(final ProceedingJoinPoint pjp, final Object event) { + Logger log = logger(pjp); + if (log.isInfoEnabled()) { + log.info("Handler Event", entry("event", event)); } } - private double samplingRate(final Logging logging) { - if (null != SAMPLING_RATE) { + @SuppressWarnings("java:S3457") + private void logRequestStreamHandlerEvent(final ProceedingJoinPoint pjp, Object[] args) { + Logger log = logger(pjp); + if (log.isInfoEnabled()) { try { - return Double.parseDouble(SAMPLING_RATE); - } catch (NumberFormatException e) { - LOG.debug("Skipping sampling rate on environment variable configuration because of invalid " + - "value. Sampling rate: {}", SAMPLING_RATE); + byte[] bytes = bytesFromInputStreamSafely((InputStream) args[0]); + args[0] = new ByteArrayInputStream(bytes); + // do not log asJson as it can be something else (String, XML...) + log.info("Handler Event", entry("event", new String(bytes, UTF_8))); + } catch (IOException e) { + LOG.warn("Failed to log event from supplied input stream.", e); } } - return logging.samplingRate(); } - private Object[] logEvent(final ProceedingJoinPoint pjp) { - Object[] args = pjp.getArgs(); - - if (isHandlerMethod(pjp)) { - if (placedOnRequestHandler(pjp)) { - Logger log = logger(pjp); - asJson(pjp, pjp.getArgs()[0]) - .ifPresent(log::info); - } - - if (placedOnStreamHandler(pjp)) { - args = logFromInputStream(pjp); - } + @SuppressWarnings("java:S3457") + private void logRequestHandlerResponse(final ProceedingJoinPoint pjp, final Object response) { + Logger log = logger(pjp); + if (log.isInfoEnabled()) { + log.info("Handler Response", entry("response", response)); } - - return args; } - private Object[] captureCorrelationId(final String correlationIdPath, - final ProceedingJoinPoint pjp) { - Object[] args = pjp.getArgs(); - if (isHandlerMethod(pjp)) { - if (placedOnRequestHandler(pjp)) { - Object arg = pjp.getArgs()[0]; - JsonNode jsonNode = objectMapper().valueToTree(arg); - - setCorrelationIdFromNode(correlationIdPath, pjp, jsonNode); - - return args; - } - - if (placedOnStreamHandler(pjp)) { - try { - byte[] bytes = bytesFromInputStreamSafely((InputStream) pjp.getArgs()[0]); - JsonNode jsonNode = objectMapper().readTree(bytes); - args[0] = new ByteArrayInputStream(bytes); - - setCorrelationIdFromNode(correlationIdPath, pjp, jsonNode); + @SuppressWarnings("java:S3457") + private void logRequestStreamHandlerResponse(final ProceedingJoinPoint pjp, final byte[] bytes) { + Logger log = logger(pjp); + if (log.isInfoEnabled()) { + // we do not log with asJson as it can be something else (String, XML, ...) + log.info("Handler Response", entry("response", new String(bytes, UTF_8))); + } + } - return args; - } catch (IOException e) { - Logger log = logger(pjp); - log.warn("Failed to capture correlation id on event from supplied input stream.", e); - } + private byte[] bytesFromInputStreamSafely(final InputStream inputStream) throws IOException { + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + InputStreamReader reader = new InputStreamReader(inputStream, UTF_8); + OutputStreamWriter writer = new OutputStreamWriter(out, UTF_8)) { + int n; + char[] buffer = new char[4096]; + while (-1 != (n = reader.read(buffer))) { + writer.write(buffer, 0, n); } + writer.flush(); + return out.toByteArray(); } - - return args; } - private void setCorrelationIdFromNode(String correlationIdPath, ProceedingJoinPoint pjp, JsonNode jsonNode) { - JsonNode node = jsonNode.at(JsonPointer.compile(correlationIdPath)); - - String asText = node.asText(); - if (null != asText && !asText.isEmpty()) { - LoggingUtils.setCorrelationId(asText); - } else { - logger(pjp).debug("Unable to extract any correlation id. Is your function expecting supported event type?"); + private OutputStream prepareOutputStreamForLogging(Logging logging, + Object[] proceedArgs) { + if (logging.logResponse() || POWERTOOLS_LOG_RESPONSE) { + OutputStream backupOutputStream = (OutputStream) proceedArgs[1]; + proceedArgs[1] = new ByteArrayOutputStream(); + return backupOutputStream; } + return null; } - private Object[] logFromInputStream(final ProceedingJoinPoint pjp) { - Object[] args = pjp.getArgs(); - - try { - byte[] bytes = bytesFromInputStreamSafely((InputStream) pjp.getArgs()[0]); - args[0] = new ByteArrayInputStream(bytes); - Logger log = logger(pjp); - - asJson(pjp, objectMapper().readValue(bytes, Map.class)) - .ifPresent(log::info); - - } catch (IOException e) { - Logger log = logger(pjp); - log.debug("Failed to log event from supplied input stream.", e); + private void handleException(ProceedingJoinPoint pjp, Logging logging, Throwable t) { + if (LOGGING_MANAGER instanceof BufferManager) { + if (logging.flushBufferOnUncaughtError()) { + ((BufferManager) LOGGING_MANAGER).flushBuffer(); + } else { + ((BufferManager) LOGGING_MANAGER).clearBuffer(); + } + } + if (logging.logError() || POWERTOOLS_LOG_ERROR) { + logger(pjp).error(MarkerFactory.getMarker("FATAL"), "Exception in Lambda Handler", t); } - - return args; } - private byte[] bytesFromInputStreamSafely(final InputStream inputStream) throws IOException { - try (ByteArrayOutputStream out = new ByteArrayOutputStream(); - InputStreamReader reader = new InputStreamReader(inputStream, UTF_8)) { - OutputStreamWriter writer = new OutputStreamWriter(out, UTF_8); - - IOUtils.copy(reader, writer); - writer.flush(); - return out.toByteArray(); + private void logResponse(ProceedingJoinPoint pjp, Logging logging, Object lambdaFunctionResponse, + boolean isOnRequestHandler, boolean isOnRequestStreamHandler, + OutputStream backupOutputStream, Object[] proceedArgs) throws IOException { + if (logging.logResponse() || POWERTOOLS_LOG_RESPONSE) { + if (isOnRequestHandler) { + logRequestHandlerResponse(pjp, lambdaFunctionResponse); + } else if (isOnRequestStreamHandler && backupOutputStream != null) { + byte[] bytes = ((ByteArrayOutputStream) proceedArgs[1]).toByteArray(); + logRequestStreamHandlerResponse(pjp, bytes); + backupOutputStream.write(bytes); + } } } - private Optional<String> asJson(final ProceedingJoinPoint pjp, - final Object target) { - try { - return ofNullable(objectMapper().writeValueAsString(target)); - } catch (JsonProcessingException e) { - logger(pjp).error("Failed logging event of type {}", target.getClass(), e); - return empty(); + private Object extractEventForCorrelationId(Logging logging, boolean isOnRequestHandler, + boolean isOnRequestStreamHandler, Object[] proceedArgs) { + if (logging.correlationIdPath().isEmpty()) { + return null; + } + + if (isOnRequestHandler && proceedArgs.length > 0) { + return proceedArgs[0]; + } else if (isOnRequestStreamHandler && proceedArgs.length > 0) { + try { + byte[] bytes = bytesFromInputStreamSafely((InputStream) proceedArgs[0]); + // Parse JSON string to Object for correlation ID extraction + Object event = JsonConfig.get().getObjectMapper().readTree(bytes); + proceedArgs[0] = new ByteArrayInputStream(bytes); // Restore stream + return event; + } catch (IOException e) { + LOG.warn("Failed to read event from input stream for correlation ID extraction.", e); + } } + return null; } private Logger logger(final ProceedingJoinPoint pjp) { - return LogManager.getLogger(pjp.getSignature().getDeclaringType()); + return LoggerFactory.getLogger(pjp.getSignature().getDeclaringType()); } } diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java new file mode 100644 index 000000000..511b12ace --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal; + +public final class LoggingConstants { + @SuppressWarnings({"java:S1104", "java:S1444", "java:S3008"}) + public static String LAMBDA_LOG_LEVEL = System.getenv("AWS_LAMBDA_LOG_LEVEL"); /* not final for test purpose */ + + @SuppressWarnings({"java:S1104", "java:S1444", "java:S3008"}) + public static String POWERTOOLS_LOG_LEVEL = System.getenv("POWERTOOLS_LOG_LEVEL"); /* not final for test purpose */ + + @SuppressWarnings({"java:S1104", "java:S1444", "java:S3008"}) + public static String POWERTOOLS_SAMPLING_RATE = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); /* not final for test purpose */ + + static boolean POWERTOOLS_LOG_EVENT = "true".equals(System.getenv("POWERTOOLS_LOGGER_LOG_EVENT")); /* not final for test purpose */ + + static boolean POWERTOOLS_LOG_RESPONSE = "true".equals(System.getenv("POWERTOOLS_LOGGER_LOG_RESPONSE")); /* not final for test purpose */ + + static boolean POWERTOOLS_LOG_ERROR = "true".equals(System.getenv("POWERTOOLS_LOGGER_LOG_ERROR")); /* not final for test purpose */ + + private LoggingConstants() { + // constants + } +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java new file mode 100644 index 000000000..51d05b25d --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal; + +import org.slf4j.Logger; +import org.slf4j.event.Level; + +/** + * Due to limitations of SLF4J, we need to rely on implementations for some operations: + * <ul> + * <li>Accessing to all loggers and change their Level</li> + * <li>Retrieving the log Level of a Logger</li> + * </ul> + * + * <p> + * This interface is used for these operations and implementations are provided in submodules and loaded thanks to a {@link java.util.ServiceLoader} + * (define a file named <code>software.amazon.lambda.powertools.logging.internal.LoggingManager</code> + * in <code>src/main/resources/META-INF/services</code> with the qualified name of the implementation). + */ +public interface LoggingManager { + /** + * Change the log Level of all loggers (named and root) + * + * @param logLevel the log Level (slf4j) to apply + */ + void setLogLevel(Level logLevel); + + /** + * Retrieve the log Level of a specific logger + * + * @param logger the logger (slf4j) for which to retrieve the log Level + * @return the Level (slf4j) of this logger. + * Note that SLF4J only support ERROR, WARN, INFO, DEBUG, TRACE while some frameworks may support others (OFF, FATAL, ...) + */ + Level getLogLevel(Logger logger); +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManagerRegistry.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManagerRegistry.java new file mode 100644 index 000000000..463981903 --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManagerRegistry.java @@ -0,0 +1,99 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal; + +import java.io.PrintStream; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; + +/** + * Thread-safe singleton registry for LoggingManager instances. + * Handles lazy loading and caching of the LoggingManager implementation. + */ +public final class LoggingManagerRegistry { + + // Used with double-checked locking within getLoggingManger() + @SuppressWarnings({ "java:S3077", "PMD.AvoidUsingVolatile" }) + private static volatile LoggingManager instance; + + private LoggingManagerRegistry() { + // Utility class + } + + /** + * Gets the LoggingManager instance, loading it lazily on first access. + * + * @return the LoggingManager instance + */ + public static LoggingManager getLoggingManager() { + LoggingManager manager = instance; + if (manager == null) { + synchronized (LoggingManagerRegistry.class) { + manager = instance; + if (manager == null) { + manager = loadLoggingManager(); + instance = manager; + } + } + } + return manager; + } + + @SuppressWarnings("java:S106") // S106: System.err is used rather than logger to make sure message is printed + private static LoggingManager loadLoggingManager() { + ServiceLoader<LoggingManager> loggingManagers; + SecurityManager securityManager = System.getSecurityManager(); + if (securityManager == null) { + loggingManagers = ServiceLoader.load(LoggingManager.class); + } else { + final PrivilegedAction<ServiceLoader<LoggingManager>> action = () -> ServiceLoader + .load(LoggingManager.class); + loggingManagers = AccessController.doPrivileged(action); + } + + List<LoggingManager> loggingManagerList = new ArrayList<>(); + for (LoggingManager lm : loggingManagers) { + loggingManagerList.add(lm); + } + return selectLoggingManager(loggingManagerList, System.err); + } + + static LoggingManager selectLoggingManager(List<LoggingManager> loggingManagerList, PrintStream printStream) { + LoggingManager loggingManager; + if (loggingManagerList.isEmpty()) { + printStream.println("ERROR. No LoggingManager was found on the classpath"); + printStream.println("ERROR. Applying default LoggingManager: POWERTOOLS_LOG_LEVEL variable is ignored"); + printStream.println( + "ERROR. Make sure to add either powertools-logging-log4j or powertools-logging-logback to your dependencies"); + loggingManager = new DefaultLoggingManager(); + } else { + if (loggingManagerList.size() > 1) { + printStream.println("WARN. Multiple LoggingManagers were found on the classpath"); + for (LoggingManager manager : loggingManagerList) { + printStream.println("WARN. Found LoggingManager: [" + manager + "]"); + } + printStream.println( + "WARN. Make sure to have only one of powertools-logging-log4j OR powertools-logging-logback in your dependencies"); + printStream.println("WARN. Using the first LoggingManager found on the classpath: [" + + loggingManagerList.get(0) + "]"); + } + loggingManager = loggingManagerList.get(0); + } + return loggingManager; + } +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefaultLambdaFields.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java similarity index 56% rename from powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefaultLambdaFields.java rename to powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java index a50b292b2..2545396d2 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefaultLambdaFields.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,31 +11,43 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.logging.internal; import com.amazonaws.services.lambda.runtime.Context; - import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; -enum DefaultLambdaFields { - FUNCTION_NAME("functionName"), - FUNCTION_VERSION("functionVersion"), - FUNCTION_ARN("functionArn"), - FUNCTION_MEMORY_SIZE("functionMemorySize"), - FUNCTION_REQUEST_ID("function_request_id"); +/** + * Fields added in the logs by Powertools. + * Same as <a href="https://docs.powertools.aws.dev/lambda/python/latest/core/logger/#standard-structured-keys">python</a> + */ +public enum PowertoolsLoggedFields { + FUNCTION_NAME("function_name"), + FUNCTION_VERSION("function_version"), + FUNCTION_ARN("function_arn"), + FUNCTION_MEMORY_SIZE("function_memory_size"), + FUNCTION_REQUEST_ID("function_request_id"), + FUNCTION_COLD_START("cold_start"), + FUNCTION_TRACE_ID("xray_trace_id"), + SAMPLING_RATE("sampling_rate"), + CORRELATION_ID("correlation_id"), + SERVICE("service"); private final String name; - DefaultLambdaFields(String name) { + PowertoolsLoggedFields(String name) { this.name = name; } - public String getName() { - return name; + public static List<String> stringValues() { + return Stream.of(values()).map(PowertoolsLoggedFields::getName).collect(Collectors.toList()); } - static Map<String, String> values(Context context) { + public static Map<String, String> setValuesFromLambdaContext(Context context) { Map<String, String> hashMap = new HashMap<>(); hashMap.put(FUNCTION_NAME.name, context.getFunctionName()); @@ -46,4 +58,8 @@ static Map<String, String> values(Context context) { return hashMap; } + + public String getName() { + return name; + } } diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java deleted file mode 100644 index c392e2ed9..000000000 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java +++ /dev/null @@ -1,51 +0,0 @@ -package software.amazon.lambda.powertools.logging.internal; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.layout.template.json.resolver.EventResolver; -import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.util.ReadOnlyStringMap; - -final class PowertoolsResolver implements EventResolver { - - private final EventResolver internalResolver; - - PowertoolsResolver() { - internalResolver = new EventResolver() { - @Override - public boolean isResolvable(LogEvent value) { - ReadOnlyStringMap contextData = value.getContextData(); - return null != contextData && !contextData.isEmpty(); - } - - @Override - public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { - StringBuilder stringBuilder = jsonWriter.getStringBuilder(); - // remove dummy field to kick inn powertools resolver - stringBuilder.setLength(stringBuilder.length() - 4); - - // Inject all the context information. - ReadOnlyStringMap contextData = logEvent.getContextData(); - contextData.forEach((key, value) -> { - jsonWriter.writeSeparator(); - jsonWriter.writeString(key); - stringBuilder.append(':'); - jsonWriter.writeValue(value); - }); - } - }; - } - - static String getName() { - return "powertools"; - } - - @Override - public void resolve(LogEvent value, JsonWriter jsonWriter) { - internalResolver.resolve(value, jsonWriter); - } - - @Override - public boolean isResolvable(LogEvent value) { - return internalResolver.isResolvable(value); - } -} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java deleted file mode 100644 index 5683c9688..000000000 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package software.amazon.lambda.powertools.logging.internal; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext; -import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory; - -@Plugin(name = "PowertoolsResolverFactory", category = TemplateResolverFactory.CATEGORY) -public final class PowertoolsResolverFactory implements EventResolverFactory { - - private static final PowertoolsResolverFactory INSTANCE = new PowertoolsResolverFactory(); - - private PowertoolsResolverFactory() {} - - @PluginFactory - public static PowertoolsResolverFactory getInstance() { - return INSTANCE; - } - - @Override - public String getName() { - return PowertoolsResolver.getName(); - } - - @Override - public TemplateResolver<LogEvent> create(EventResolverContext context, - TemplateResolverConfig config) { - return new PowertoolsResolver(); - } -} diff --git a/powertools-logging/src/main/resources/LambdaEcsLayout.json b/powertools-logging/src/main/resources/LambdaEcsLayout.json deleted file mode 100644 index 4ab9c7ce2..000000000 --- a/powertools-logging/src/main/resources/LambdaEcsLayout.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "@timestamp": { - "$resolver": "timestamp", - "pattern": { - "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", - "timeZone": "UTC" - } - }, - "ecs.version": "1.2.0", - "log.level": { - "$resolver": "level", - "field": "name" - }, - "message": { - "$resolver": "message", - "stringified": true - }, - "process.thread.name": { - "$resolver": "thread", - "field": "name" - }, - "log.logger": { - "$resolver": "logger", - "field": "name" - }, - "labels": { - "$resolver": "mdc", - "flatten": true, - "stringified": true - }, - "tags": { - "$resolver": "ndc" - }, - "error.type": { - "$resolver": "exception", - "field": "className" - }, - "error.message": { - "$resolver": "exception", - "field": "message" - }, - "error.stack_trace": { - "$resolver": "exception", - "field": "stackTrace", - "stackTrace": { - "stringified": true - } - }, - "": { - "$resolver": "powertools" - } -} \ No newline at end of file diff --git a/powertools-logging/src/main/resources/LambdaJsonLayout.json b/powertools-logging/src/main/resources/LambdaJsonLayout.json deleted file mode 100644 index dfc1fc78f..000000000 --- a/powertools-logging/src/main/resources/LambdaJsonLayout.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "timestamp": { - "$resolver": "timestamp" - }, - "instant": { - "epochSecond": { - "$resolver": "timestamp", - "epoch": { - "unit": "secs", - "rounded": true - } - }, - "nanoOfSecond": { - "$resolver": "timestamp", - "epoch": { - "unit": "secs.nanos" - } - } - }, - "thread": { - "$resolver": "thread", - "field": "name" - }, - "level": { - "$resolver": "level", - "field": "name" - }, - "loggerName": { - "$resolver": "logger", - "field": "name" - }, - "message": { - "$resolver": "message", - "stringified": true - }, - "thrown": { - "message": { - "$resolver": "exception", - "field": "message" - }, - "name": { - "$resolver": "exception", - "field": "className" - }, - "extendedStackTrace": { - "$resolver": "exception", - "field": "stackTrace" - } - }, - "contextStack": { - "$resolver": "ndc" - }, - "endOfBatch": { - "$resolver": "endOfBatch" - }, - "loggerFqcn": { - "$resolver": "logger", - "field": "fqcn" - }, - "threadId": { - "$resolver": "thread", - "field": "id" - }, - "threadPriority": { - "$resolver": "thread", - "field": "priority" - }, - "source": { - "class": { - "$resolver": "source", - "field": "className" - }, - "method": { - "$resolver": "source", - "field": "methodName" - }, - "file": { - "$resolver": "source", - "field": "fileName" - }, - "line": { - "$resolver": "source", - "field": "lineNumber" - } - }, - "": { - "$resolver": "powertools" - } -} diff --git a/powertools-logging/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging/jni-config.json b/powertools-logging/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging/jni-config.json new file mode 100644 index 000000000..c8b081385 --- /dev/null +++ b/powertools-logging/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging/jni-config.json @@ -0,0 +1,10 @@ +[ +{ + "name":"java.lang.Boolean", + "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"sun.management.VMManagementImpl", + "fields":[{"name":"compTimeMonitoringSupport"}, {"name":"currentThreadCpuTimeSupport"}, {"name":"objectMonitorUsageSupport"}, {"name":"otherThreadCpuTimeSupport"}, {"name":"remoteDiagnosticCommandsSupport"}, {"name":"synchronizerUsageSupport"}, {"name":"threadAllocatedMemorySupport"}, {"name":"threadContentionMonitoringSupport"}] +} +] diff --git a/powertools-logging/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging/reflect-config.json b/powertools-logging/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging/reflect-config.json new file mode 100644 index 000000000..9e665e87a --- /dev/null +++ b/powertools-logging/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging/reflect-config.json @@ -0,0 +1,282 @@ +[ +{ + "name":"[Lcom.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers;" +}, +{ + "name":"[Lcom.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.KeyDeserializers;" +}, +{ + "name":"[Lcom.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiators;" +}, +{ + "name":"[Lcom.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers;" +}, +{ + "name":"[Lsoftware.amazon.lambda.powertools.logging.model.Product;" +}, +{ + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.Context" +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getBody","parameterTypes":[] }, {"name":"getHeaders","parameterTypes":[] }, {"name":"getHttpMethod","parameterTypes":[] }, {"name":"getIsBase64Encoded","parameterTypes":[] }, {"name":"getMultiValueHeaders","parameterTypes":[] }, {"name":"getMultiValueQueryStringParameters","parameterTypes":[] }, {"name":"getPath","parameterTypes":[] }, {"name":"getPathParameters","parameterTypes":[] }, {"name":"getQueryStringParameters","parameterTypes":[] }, {"name":"getRequestContext","parameterTypes":[] }, {"name":"getResource","parameterTypes":[] }, {"name":"getStageVariables","parameterTypes":[] }, {"name":"getVersion","parameterTypes":[] }, {"name":"setBody","parameterTypes":["java.lang.String"] }, {"name":"setHeaders","parameterTypes":["java.util.Map"] }, {"name":"setHttpMethod","parameterTypes":["java.lang.String"] }, {"name":"setIsBase64Encoded","parameterTypes":["java.lang.Boolean"] }, {"name":"setMultiValueHeaders","parameterTypes":["java.util.Map"] }, {"name":"setMultiValueQueryStringParameters","parameterTypes":["java.util.Map"] }, {"name":"setPath","parameterTypes":["java.lang.String"] }, {"name":"setPathParameters","parameterTypes":["java.util.Map"] }, {"name":"setQueryStringParameters","parameterTypes":["java.util.Map"] }, {"name":"setRequestContext","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext"] }, {"name":"setResource","parameterTypes":["java.lang.String"] }, {"name":"setStageVariables","parameterTypes":["java.util.Map"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getAccountId","parameterTypes":[] }, {"name":"getApiId","parameterTypes":[] }, {"name":"getAuthorizer","parameterTypes":[] }, {"name":"getDomainName","parameterTypes":[] }, {"name":"getDomainPrefix","parameterTypes":[] }, {"name":"getExtendedRequestId","parameterTypes":[] }, {"name":"getHttpMethod","parameterTypes":[] }, {"name":"getIdentity","parameterTypes":[] }, {"name":"getOperationName","parameterTypes":[] }, {"name":"getPath","parameterTypes":[] }, {"name":"getProtocol","parameterTypes":[] }, {"name":"getRequestId","parameterTypes":[] }, {"name":"getRequestTime","parameterTypes":[] }, {"name":"getRequestTimeEpoch","parameterTypes":[] }, {"name":"getResourceId","parameterTypes":[] }, {"name":"getResourcePath","parameterTypes":[] }, {"name":"getStage","parameterTypes":[] }, {"name":"setHttpMethod","parameterTypes":["java.lang.String"] }, {"name":"setPath","parameterTypes":["java.lang.String"] }, {"name":"setRequestId","parameterTypes":["java.lang.String"] }, {"name":"setResourcePath","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getBody","parameterTypes":[] }, {"name":"getCookies","parameterTypes":[] }, {"name":"getHeaders","parameterTypes":[] }, {"name":"getIsBase64Encoded","parameterTypes":[] }, {"name":"getPathParameters","parameterTypes":[] }, {"name":"getQueryStringParameters","parameterTypes":[] }, {"name":"getRawPath","parameterTypes":[] }, {"name":"getRawQueryString","parameterTypes":[] }, {"name":"getRequestContext","parameterTypes":[] }, {"name":"getRouteKey","parameterTypes":[] }, {"name":"getStageVariables","parameterTypes":[] }, {"name":"getVersion","parameterTypes":[] }, {"name":"setCookies","parameterTypes":["java.util.List"] }, {"name":"setHeaders","parameterTypes":["java.util.Map"] }, {"name":"setIsBase64Encoded","parameterTypes":["boolean"] }, {"name":"setRawPath","parameterTypes":["java.lang.String"] }, {"name":"setRawQueryString","parameterTypes":["java.lang.String"] }, {"name":"setRequestContext","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext"] }, {"name":"setRouteKey","parameterTypes":["java.lang.String"] }, {"name":"setVersion","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getAccountId","parameterTypes":[] }, {"name":"getApiId","parameterTypes":[] }, {"name":"getAuthorizer","parameterTypes":[] }, {"name":"getDomainName","parameterTypes":[] }, {"name":"getDomainPrefix","parameterTypes":[] }, {"name":"getHttp","parameterTypes":[] }, {"name":"getRequestId","parameterTypes":[] }, {"name":"getRouteKey","parameterTypes":[] }, {"name":"getStage","parameterTypes":[] }, {"name":"getTime","parameterTypes":[] }, {"name":"getTimeEpoch","parameterTypes":[] }, {"name":"setAccountId","parameterTypes":["java.lang.String"] }, {"name":"setApiId","parameterTypes":["java.lang.String"] }, {"name":"setDomainName","parameterTypes":["java.lang.String"] }, {"name":"setDomainPrefix","parameterTypes":["java.lang.String"] }, {"name":"setHttp","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext$Http"] }, {"name":"setRequestId","parameterTypes":["java.lang.String"] }, {"name":"setRouteKey","parameterTypes":["java.lang.String"] }, {"name":"setStage","parameterTypes":["java.lang.String"] }, {"name":"setTime","parameterTypes":["java.lang.String"] }, {"name":"setTimeEpoch","parameterTypes":["long"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext$Authorizer", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext$Authorizer$JWT", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext$CognitoIdentity", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext$Http", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getMethod","parameterTypes":[] }, {"name":"getPath","parameterTypes":[] }, {"name":"getProtocol","parameterTypes":[] }, {"name":"getSourceIp","parameterTypes":[] }, {"name":"getUserAgent","parameterTypes":[] }, {"name":"setMethod","parameterTypes":["java.lang.String"] }, {"name":"setPath","parameterTypes":["java.lang.String"] }, {"name":"setProtocol","parameterTypes":["java.lang.String"] }, {"name":"setSourceIp","parameterTypes":["java.lang.String"] }, {"name":"setUserAgent","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext$IAM", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getBody","parameterTypes":[] }, {"name":"getHeaders","parameterTypes":[] }, {"name":"getHttpMethod","parameterTypes":[] }, {"name":"getIsBase64Encoded","parameterTypes":[] }, {"name":"getMultiValueHeaders","parameterTypes":[] }, {"name":"getMultiValueQueryStringParameters","parameterTypes":[] }, {"name":"getPath","parameterTypes":[] }, {"name":"getQueryStringParameters","parameterTypes":[] }, {"name":"getRequestContext","parameterTypes":[] }, {"name":"setBody","parameterTypes":["java.lang.String"] }, {"name":"setHeaders","parameterTypes":["java.util.Map"] }, {"name":"setHttpMethod","parameterTypes":["java.lang.String"] }, {"name":"setIsBase64Encoded","parameterTypes":["boolean"] }, {"name":"setPath","parameterTypes":["java.lang.String"] }, {"name":"setQueryStringParameters","parameterTypes":["java.util.Map"] }, {"name":"setRequestContext","parameterTypes":["com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent$RequestContext"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent$Elb", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getTargetGroupArn","parameterTypes":[] }, {"name":"setTargetGroupArn","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent$RequestContext", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getElb","parameterTypes":[] }, {"name":"setElb","parameterTypes":["com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent$Elb"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.tests.EventArgumentsProvider", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.tests.annotations.Event", + "queryAllPublicMethods":true +}, +{ + "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"double", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.io.IOException" +}, +{ + "name":"java.io.InputStream" +}, +{ + "name":"java.io.OutputStream" +}, +{ + "name":"java.io.Serializable", + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"java.lang.Boolean" +}, +{ + "name":"java.lang.Cloneable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Object" +}, +{ + "name":"java.lang.ProcessEnvironment", + "fields":[{"name":"theCaseInsensitiveEnvironment"}, {"name":"theEnvironment"}] +}, +{ + "name":"java.lang.String" +}, +{ + "name":"java.util.AbstractMap", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.Collections$SingletonMap", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.Collections$UnmodifiableMap", + "fields":[{"name":"m"}] +}, +{ + "name":"java.util.Map", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.function.Consumer", + "queryAllPublicMethods":true +}, +{ + "name":"org.apiguardian.api.API", + "queryAllPublicMethods":true +}, +{ + "name":"org.joda.time.DateTime" +}, +{ + "name":"org.slf4j.ILoggerFactory", + "allDeclaredClasses":true, + "queryAllPublicMethods":true +}, +{ + "name":"org.slf4j.Logger", + "allDeclaredClasses":true, + "queryAllPublicMethods":true +}, +{ + "name":"org.slf4j.helpers.AbstractLogger", + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"org.slf4j.helpers.LegacyAbstractLogger", + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"org.slf4j.spi.SLF4JServiceProvider", + "allDeclaredClasses":true, + "queryAllPublicMethods":true +}, +{ + "name":"software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor", + "fields":[{"name":"isColdStart"}, {"name":"serviceName"}] +}, +{ + "name":"software.amazon.lambda.powertools.logging.argument.StructuredArgumentsTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"arrayArgument","parameterTypes":[] }, {"name":"emptyMapArgument","parameterTypes":[] }, {"name":"jsonArgument","parameterTypes":[] }, {"name":"keyValueArgument","parameterTypes":[] }, {"name":"mapArgument","parameterTypes":[] }, {"name":"reservedKeywordArgumentIgnored","parameterTypes":[] }, {"name":"setUp","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.logging.internal.BufferManager", + "allDeclaredClasses":true, + "queryAllPublicMethods":true +}, +{ + "name":"software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect", + "fields":[{"name":"LEVEL_AT_INITIALISATION"}], + "methods":[{"name":"setLogLevels","parameterTypes":["org.slf4j.event.Level"] }] +}, +{ + "name":"software.amazon.lambda.powertools.logging.internal.LoggingConstants", + "fields":[{"name":"LAMBDA_LOG_LEVEL"}, {"name":"POWERTOOLS_LOG_EVENT"}, {"name":"POWERTOOLS_LOG_LEVEL"}, {"name":"POWERTOOLS_SAMPLING_RATE"}] +}, +{ + "name":"software.amazon.lambda.powertools.logging.internal.LoggingManager", + "allDeclaredClasses":true, + "queryAllPublicMethods":true +}, +{ + "name":"software.amazon.lambda.powertools.logging.internal.TestLoggingManager", + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"software.amazon.lambda.powertools.logging.model.Basket", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getProducts","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.logging.model.Product", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getId","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPrice","parameterTypes":[] }] +} +] diff --git a/powertools-logging/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging/resource-config.json b/powertools-logging/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging/resource-config.json new file mode 100644 index 000000000..832be3d72 --- /dev/null +++ b/powertools-logging/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-logging/resource-config.json @@ -0,0 +1,17 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory\\E" + }, { + "pattern":"\\QMETA-INF/services/org.assertj.core.configuration.Configuration\\E" + }, { + "pattern":"\\QMETA-INF/services/org.assertj.core.presentation.Representation\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager\\E" + }]}, + "bundles":[] +} diff --git a/powertools-logging/src/main/resources/log4j2.component.properties b/powertools-logging/src/main/resources/log4j2.component.properties deleted file mode 100644 index 3c392dd13..000000000 --- a/powertools-logging/src/main/resources/log4j2.component.properties +++ /dev/null @@ -1,2 +0,0 @@ -log4j.layout.jsonTemplate.timestampFormatPattern=yyyy-MM-dd'T'HH:mm:ss.SSSZz -#log4j.layout.jsonTemplate.timeZone= \ No newline at end of file diff --git a/powertools-logging/src/test/java/org/apache/logging/log4j/core/layout/LambdaJsonLayoutTest.java b/powertools-logging/src/test/java/org/apache/logging/log4j/core/layout/LambdaJsonLayoutTest.java deleted file mode 100644 index 5fc0398d1..000000000 --- a/powertools-logging/src/test/java/org/apache/logging/log4j/core/layout/LambdaJsonLayoutTest.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. - * 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 - * http://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. - * - */ -package org.apache.logging.log4j.core.layout; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.Map; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.logging.log4j.Level; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabled; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolSamplingEnabled; -import software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect; - -import static java.util.Collections.emptyMap; -import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; - -class LambdaJsonLayoutTest { - - private RequestHandler<Object, Object> handler = new PowerLogToolEnabled(); - - @Mock - private Context context; - - @BeforeEach - void setUp() throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { - openMocks(this); - setupContext(); - //Make sure file is cleaned up before running full stack logging regression - FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); - resetLogLevel(Level.INFO); - } - - @Test - void shouldLogInStructuredFormat() throws IOException { - handler.handleRequest("test", context); - - assertThat(Files.lines(Paths.get("target/logfile.json"))) - .hasSize(1) - .allSatisfy(line -> assertThat(parseToMap(line)) - .containsEntry("functionName", "testFunction") - .containsEntry("functionVersion", "1") - .containsEntry("functionMemorySize", "10") - .containsEntry("functionArn", "testArn") - .containsKey("timestamp") - .containsKey("message") - .containsKey("service")); - } - - @Test - void shouldModifyLogLevelBasedOnEnvVariable() throws IllegalAccessException, IOException, NoSuchMethodException, InvocationTargetException { - resetLogLevel(Level.DEBUG); - - handler.handleRequest("test", context); - - assertThat(Files.lines(Paths.get("target/logfile.json"))) - .hasSize(2) - .satisfies(line -> { - assertThat(parseToMap(line.get(0))) - .containsEntry("level", "INFO") - .containsEntry("message", "Test event"); - - assertThat(parseToMap(line.get(1))) - .containsEntry("level", "DEBUG") - .containsEntry("message", "Test debug event"); - }); - } - - @Test - void shouldModifyLogLevelBasedOnSamplingRule() throws IOException { - handler = new PowerLogToolSamplingEnabled(); - - handler.handleRequest("test", context); - - assertThat(Files.lines(Paths.get("target/logfile.json"))) - .hasSize(3) - .satisfies(line -> { - assertThat(parseToMap(line.get(0))) - .containsEntry("level", "DEBUG") - .containsEntry("loggerName", LambdaLoggingAspect.class.getCanonicalName()); - - assertThat(parseToMap(line.get(1))) - .containsEntry("level", "INFO") - .containsEntry("message", "Test event"); - - assertThat(parseToMap(line.get(2))) - .containsEntry("level", "DEBUG") - .containsEntry("message", "Test debug event"); - }); - } - - private void resetLogLevel(Level level) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - Method resetLogLevels = LambdaLoggingAspect.class.getDeclaredMethod("resetLogLevels", Level.class); - resetLogLevels.setAccessible(true); - resetLogLevels.invoke(null, level); - writeStaticField(LambdaLoggingAspect.class, "LEVEL_AT_INITIALISATION", level, true); - } - - private Map<String, Object> parseToMap(String stringAsJson) { - try { - return new ObjectMapper().readValue(stringAsJson, Map.class); - } catch (JsonProcessingException e) { - fail("Failed parsing logger line " + stringAsJson); - return emptyMap(); - } - } - - private void setupContext() { - when(context.getFunctionName()).thenReturn("testFunction"); - when(context.getInvokedFunctionArn()).thenReturn("testArn"); - when(context.getFunctionVersion()).thenReturn("1"); - when(context.getMemoryLimitInMB()).thenReturn(10); - } -} \ No newline at end of file diff --git a/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java b/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java new file mode 100644 index 000000000..9a6d56b81 --- /dev/null +++ b/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2004-2011 QOS.ch + * All rights reserved. + * <p> + * 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: + * <p> + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * <p> + * 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. + */ + +package org.slf4j.test; + +import java.io.PrintStream; + +/** + * This class encapsulates the user's choice of output target. + * + * @see <a href="https://www.slf4j.org/xref/org/slf4j/simple/OutputChoice.html">...</a> + */ +class OutputChoice { + + final OutputChoiceType outputChoiceType; + final PrintStream targetPrintStream; + OutputChoice(OutputChoiceType outputChoiceType) { + if (outputChoiceType == OutputChoiceType.FILE) { + throw new IllegalArgumentException(); + } + this.outputChoiceType = outputChoiceType; + if (outputChoiceType == OutputChoiceType.CACHED_SYS_OUT) { + this.targetPrintStream = System.out; + } else if (outputChoiceType == OutputChoiceType.CACHED_SYS_ERR) { + this.targetPrintStream = System.err; + } else { + this.targetPrintStream = null; + } + } + + OutputChoice(PrintStream printStream) { + this.outputChoiceType = OutputChoiceType.FILE; + this.targetPrintStream = printStream; + } + + PrintStream getTargetPrintStream() { + switch (outputChoiceType) { + case SYS_OUT: + return System.out; + case SYS_ERR: + return System.err; + case CACHED_SYS_ERR: + case CACHED_SYS_OUT: + case FILE: + return targetPrintStream; + default: + throw new IllegalArgumentException(); + } + + } + + enum OutputChoiceType { + SYS_OUT, CACHED_SYS_OUT, SYS_ERR, CACHED_SYS_ERR, FILE; + } + +} diff --git a/powertools-logging/src/test/java/org/slf4j/test/TestLogger.java b/powertools-logging/src/test/java/org/slf4j/test/TestLogger.java new file mode 100644 index 000000000..acc635e75 --- /dev/null +++ b/powertools-logging/src/test/java/org/slf4j/test/TestLogger.java @@ -0,0 +1,464 @@ +/** + * Copyright (c) 2004-2022 QOS.ch Sarl (Switzerland) + * All rights reserved. + * <p> + * 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: + * <p> + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * <p> + * 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. + */ + +package org.slf4j.test; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.Marker; +import org.slf4j.event.Level; +import org.slf4j.event.LoggingEvent; +import org.slf4j.helpers.LegacyAbstractLogger; +import org.slf4j.helpers.MessageFormatter; +import org.slf4j.helpers.NormalizedParameters; +import org.slf4j.spi.LocationAwareLogger; + +/** + * <p> + * Simple implementation of {@link Logger} that sends all enabled log messages, + * for all defined loggers, to the console ({@code System.err}). The following + * system properties are supported to configure the behavior of this logger: + * + * + * <ul> + * <li><code>org.slf4j.simpleLogger.logFile</code> - The output target which can + * be the <em>path</em> to a file, or the special values "System.out" and + * "System.err". Default is "System.err".</li> + * + * <li><code>org.slf4j.simpleLogger.cacheOutputStream</code> - If the output + * target is set to "System.out" or "System.err" (see preceding entry), by + * default, logs will be output to the latest value referenced by + * <code>System.out/err</code> variables. By setting this parameter to true, the + * output stream will be cached, i.e. assigned once at initialization time and + * re-used independently of the current value referenced by + * <code>System.out/err</code>.</li> + * + * <li><code>org.slf4j.simpleLogger.defaultLogLevel</code> - Default log level + * for all instances of SimpleLogger. Must be one of ("trace", "debug", "info", + * "warn", "error" or "off"). If not specified, defaults to "info".</li> + * + * <li><code>org.slf4j.simpleLogger.log.<em>a.b.c</em></code> - Logging detail + * level for a SimpleLogger instance named "a.b.c". Right-side value must be one + * of "trace", "debug", "info", "warn", "error" or "off". When a SimpleLogger + * named "a.b.c" is initialized, its level is assigned from this property. If + * unspecified, the level of nearest parent logger will be used, and if none is + * set, then the value specified by + * <code>org.slf4j.simpleLogger.defaultLogLevel</code> will be used.</li> + * + * <li><code>org.slf4j.simpleLogger.showDateTime</code> - Set to + * <code>true</code> if you want the current date and time to be included in + * output messages. Default is <code>false</code></li> + * + * <li><code>org.slf4j.simpleLogger.dateTimeFormat</code> - The date and time + * format to be used in the output messages. The pattern describing the date and + * time format is defined by <a href= + * "http://docs.oracle.com/javase/1.5.0/docs/api/java/text/SimpleDateFormat.html"> + * <code>SimpleDateFormat</code></a>. If the format is not specified or is + * invalid, the number of milliseconds since start up will be output.</li> + * + * <li><code>org.slf4j.simpleLogger.showThreadName</code> -Set to + * <code>true</code> if you want to output the current thread name. Defaults to + * <code>true</code>.</li> + * + * <li>(since version 1.7.33 and 2.0.0-alpha6) <code>org.slf4j.simpleLogger.showThreadId</code> - + * If you would like to output the current thread id, then set to + * <code>true</code>. Defaults to <code>false</code>.</li> + * + * <li><code>org.slf4j.simpleLogger.showLogName</code> - Set to + * <code>true</code> if you want the Logger instance name to be included in + * output messages. Defaults to <code>true</code>.</li> + * + * <li><code>org.slf4j.simpleLogger.showShortLogName</code> - Set to + * <code>true</code> if you want the last component of the name to be included + * in output messages. Defaults to <code>false</code>.</li> + * + * <li><code>org.slf4j.simpleLogger.levelInBrackets</code> - Should the level + * string be output in brackets? Defaults to <code>false</code>.</li> + * + * <li><code>org.slf4j.simpleLogger.warnLevelString</code> - The string value + * output for the warn level. Defaults to <code>WARN</code>.</li> + * + * </ul> + * + * <p> + * In addition to looking for system properties with the names specified above, + * this implementation also checks for a class loader resource named + * <code>"simplelogger.properties"</code>, and includes any matching definitions + * from this resource (if it exists). + * + * + * <p> + * With no configuration, the default output includes the relative time in + * milliseconds, thread name, the level, logger name, and the message followed + * by the line separator for the host. In log4j terms it amounts to the "%r [%t] + * %level %logger - %m%n" pattern. + * + * <p> + * Sample output follows. + * + * + * <pre> + * 176 [main] INFO examples.Sort - Populating an array of 2 elements in reverse order. + * 225 [main] INFO examples.SortAlgo - Entered the sort method. + * 304 [main] INFO examples.SortAlgo - Dump of integer array: + * 317 [main] INFO examples.SortAlgo - Element [0] = 0 + * 331 [main] INFO examples.SortAlgo - Element [1] = 1 + * 343 [main] INFO examples.Sort - The next log statement should be an error message. + * 346 [main] ERROR examples.SortAlgo - Tried to dump an uninitialized array. + * at org.log4j.examples.SortAlgo.dump(SortAlgo.java:58) + * at org.log4j.examples.Sort.main(Sort.java:64) + * 467 [main] INFO examples.Sort - Exiting main method. + * </pre> + * + * <p> + * This implementation is heavily inspired by + * <a href="http://commons.apache.org/logging/">Apache Commons Logging</a>'s + * SimpleLog. + * + * + * @author Ceki Gülcü + * @author Scott Sanders + * @author Rod Waldhoff + * @author Robert Burrell Donkin + * @author Cédrik LIME + */ +public class TestLogger extends LegacyAbstractLogger { + + /** + * All system properties used by <code>SimpleLogger</code> start with this + * prefix + */ + public static final String SYSTEM_PREFIX = "org.slf4j.simpleLogger."; + public static final String LOG_KEY_PREFIX = TestLogger.SYSTEM_PREFIX + "log."; + public static final String CACHE_OUTPUT_STREAM_STRING_KEY = TestLogger.SYSTEM_PREFIX + "cacheOutputStream"; + public static final String WARN_LEVEL_STRING_KEY = TestLogger.SYSTEM_PREFIX + "warnLevelString"; + public static final String LEVEL_IN_BRACKETS_KEY = TestLogger.SYSTEM_PREFIX + "levelInBrackets"; + public static final String LOG_FILE_KEY = TestLogger.SYSTEM_PREFIX + "logFile"; + public static final String SHOW_SHORT_LOG_NAME_KEY = TestLogger.SYSTEM_PREFIX + "showShortLogName"; + public static final String SHOW_LOG_NAME_KEY = TestLogger.SYSTEM_PREFIX + "showLogName"; + public static final String SHOW_THREAD_NAME_KEY = TestLogger.SYSTEM_PREFIX + "showThreadName"; + public static final String SHOW_THREAD_ID_KEY = TestLogger.SYSTEM_PREFIX + "showThreadId"; + public static final String DATE_TIME_FORMAT_KEY = TestLogger.SYSTEM_PREFIX + "dateTimeFormat"; + public static final String SHOW_DATE_TIME_KEY = TestLogger.SYSTEM_PREFIX + "showDateTime"; + public static final String DEFAULT_LOG_LEVEL_KEY = TestLogger.SYSTEM_PREFIX + "defaultLogLevel"; + protected static final int LOG_LEVEL_TRACE = LocationAwareLogger.TRACE_INT; + protected static final int LOG_LEVEL_DEBUG = LocationAwareLogger.DEBUG_INT; + protected static final int LOG_LEVEL_INFO = LocationAwareLogger.INFO_INT; + protected static final int LOG_LEVEL_WARN = LocationAwareLogger.WARN_INT; + protected static final int LOG_LEVEL_ERROR = LocationAwareLogger.ERROR_INT; + // The OFF level can only be used in configuration files to disable logging. + // It has + // no printing method associated with it in o.s.Logger interface. + protected static final int LOG_LEVEL_OFF = LOG_LEVEL_ERROR + 10; + static final String TID_PREFIX = "tid="; + static final TestLoggerConfiguration CONFIG_PARAMS = new TestLoggerConfiguration(); + private static final long serialVersionUID = -632788891211436180L; + private static final long START_TIME = System.currentTimeMillis(); + static char SP = ' '; + private static boolean INITIALIZED = false; + /** The current log level */ + protected int currentLogLevel = LOG_LEVEL_INFO; + /** The short name of this simple log instance */ + private transient String shortLogName = null; + + // used for test purpose + private Object[] arguments; + + /** + * Package access allows only {@link TestLoggerFactory} to instantiate + * SimpleLogger instances. + */ + TestLogger(String name) { + this.name = name; + + String levelString = recursivelyComputeLevelString(); + if (levelString != null) { + this.currentLogLevel = TestLoggerConfiguration.stringToLevel(levelString); + } else { + this.currentLogLevel = CONFIG_PARAMS.defaultLogLevel; + } + } + + static void lazyInit() { + if (INITIALIZED) { + return; + } + INITIALIZED = true; + init(); + } + + // external software might be invoking this method directly. Do not rename + // or change its semantics. + static void init() { + CONFIG_PARAMS.init(); + } + + public int getLogLevel() { + return currentLogLevel; + } + + public void setLogLevel(String levelString) { + this.currentLogLevel = TestLoggerConfiguration.stringToLevel(levelString); + } + + String recursivelyComputeLevelString() { + String tempName = name; + String levelString = null; + int indexOfLastDot = tempName.length(); + while ((levelString == null) && (indexOfLastDot > -1)) { + tempName = tempName.substring(0, indexOfLastDot); + levelString = CONFIG_PARAMS.getStringProperty(TestLogger.LOG_KEY_PREFIX + tempName, null); + indexOfLastDot = String.valueOf(tempName).lastIndexOf("."); + } + return levelString; + } + + /** + * To avoid intermingling of log messages and associated stack traces, the two + * operations are done in a synchronized block. + * + * @param buf + * @param t + */ + void write(StringBuilder buf, Throwable t) { + PrintStream targetStream = CONFIG_PARAMS.outputChoice.getTargetPrintStream(); + + synchronized (CONFIG_PARAMS) { + targetStream.println(buf.toString()); + writeThrowable(t, targetStream); + targetStream.flush(); + } + + } + + protected void writeThrowable(Throwable t, PrintStream targetStream) { + if (t != null) { + t.printStackTrace(targetStream); + } + } + + private String getFormattedDate() { + Date now = new Date(); + String dateText; + synchronized (CONFIG_PARAMS.dateFormatter) { + dateText = CONFIG_PARAMS.dateFormatter.format(now); + } + return dateText; + } + + private String computeShortName() { + return name.substring(name.lastIndexOf(".") + 1); + } + + // /** + // * For formatted messages, first substitute arguments and then log. + // * + // * @param level + // * @param format + // * @param arg1 + // * @param arg2 + // */ + // private void formatAndLog(int level, String format, Object arg1, Object arg2) { + // if (!isLevelEnabled(level)) { + // return; + // } + // FormattingTuple tp = MessageFormatter.format(format, arg1, arg2); + // log(level, tp.getMessage(), tp.getThrowable()); + // } + + // /** + // * For formatted messages, first substitute arguments and then log. + // * + // * @param level + // * @param format + // * @param arguments + // * a list of 3 ore more arguments + // */ + // private void formatAndLog(int level, String format, Object... arguments) { + // if (!isLevelEnabled(level)) { + // return; + // } + // FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments); + // log(level, tp.getMessage(), tp.getThrowable()); + // } + + /** + * Is the given log level currently enabled? + * + * @param logLevel is this level enabled? + * @return whether the logger is enabled for the given level + */ + protected boolean isLevelEnabled(int logLevel) { + // log level are numerically ordered so can use simple numeric + // comparison + return (logLevel >= currentLogLevel); + } + + /** Are {@code trace} messages currently enabled? */ + public boolean isTraceEnabled() { + return isLevelEnabled(LOG_LEVEL_TRACE); + } + + /** Are {@code debug} messages currently enabled? */ + public boolean isDebugEnabled() { + return isLevelEnabled(LOG_LEVEL_DEBUG); + } + + /** Are {@code info} messages currently enabled? */ + public boolean isInfoEnabled() { + return isLevelEnabled(LOG_LEVEL_INFO); + } + + /** Are {@code warn} messages currently enabled? */ + public boolean isWarnEnabled() { + return isLevelEnabled(LOG_LEVEL_WARN); + } + + /** Are {@code error} messages currently enabled? */ + public boolean isErrorEnabled() { + return isLevelEnabled(LOG_LEVEL_ERROR); + } + + /** + * SimpleLogger's implementation of + * {@link org.slf4j.helpers.AbstractLogger#handleNormalizedLoggingCall(Level, Marker, String, Object[], Throwable) AbstractLogger#handleNormalizedLoggingCall} + * } + * + * @param level the SLF4J level for this event + * @param marker The marker to be used for this event, may be null. + * @param messagePattern The message pattern which will be parsed and formatted + * @param arguments the array of arguments to be formatted, may be null + * @param throwable The exception whose stack trace should be logged, may be null + */ + @Override + protected void handleNormalizedLoggingCall(Level level, Marker marker, String messagePattern, Object[] arguments, + Throwable throwable) { + + List<Marker> markers = null; + + if (marker != null) { + markers = new ArrayList<>(); + markers.add(marker); + } + + innerHandleNormalizedLoggingCall(level, markers, messagePattern, arguments, throwable); + } + + private void innerHandleNormalizedLoggingCall(Level level, List<Marker> markers, String messagePattern, + Object[] arguments, Throwable t) { + + this.arguments = arguments; + + StringBuilder buf = new StringBuilder(32); + + // Append date-time if so configured + if (CONFIG_PARAMS.showDateTime) { + if (CONFIG_PARAMS.dateFormatter != null) { + buf.append(getFormattedDate()); + buf.append(SP); + } else { + buf.append(System.currentTimeMillis() - START_TIME); + buf.append(SP); + } + } + + // Append current thread name if so configured + if (CONFIG_PARAMS.showThreadName) { + buf.append('['); + buf.append(Thread.currentThread().getName()); + buf.append("] "); + } + + if (CONFIG_PARAMS.showThreadId) { + buf.append(TID_PREFIX); + buf.append(Thread.currentThread().getId()); + buf.append(SP); + } + + if (CONFIG_PARAMS.levelInBrackets) { + buf.append('['); + } + + // Append a readable representation of the log level + String levelStr = level.name(); + buf.append(levelStr); + if (CONFIG_PARAMS.levelInBrackets) { + buf.append(']'); + } + buf.append(SP); + + // Append the name of the log instance if so configured + if (CONFIG_PARAMS.showShortLogName) { + if (shortLogName == null) { + shortLogName = computeShortName(); + } + buf.append(String.valueOf(shortLogName)).append(" - "); + } else if (CONFIG_PARAMS.showLogName) { + buf.append(String.valueOf(name)).append(" - "); + } + + if (markers != null) { + buf.append(SP); + for (Marker marker : markers) { + buf.append(marker.getName()).append(SP); + } + } + + String formattedMessage = MessageFormatter.basicArrayFormat(messagePattern, arguments); + + // Append the message + buf.append(formattedMessage); + + write(buf, t); + } + + public void log(LoggingEvent event) { + int levelInt = event.getLevel().toInt(); + + if (!isLevelEnabled(levelInt)) { + return; + } + + NormalizedParameters np = NormalizedParameters.normalize(event); + + innerHandleNormalizedLoggingCall(event.getLevel(), event.getMarkers(), np.getMessage(), np.getArguments(), + event.getThrowable()); + } + + @Override + protected String getFullyQualifiedCallerName() { + return null; + } + + public Object[] getArguments() { + return arguments; + } + + public void clearArguments() { + arguments = null; + } +} diff --git a/powertools-logging/src/test/java/org/slf4j/test/TestLoggerConfiguration.java b/powertools-logging/src/test/java/org/slf4j/test/TestLoggerConfiguration.java new file mode 100644 index 000000000..7601dbfde --- /dev/null +++ b/powertools-logging/src/test/java/org/slf4j/test/TestLoggerConfiguration.java @@ -0,0 +1,204 @@ +/** + * Copyright (c) 2004-2011 QOS.ch + * All rights reserved. + * <p> + * 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: + * <p> + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * <p> + * 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. + */ + +package org.slf4j.test; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Properties; +import org.slf4j.helpers.Util; + +/** + * This class holds configuration values for {@link TestLogger}. The + * values are computed at runtime. See {@link TestLogger} documentation for + * more information. + * + * + * @author Ceki Gülcü + * @author Scott Sanders + * @author Rod Waldhoff + * @author Robert Burrell Donkin + * @author Cédrik LIME + * + * @since 1.7.25 + */ +public class TestLoggerConfiguration { + + final static boolean SHOW_LOG_NAME_DEFAULT = true; + private static final String CONFIGURATION_FILE = "testlogger.properties"; + private static final boolean SHOW_DATE_TIME_DEFAULT = false; + private static final String DATE_TIME_FORMAT_STR_DEFAULT = null; + private static final boolean SHOW_THREAD_NAME_DEFAULT = true; + /** + * See https://jira.qos.ch/browse/SLF4J-499 + * @since 1.7.33 and 2.0.0-alpha6 + */ + private static final boolean SHOW_THREAD_ID_DEFAULT = false; + private static final boolean SHOW_SHORT_LOG_NAME_DEFAULT = false; + private static final boolean LEVEL_IN_BRACKETS_DEFAULT = false; + private static final String LOG_FILE_DEFAULT = "System.err"; + private static final boolean CACHE_OUTPUT_STREAM_DEFAULT = false; + private static final String WARN_LEVELS_STRING_DEFAULT = "WARN"; + static int DEFAULT_LOG_LEVEL_DEFAULT = TestLogger.LOG_LEVEL_INFO; + private static String dateTimeFormatStr = DATE_TIME_FORMAT_STR_DEFAULT; + private final Properties properties = new Properties(); + int defaultLogLevel = DEFAULT_LOG_LEVEL_DEFAULT; + boolean showDateTime = SHOW_DATE_TIME_DEFAULT; + DateFormat dateFormatter = null; + boolean showThreadName = SHOW_THREAD_NAME_DEFAULT; + boolean showThreadId = SHOW_THREAD_ID_DEFAULT; + boolean showLogName = SHOW_LOG_NAME_DEFAULT; + boolean showShortLogName = SHOW_SHORT_LOG_NAME_DEFAULT; + boolean levelInBrackets = LEVEL_IN_BRACKETS_DEFAULT; + OutputChoice outputChoice = null; + String warnLevelString = WARN_LEVELS_STRING_DEFAULT; + private String logFile = LOG_FILE_DEFAULT; + private boolean cacheOutputStream = CACHE_OUTPUT_STREAM_DEFAULT; + + static int stringToLevel(String levelStr) { + if ("trace".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_TRACE; + } else if ("debug".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_DEBUG; + } else if ("info".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_INFO; + } else if ("warn".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_WARN; + } else if ("error".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_ERROR; + } else if ("off".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_OFF; + } + // assume INFO by default + return TestLogger.LOG_LEVEL_INFO; + } + + private static OutputChoice computeOutputChoice(String logFile, boolean cacheOutputStream) { + if ("System.err".equalsIgnoreCase(logFile)) { + if (cacheOutputStream) { + return new OutputChoice(OutputChoice.OutputChoiceType.CACHED_SYS_ERR); + } else { + return new OutputChoice(OutputChoice.OutputChoiceType.SYS_ERR); + } + } else if ("System.out".equalsIgnoreCase(logFile)) { + if (cacheOutputStream) { + return new OutputChoice(OutputChoice.OutputChoiceType.CACHED_SYS_OUT); + } else { + return new OutputChoice(OutputChoice.OutputChoiceType.SYS_OUT); + } + } else { + try { + FileOutputStream fos = new FileOutputStream(logFile); + PrintStream printStream = new PrintStream(fos); + return new OutputChoice(printStream); + } catch (FileNotFoundException e) { + Util.report("Could not open [" + logFile + "]. Defaulting to System.err", e); + return new OutputChoice(OutputChoice.OutputChoiceType.SYS_ERR); + } + } + } + + void init() { + loadProperties(); + + String defaultLogLevelString = getStringProperty(TestLogger.DEFAULT_LOG_LEVEL_KEY, null); + if (defaultLogLevelString != null) { + defaultLogLevel = stringToLevel(defaultLogLevelString); + } + + showLogName = getBooleanProperty(TestLogger.SHOW_LOG_NAME_KEY, TestLoggerConfiguration.SHOW_LOG_NAME_DEFAULT); + showShortLogName = getBooleanProperty(TestLogger.SHOW_SHORT_LOG_NAME_KEY, SHOW_SHORT_LOG_NAME_DEFAULT); + showDateTime = getBooleanProperty(TestLogger.SHOW_DATE_TIME_KEY, SHOW_DATE_TIME_DEFAULT); + showThreadName = getBooleanProperty(TestLogger.SHOW_THREAD_NAME_KEY, SHOW_THREAD_NAME_DEFAULT); + showThreadId = getBooleanProperty(TestLogger.SHOW_THREAD_ID_KEY, SHOW_THREAD_ID_DEFAULT); + dateTimeFormatStr = getStringProperty(TestLogger.DATE_TIME_FORMAT_KEY, DATE_TIME_FORMAT_STR_DEFAULT); + levelInBrackets = getBooleanProperty(TestLogger.LEVEL_IN_BRACKETS_KEY, LEVEL_IN_BRACKETS_DEFAULT); + warnLevelString = getStringProperty(TestLogger.WARN_LEVEL_STRING_KEY, WARN_LEVELS_STRING_DEFAULT); + + logFile = getStringProperty(TestLogger.LOG_FILE_KEY, logFile); + + cacheOutputStream = getBooleanProperty(TestLogger.CACHE_OUTPUT_STREAM_STRING_KEY, CACHE_OUTPUT_STREAM_DEFAULT); + outputChoice = computeOutputChoice(logFile, cacheOutputStream); + + if (dateTimeFormatStr != null) { + try { + dateFormatter = new SimpleDateFormat(dateTimeFormatStr); + } catch (IllegalArgumentException e) { + Util.report("Bad date format in " + CONFIGURATION_FILE + "; will output relative time", e); + } + } + } + + private void loadProperties() { + // Add props from the resource testlogger.properties + InputStream in = AccessController.doPrivileged((PrivilegedAction<InputStream>) () -> { + ClassLoader threadCL = Thread.currentThread().getContextClassLoader(); + if (threadCL != null) { + return threadCL.getResourceAsStream(CONFIGURATION_FILE); + } else { + return ClassLoader.getSystemResourceAsStream(CONFIGURATION_FILE); + } + }); + if (null != in) { + try { + properties.load(in); + } catch (java.io.IOException e) { + // ignored + } finally { + try { + in.close(); + } catch (java.io.IOException e) { + // ignored + } + } + } + } + + String getStringProperty(String name, String defaultValue) { + String prop = getStringProperty(name); + return (prop == null) ? defaultValue : prop; + } + + boolean getBooleanProperty(String name, boolean defaultValue) { + String prop = getStringProperty(name); + return (prop == null) ? defaultValue : "true".equalsIgnoreCase(prop); + } + + String getStringProperty(String name) { + String prop = null; + try { + prop = System.getProperty(name); + } catch (SecurityException e) { + ; // Ignore + } + return (prop == null) ? properties.getProperty(name) : prop; + } + +} diff --git a/powertools-logging/src/test/java/org/slf4j/test/TestLoggerFactory.java b/powertools-logging/src/test/java/org/slf4j/test/TestLoggerFactory.java new file mode 100644 index 000000000..d597b5706 --- /dev/null +++ b/powertools-logging/src/test/java/org/slf4j/test/TestLoggerFactory.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2004-2011 QOS.ch + * All rights reserved. + * <p> + * 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: + * <p> + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * <p> + * 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. + */ + +package org.slf4j.test; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; + +/** + * An implementation of {@link ILoggerFactory} which always returns + * {@link TestLogger} instances. + * + * @author Ceki Gülcü + */ +public class TestLoggerFactory implements ILoggerFactory { + + ConcurrentMap<String, Logger> loggerMap; + + public TestLoggerFactory() { + loggerMap = new ConcurrentHashMap<>(); + TestLogger.lazyInit(); + } + + public Map<String, Logger> getLoggers() { + return loggerMap; + } + + /** + * Return an appropriate {@link TestLogger} instance by name. + */ + public Logger getLogger(String name) { + Logger simpleLogger = loggerMap.get(name); + if (simpleLogger != null) { + return simpleLogger; + } else { + Logger newInstance = new TestLogger(name); + Logger oldInstance = loggerMap.putIfAbsent(name, newInstance); + return oldInstance == null ? newInstance : oldInstance; + } + } + + /** + * Clear the internal logger cache. + * + * This method is intended to be called by classes (in the same package) for + * testing purposes. This method is internal. It can be modified, renamed or + * removed at any time without notice. + * + * You are strongly discouraged from calling this method in production code. + */ + void reset() { + loggerMap.clear(); + } +} diff --git a/powertools-logging/src/test/java/org/slf4j/test/TestServiceProvider.java b/powertools-logging/src/test/java/org/slf4j/test/TestServiceProvider.java new file mode 100644 index 000000000..357360d1e --- /dev/null +++ b/powertools-logging/src/test/java/org/slf4j/test/TestServiceProvider.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2004-2011 QOS.ch + * All rights reserved. + * <p> + * 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: + * <p> + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * <p> + * 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. + */ + +package org.slf4j.test; + +import org.slf4j.ILoggerFactory; +import org.slf4j.IMarkerFactory; +import org.slf4j.helpers.BasicMDCAdapter; +import org.slf4j.helpers.BasicMarkerFactory; +import org.slf4j.spi.MDCAdapter; +import org.slf4j.spi.SLF4JServiceProvider; + +/** + * Copy of the org.slf4j.simple.SimpleServiceProvider, replacing the NoOpMDCAdapter with a BasicMDCAdapter to test the MDC + */ +public class TestServiceProvider implements SLF4JServiceProvider { + /** + * Declare the version of the SLF4J API this implementation is compiled against. + * The value of this field is modified with each major release. + */ + // to avoid constant folding by the compiler, this field must *not* be final + public static String REQUESTED_API_VERSION = "2.0.99"; // !final + + private ILoggerFactory loggerFactory; + private IMarkerFactory markerFactory; + private MDCAdapter mdcAdapter; + + public ILoggerFactory getLoggerFactory() { + return loggerFactory; + } + + @Override + public IMarkerFactory getMarkerFactory() { + return markerFactory; + } + + @Override + public MDCAdapter getMDCAdapter() { + return mdcAdapter; + } + + @Override + public String getRequestedApiVersion() { + return REQUESTED_API_VERSION; + } + + @Override + public void initialize() { + + loggerFactory = new TestLoggerFactory(); + markerFactory = new BasicMarkerFactory(); + mdcAdapter = new BasicMDCAdapter(); + } + +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java deleted file mode 100644 index 91fea4c7a..000000000 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. - * 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 - * http://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. - * - */ -package software.amazon.lambda.powertools.logging; - -import java.util.HashMap; -import java.util.Map; - -import org.apache.logging.log4j.ThreadContext; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - - -class LoggingUtilsTest { - - @BeforeEach - void setUp() { - ThreadContext.clearAll(); - } - - @Test - void shouldSetCustomKeyOnThreadContext() { - LoggingUtils.appendKey("test", "value"); - - assertThat(ThreadContext.getImmutableContext()) - .hasSize(1) - .containsEntry("test", "value"); - } - - @Test - void shouldSetCustomKeyAsMapOnThreadContext() { - Map<String, String> customKeys = new HashMap<>(); - customKeys.put("test", "value"); - customKeys.put("test1", "value1"); - - LoggingUtils.appendKeys(customKeys); - - assertThat(ThreadContext.getImmutableContext()) - .hasSize(2) - .containsEntry("test", "value") - .containsEntry("test1", "value1"); - } - - @Test - void shouldRemoveCustomKeyOnThreadContext() { - LoggingUtils.appendKey("test", "value"); - - assertThat(ThreadContext.getImmutableContext()) - .hasSize(1) - .containsEntry("test", "value"); - - LoggingUtils.removeKey("test"); - - assertThat(ThreadContext.getImmutableContext()) - .isEmpty(); - } - - @Test - void shouldRemoveCustomKeysOnThreadContext() { - Map<String, String> customKeys = new HashMap<>(); - customKeys.put("test", "value"); - customKeys.put("test1", "value1"); - - LoggingUtils.appendKeys(customKeys); - - assertThat(ThreadContext.getImmutableContext()) - .hasSize(2) - .containsEntry("test", "value") - .containsEntry("test1", "value1"); - - LoggingUtils.removeKeys("test", "test1"); - - assertThat(ThreadContext.getImmutableContext()) - .isEmpty(); - } -} \ No newline at end of file diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/PowertoolsLoggingTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/PowertoolsLoggingTest.java new file mode 100644 index 000000000..9c68f687b --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/PowertoolsLoggingTest.java @@ -0,0 +1,517 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.contentOf; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.channels.FileChannel; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import com.amazonaws.services.lambda.runtime.Context; + +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; +import software.amazon.lambda.powertools.logging.internal.LoggingConstants; +import software.amazon.lambda.powertools.logging.internal.LoggingManagerRegistry; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; +import software.amazon.lambda.powertools.logging.internal.TestLoggingManager; + +class PowertoolsLoggingTest { + + private static final Logger LOG = LoggerFactory.getLogger(PowertoolsLoggingTest.class); + private TestLoggingManager testManager; + private Context context; + + @BeforeEach + void setUp() throws IllegalAccessException, IOException { + // Get the TestLoggingManager instance from registry + testManager = (TestLoggingManager) LoggingManagerRegistry.getLoggingManager(); + testManager.resetBufferState(); + + context = new TestLambdaContext(); + + // Reset environment variables for clean test isolation + writeStaticField(LoggingConstants.class, "LAMBDA_LOG_LEVEL", null, true); + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", null, true); + writeStaticField(LoggingConstants.class, "POWERTOOLS_SAMPLING_RATE", null, true); + + // Clear MDC for clean test isolation + MDC.clear(); + + // Reset cold start state + writeStaticField(LambdaHandlerProcessor.class, "isColdStart", null, true); + writeStaticField(PowertoolsLogging.class, "hasBeenInitialized", new AtomicBoolean(false), true); + + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + } + + @AfterEach + void cleanUp() throws IOException { + // Make sure file is cleaned up + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + } + + @Test + void testFlushBuffer_shouldCallBufferManager() { + // WHEN + PowertoolsLogging.flushBuffer(); + + // THEN + assertThat(testManager.isBufferFlushed()).isTrue(); + } + + @Test + void testClearBuffer_shouldCallBufferManager() { + // WHEN + PowertoolsLogging.clearBuffer(); + + // THEN + assertThat(testManager.isBufferCleared()).isTrue(); + } + + @Test + void shouldLogDebugWhenPowertoolsLevelEnvVarIsDebug() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "DEBUG", true); + + // WHEN + reinitializeLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isTrue(); + } + + @Test + void shouldLogInfoWhenPowertoolsLevelEnvVarIsInfo() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "INFO", true); + + // WHEN + reinitializeLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isTrue(); + } + + @Test + void shouldLogInfoWhenPowertoolsLevelEnvVarIsInvalid() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "INVALID", true); + + // WHEN + reinitializeLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isTrue(); + } + + @Test + void shouldLogWarnWhenPowertoolsLevelEnvVarIsWarn() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "WARN", true); + + // WHEN + reinitializeLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isFalse(); + assertThat(LOG.isWarnEnabled()).isTrue(); + } + + @Test + void shouldLogErrorWhenPowertoolsLevelEnvVarIsError() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "ERROR", true); + + // WHEN + reinitializeLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isFalse(); + assertThat(LOG.isWarnEnabled()).isFalse(); + assertThat(LOG.isErrorEnabled()).isTrue(); + } + + @Test + void shouldLogErrorWhenPowertoolsLevelEnvVarIsFatal() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "FATAL", true); + + // WHEN + reinitializeLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isFalse(); + assertThat(LOG.isWarnEnabled()).isFalse(); + assertThat(LOG.isErrorEnabled()).isTrue(); + } + + @Test + void shouldLogWarnWhenPowertoolsLevelEnvVarIsWarnAndLambdaLevelVarIsInfo() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "WARN", true); + writeStaticField(LoggingConstants.class, "LAMBDA_LOG_LEVEL", "INFO", true); + + // WHEN + reinitializeLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isFalse(); + assertThat(LOG.isWarnEnabled()).isTrue(); + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .doesNotContain(" does not match AWS Lambda Advanced Logging Controls minimum log level"); + } + + @Test + void shouldLogInfoWhenPowertoolsLevelEnvVarIsInfoAndLambdaLevelVarIsWarn() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "INFO", true); + writeStaticField(LoggingConstants.class, "LAMBDA_LOG_LEVEL", "WARN", true); + + // WHEN + reinitializeLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isTrue(); + File logFile = new File("target/logfile.json"); + // should log a warning as powertools level is lower than lambda level + assertThat(contentOf(logFile)).contains( + "Current log level (INFO) does not match AWS Lambda Advanced Logging Controls minimum log level (WARN). This can lead to data loss, consider adjusting them."); + } + + @Test + void shouldLogWarnWhenPowertoolsLevelEnvVarINotSetAndLambdaLevelVarIsWarn() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", null, true); + writeStaticField(LoggingConstants.class, "LAMBDA_LOG_LEVEL", "WARN", true); + + // WHEN + reinitializeLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isFalse(); + assertThat(LOG.isWarnEnabled()).isTrue(); + } + + @Test + void initializeLogging_withContextOnly_shouldSetLambdaFields() { + // WHEN + PowertoolsLogging.initializeLogging(context); + + // THEN + Map<String, String> mdcMap = MDC.getCopyOfContextMap(); + assertThat(mdcMap) + .containsEntry(PowertoolsLoggedFields.FUNCTION_NAME.getName(), "test-function") + .containsEntry(PowertoolsLoggedFields.FUNCTION_VERSION.getName(), "1") + .containsEntry(PowertoolsLoggedFields.FUNCTION_COLD_START.getName(), "true") + .containsEntry(PowertoolsLoggedFields.SERVICE.getName(), "testService"); + } + + @Test + void initializeLogging_withSamplingRate_shouldSetSamplingRateInMdc() { + // WHEN + PowertoolsLogging.initializeLogging(context, 0.5); + + // THEN + assertThat(MDC.get(PowertoolsLoggedFields.SAMPLING_RATE.getName())).isEqualTo("0.5"); + } + + @Test + void initializeLogging_withCorrelationId_shouldExtractFromEvent() { + // GIVEN + Map<String, Object> event = Map.of("requestContext", Map.of("requestId", "test-correlation-id")); + + // WHEN + PowertoolsLogging.initializeLogging(context, "requestContext.requestId", event); + + // THEN + assertThat(MDC.get(PowertoolsLoggedFields.CORRELATION_ID.getName())).isEqualTo("test-correlation-id"); + } + + @Test + void initializeLogging_withFullConfiguration_shouldSetAllFields() { + // GIVEN + Map<String, Object> event = Map.of("id", "correlation-123"); + + // WHEN + PowertoolsLogging.initializeLogging(context, 0.5, "id", event); + + // THEN + Map<String, String> mdcMap = MDC.getCopyOfContextMap(); + assertThat(mdcMap) + .containsEntry(PowertoolsLoggedFields.FUNCTION_NAME.getName(), "test-function") + .containsEntry(PowertoolsLoggedFields.CORRELATION_ID.getName(), "correlation-123") + .containsEntry(PowertoolsLoggedFields.SAMPLING_RATE.getName(), "0.5"); + } + + @Test + void initializeLogging_withInvalidSamplingRate_shouldSkipSampling() { + // WHEN + PowertoolsLogging.initializeLogging(context, 2.0); + + // THEN + assertThat(MDC.get(PowertoolsLoggedFields.SAMPLING_RATE.getName())).isNull(); + } + + @Test + void initializeLogging_withEnvVarAndParameter_shouldUseEnvVarPrecedence() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_SAMPLING_RATE", "0.8", true); + + // WHEN + PowertoolsLogging.initializeLogging(context, 0.3); + + // THEN + assertThat(MDC.get(PowertoolsLoggedFields.SAMPLING_RATE.getName())).isEqualTo("0.8"); + } + + @Test + void initializeLogging_calledTwice_shouldMarkColdStartDoneOnSecondCall() throws IllegalAccessException { + // GIVEN + writeStaticField(PowertoolsLogging.class, "hasBeenInitialized", new AtomicBoolean(false), true); + + // WHEN - First call + PowertoolsLogging.initializeLogging(context); + String firstCallColdStart = MDC.get(PowertoolsLoggedFields.FUNCTION_COLD_START.getName()); + + // WHEN - Second call + PowertoolsLogging.initializeLogging(context); + String secondCallColdStart = MDC.get(PowertoolsLoggedFields.FUNCTION_COLD_START.getName()); + + // THEN + assertThat(firstCallColdStart).isEqualTo("true"); + assertThat(secondCallColdStart).isEqualTo("false"); + } + + @Test + void initializeLogging_withNullContext_shouldNotThrow() { + // WHEN & THEN + assertThatNoException().isThrownBy(() -> { + PowertoolsLogging.initializeLogging(null); + PowertoolsLogging.initializeLogging(null, 0.5); + PowertoolsLogging.initializeLogging(null, "path", Map.of()); + PowertoolsLogging.initializeLogging(null, 0.5, "path", Map.of()); + }); + } + + @Test + void clearState_shouldClearMdcAndBuffer() { + // GIVEN + MDC.put("test", "value"); + + // WHEN + PowertoolsLogging.clearState(true); + + // THEN + assertThat(MDC.getCopyOfContextMap()).isNull(); + assertThat(testManager.isBufferCleared()).isTrue(); + } + + @Test + void clearState_withoutMdcClear_shouldOnlyClearBuffer() { + // GIVEN + MDC.put("test", "value"); + + // WHEN + PowertoolsLogging.clearState(false); + + // THEN + assertThat(MDC.get("test")).isEqualTo("value"); + assertThat(testManager.isBufferCleared()).isTrue(); + } + + @Test + void initializeLogging_concurrentCalls_shouldBeThreadSafe() throws InterruptedException { + // GIVEN + int threadCount = 10; + Thread[] threads = new Thread[threadCount]; + String[] samplingRates = new String[threadCount]; + boolean[] coldStarts = new boolean[threadCount]; + boolean[] success = new boolean[threadCount]; + + // WHEN - Multiple threads call initializeLogging with alternating sampling rates + for (int i = 0; i < threadCount; i++) { + final int threadIndex = i; + final double samplingRate = (i % 2 == 0) ? 1.0 : 0.0; // Alternate between 1.0 and 0.0 + + threads[i] = new Thread(() -> { + try { + PowertoolsLogging.initializeLogging(context, samplingRate); + + // Capture the sampling rate and cold start values set in MDC (thread-local) + samplingRates[threadIndex] = MDC.get(PowertoolsLoggedFields.SAMPLING_RATE.getName()); + coldStarts[threadIndex] = Boolean + .parseBoolean(MDC.get(PowertoolsLoggedFields.FUNCTION_COLD_START.getName())); + success[threadIndex] = true; + + // Clean up thread-local state + PowertoolsLogging.clearState(true); + } catch (Exception e) { + success[threadIndex] = false; + } + }); + } + + // Start all threads + for (Thread thread : threads) { + thread.start(); + } + + // Wait for all threads to complete + for (Thread thread : threads) { + thread.join(); + } + + // THEN - All threads should complete successfully + for (boolean result : success) { + assertThat(result).isTrue(); + } + + // THEN - Each thread should have its own sampling rate in MDC and exactly one invocation was a cold start + int coldStartCount = 0; + for (int i = 0; i < threadCount; i++) { + String expectedSamplingRate = (i % 2 == 0) ? "1.0" : "0.0"; + assertThat(samplingRates[i]).as("Thread %d should have sampling rate %s", i, expectedSamplingRate) + .isEqualTo(expectedSamplingRate); + + coldStartCount += coldStarts[i] ? 1 : 0; + } + assertThat(coldStartCount).isEqualTo(1); + } + + @Test + void withLogging_basicUsage_shouldInitializeAndCleanup() { + // WHEN + String result = PowertoolsLogging.withLogging(context, () -> { + assertThat(MDC.get(PowertoolsLoggedFields.FUNCTION_NAME.getName())).isEqualTo("test-function"); + return "test-result"; + }); + + // THEN + assertThat(result).isEqualTo("test-result"); + assertThat(MDC.getCopyOfContextMap()).isNull(); + assertThat(testManager.isBufferCleared()).isTrue(); + } + + @Test + void withLogging_withSamplingRate_shouldSetSamplingRateAndCleanup() { + // WHEN + String result = PowertoolsLogging.withLogging(context, 0.5, () -> { + assertThat(MDC.get(PowertoolsLoggedFields.SAMPLING_RATE.getName())).isEqualTo("0.5"); + return "sampled-result"; + }); + + // THEN + assertThat(result).isEqualTo("sampled-result"); + assertThat(MDC.getCopyOfContextMap()).isNull(); + } + + @Test + void withLogging_withCorrelationId_shouldExtractCorrelationIdAndCleanup() { + // GIVEN + Map<String, Object> event = Map.of("requestId", "correlation-123"); + + // WHEN + Integer result = PowertoolsLogging.withLogging(context, "requestId", event, () -> { + assertThat(MDC.get(PowertoolsLoggedFields.CORRELATION_ID.getName())).isEqualTo("correlation-123"); + return 42; + }); + + // THEN + assertThat(result).isEqualTo(42); + assertThat(MDC.getCopyOfContextMap()).isNull(); + } + + @Test + void withLogging_withFullConfiguration_shouldSetAllFieldsAndCleanup() { + // GIVEN + Map<String, Object> event = Map.of("id", "full-correlation"); + + // WHEN + Boolean result = PowertoolsLogging.withLogging(context, 0.8, "id", event, () -> { + Map<String, String> mdcMap = MDC.getCopyOfContextMap(); + assertThat(mdcMap) + .containsEntry(PowertoolsLoggedFields.FUNCTION_NAME.getName(), "test-function") + .containsEntry(PowertoolsLoggedFields.CORRELATION_ID.getName(), "full-correlation") + .containsEntry(PowertoolsLoggedFields.SAMPLING_RATE.getName(), "0.8"); + return true; + }); + + // THEN + assertThat(result).isTrue(); + assertThat(MDC.getCopyOfContextMap()).isNull(); + } + + @Test + void withLogging_whenSupplierThrowsException_shouldStillCleanup() { + // WHEN & THEN + try { + PowertoolsLogging.withLogging(context, () -> { + assertThat(MDC.get(PowertoolsLoggedFields.FUNCTION_NAME.getName())).isEqualTo("test-function"); + throw new RuntimeException("test exception"); + }); + } catch (RuntimeException e) { + assertThat(e.getMessage()).isEqualTo("test exception"); + } + + // THEN - cleanup should still happen + assertThat(MDC.getCopyOfContextMap()).isNull(); + assertThat(testManager.isBufferCleared()).isTrue(); + } + + private void reinitializeLogLevel() { + try { + Method initializeLogLevel = PowertoolsLogging.class.getDeclaredMethod("initializeLogLevel"); + initializeLogLevel.setAccessible(true); + initializeLogLevel.invoke(null); + } catch (Exception e) { + throw new RuntimeException("Failed to reinitialize log level", e); + } + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/argument/StructuredArgumentsTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/argument/StructuredArgumentsTest.java new file mode 100644 index 000000000..b478ac0f0 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/argument/StructuredArgumentsTest.java @@ -0,0 +1,157 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.argument; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import software.amazon.lambda.powertools.logging.internal.JsonSerializer; +import software.amazon.lambda.powertools.logging.model.Basket; +import software.amazon.lambda.powertools.logging.model.Product; + +class StructuredArgumentsTest { + private StringBuilder sb; + private JsonSerializer serializer; + + @BeforeEach + void setUp() { + sb = new StringBuilder(); + serializer = new JsonSerializer(sb); + } + + @Test + void keyValueArgument() throws IOException { + // GIVEN + Basket basket = new Basket(); + basket.add(new Product(42, "Nintendo DS", 299.45)); + basket.add(new Product(98, "Playstation 5", 499.99)); + + // WHEN + StructuredArgument argument = StructuredArguments.entry("basket", basket); + argument.writeTo(serializer); + + // THEN + assertThat(sb.toString()).hasToString( + "\"basket\":{\"products\":[{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45},{\"id\":98,\"name\":\"Playstation 5\",\"price\":499.99}]}"); + assertThat(argument.toString()).hasToString( + "basket=Basket{products=[Product{id=42, name='Nintendo DS', price=299.45}, Product{id=98, name='Playstation 5', price=499.99}]}"); + } + + @Test + void mapArgument() throws IOException { + // GIVEN + Map<String, Product> catalog = new HashMap<>(); + catalog.put("nds", new Product(42, "Nintendo DS", 299.45)); + catalog.put("ps5", new Product(98, "Playstation 5", 499.99)); + + // WHEN + StructuredArgument argument = StructuredArguments.entries(catalog); + argument.writeTo(serializer); + + // THEN + assertThat(sb.toString()) + .contains("\"nds\":{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45},") + .contains("\"ps5\":{\"id\":98,\"name\":\"Playstation 5\",\"price\":499.99}"); + assertThat(argument.toString()) + .contains("nds=Product{id=42, name='Nintendo DS', price=299.45}") + .contains("ps5=Product{id=98, name='Playstation 5', price=499.99}"); + } + + @Test + void emptyMapArgument() throws IOException { + // GIVEN + Map<String, Product> catalog = new HashMap<>(); + + // WHEN + assertNull(StructuredArguments.entries(null)); + StructuredArgument argument = StructuredArguments.entries(catalog); + argument.writeTo(serializer); + + // THEN + assertThat(sb.toString()).isEmpty(); + assertThat(argument.toString()).hasToString("{}"); + } + + @Test + void arrayArgument() throws IOException { + // GIVEN + Product[] products = new Product[] { + new Product(42, "Nintendo DS", 299.45), + new Product(98, "Playstation 5", 499.99) + }; + + // WHEN + StructuredArgument argument = StructuredArguments.array("products", products); + argument.writeTo(serializer); + + // THEN + assertThat(sb.toString()).contains( + "\"products\":[{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45},{\"id\":98,\"name\":\"Playstation 5\",\"price\":499.99}]"); + assertThat(argument.toString()).contains( + "products=[Product{id=42, name='Nintendo DS', price=299.45}, Product{id=98, name='Playstation 5', price=499.99}]"); + } + + @Test + void jsonArgument() throws IOException { + // GIVEN + String rawJson = "{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45}"; + + // WHEN + StructuredArgument argument = StructuredArguments.json("product", rawJson); + argument.writeTo(serializer); + + // THEN + assertThat(sb.toString()).contains("\"product\":{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45}"); + assertThat(argument.toString()).contains("product={\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45}"); + } + + @Test + void reservedKeywordArgumentIgnored() throws IOException { + // GIVEN + Basket basket = new Basket(); + basket.add(new Product(42, "Nintendo DS", 299.45)); + basket.add(new Product(98, "Playstation 5", 499.99)); + Product[] products = new Product[] { + new Product(42, "Nintendo DS", 299.45), + new Product(98, "Playstation 5", 499.99) + }; + String rawJson = "{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45}"; + Map<String, Product> catalog = new HashMap<>(); + catalog.put("nds", new Product(42, "Nintendo DS", 299.45)); + catalog.put("message", new Product(98, "Playstation 5", 499.99)); + + // THEN + assertNull(StructuredArguments.entry("message", basket)); + assertNull(StructuredArguments.array("message", products)); + assertNull(StructuredArguments.json("message", rawJson)); + + StructuredArgument mapArg = StructuredArguments.entries(catalog); + mapArg.writeTo(serializer); + assertThat(sb.toString()) + .contains("\"nds\":{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45}"); + assertThat(sb.toString()).doesNotContain("message"); + assertThat(mapArg.toString()) + .contains("nds=Product{id=42, name='Nintendo DS', price=299.45}"); + assertThat(mapArg.toString()).doesNotContain("message"); + } + +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledWithCustomMapper.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledWithCustomMapper.java deleted file mode 100644 index d761c9ac0..000000000 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledWithCustomMapper.java +++ /dev/null @@ -1,49 +0,0 @@ -package software.amazon.lambda.powertools.logging.handlers; - -import java.io.IOException; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.logging.LoggingUtils; - -public class PowerToolLogEventEnabledWithCustomMapper implements RequestHandler<S3EventNotification, Object> { - - static { - ObjectMapper objectMapper = new ObjectMapper(); - SimpleModule module = new SimpleModule(); - module.addSerializer(S3EventNotification.class, new S3EventNotificationSerializer()); - objectMapper.registerModule(module); - LoggingUtils.defaultObjectMapper(objectMapper); - } - - @Logging(logEvent = true) - @Override - public Object handleRequest(S3EventNotification input, Context context) { - return null; - } - - static class S3EventNotificationSerializer extends StdSerializer<S3EventNotification> { - - public S3EventNotificationSerializer() { - this(null); - } - - public S3EventNotificationSerializer(Class<S3EventNotification> t) { - super(t); - } - - @Override - public void serialize(S3EventNotification o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeStartObject(); - jsonGenerator.writeStringField("eventSource", o.getRecords().get(0).getEventSource()); - jsonGenerator.writeEndObject(); - } - } -} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolAlbCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java similarity index 68% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolAlbCorrelationId.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java index 125c13e26..065d4c5b0 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolAlbCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,21 +11,20 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.logging.handlers; +import static software.amazon.lambda.powertools.logging.CorrelationIdPaths.APPLICATION_LOAD_BALANCER; + import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import software.amazon.lambda.powertools.logging.CorrelationIdPathConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; -import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.API_GATEWAY_REST; -import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.APPLICATION_LOAD_BALANCER; - -public class PowerLogToolAlbCorrelationId implements RequestHandler<ApplicationLoadBalancerRequestEvent, Object> { - private final Logger LOG = LogManager.getLogger(PowerLogToolAlbCorrelationId.class); +public class PowertoolsLogAlbCorrelationId implements RequestHandler<ApplicationLoadBalancerRequestEvent, Object> { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogAlbCorrelationId.class); @Override @Logging(correlationIdPath = APPLICATION_LOAD_BALANCER) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayHttpApiCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayHttpApiCorrelationId.java similarity index 75% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayHttpApiCorrelationId.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayHttpApiCorrelationId.java index 4e40e0f97..922a09f13 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayHttpApiCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayHttpApiCorrelationId.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,19 +11,20 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.logging.handlers; +import static software.amazon.lambda.powertools.logging.CorrelationIdPaths.API_GATEWAY_HTTP; + import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; -import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.API_GATEWAY_HTTP; - -public class PowerLogToolApiGatewayHttpApiCorrelationId implements RequestHandler<APIGatewayV2HTTPEvent, Object> { - private final Logger LOG = LogManager.getLogger(PowerLogToolApiGatewayHttpApiCorrelationId.class); +public class PowertoolsLogApiGatewayHttpApiCorrelationId implements RequestHandler<APIGatewayV2HTTPEvent, Object> { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogApiGatewayHttpApiCorrelationId.class); @Override @Logging(correlationIdPath = API_GATEWAY_HTTP) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayRestApiCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayRestApiCorrelationId.java similarity index 74% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayRestApiCorrelationId.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayRestApiCorrelationId.java index e3cadaf84..7271e1d24 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayRestApiCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayRestApiCorrelationId.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,19 +11,20 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.logging.handlers; +import static software.amazon.lambda.powertools.logging.CorrelationIdPaths.API_GATEWAY_REST; + import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; -import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.API_GATEWAY_REST; - -public class PowerLogToolApiGatewayRestApiCorrelationId implements RequestHandler<APIGatewayProxyRequestEvent, Object> { - private final Logger LOG = LogManager.getLogger(PowerLogToolApiGatewayRestApiCorrelationId.class); +public class PowertoolsLogApiGatewayRestApiCorrelationId implements RequestHandler<APIGatewayProxyRequestEvent, Object> { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogApiGatewayRestApiCorrelationId.class); @Override @Logging(correlationIdPath = API_GATEWAY_REST) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAppSyncCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAppSyncCorrelationId.java new file mode 100644 index 000000000..fbe2bc89b --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAppSyncCorrelationId.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.handlers; + +import static software.amazon.lambda.powertools.logging.CorrelationIdPaths.APPSYNC_RESOLVER; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogAppSyncCorrelationId implements RequestStreamHandler { + + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogAppSyncCorrelationId.class); + + @Override + @Logging(correlationIdPath = APPSYNC_RESOLVER) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + LOG.info("Test event"); + } +} \ No newline at end of file diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabledWithClearState.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogClearState.java similarity index 60% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabledWithClearState.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogClearState.java index 8fef32c94..cb7fbb408 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabledWithClearState.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogClearState.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,26 +11,25 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.logging.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.logging.LoggingUtils; -public class PowerLogToolEnabledWithClearState implements RequestHandler<Object, Object> { - private final Logger LOG = LogManager.getLogger(PowerLogToolEnabledWithClearState.class); - public static int COUNT = 1; +public class PowertoolsLogClearState implements RequestHandler<Map<String, String>, Object> { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogClearState.class); + @Override @Logging(clearState = true) - public Object handleRequest(Object input, Context context) { - if(COUNT == 1) { - LoggingUtils.appendKey("TestKey", "TestValue"); - } + public Object handleRequest(Map<String, String> input, Context context) { + MDC.put("mySuperSecret", input.get("mySuperSecret")); LOG.info("Test event"); - COUNT++; return null; } } diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogDisabled.java similarity index 86% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabled.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogDisabled.java index 0391a5177..54e887e40 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabled.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogDisabled.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,12 +11,13 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.logging.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -public class PowerToolDisabled implements RequestHandler<Object, Object> { +public class PowertoolsLogDisabled implements RequestHandler<Object, Object> { @Override public Object handleRequest(Object input, Context context) { diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabledForStream.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogDisabledForStream.java similarity index 87% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabledForStream.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogDisabledForStream.java index f0f7f676e..7f7418ed6 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabledForStream.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogDisabledForStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,15 +11,15 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.logging.handlers; -import java.io.InputStream; -import java.io.OutputStream; +package software.amazon.lambda.powertools.logging.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import java.io.InputStream; +import java.io.OutputStream; -public class PowerToolDisabledForStream implements RequestStreamHandler { +public class PowertoolsLogDisabledForStream implements RequestStreamHandler { @Override public void handleRequest(InputStream input, OutputStream output, Context context) { diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java new file mode 100644 index 000000000..aa0a5942c --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogEnabled implements RequestHandler<Object, Object> { + private static final Logger LOG = LoggerFactory.getLogger(PowertoolsLogEnabled.class); + private final boolean throwError; + + public PowertoolsLogEnabled(boolean throwError) { + this.throwError = throwError; + } + + public PowertoolsLogEnabled() { + this(false); + } + + @Override + @Logging + public Object handleRequest(Object input, Context context) { + if (throwError) { + throw new RuntimeException("Something went wrong"); + } + LOG.error("Test error event"); + LOG.warn("Test warn event"); + LOG.info("Test event"); + LOG.debug("Test debug event"); + return "Bonjour le monde"; + } + + @Logging + public void anotherMethod() { + System.out.println("test"); + } + + public static Logger getLogger() { + return LOG; + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabledForStream.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledForStream.java similarity index 88% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabledForStream.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledForStream.java index e2c2d66d0..c95627302 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabledForStream.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledForStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,16 +11,16 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.logging.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import software.amazon.lambda.powertools.logging.Logging; - import java.io.InputStream; import java.io.OutputStream; +import software.amazon.lambda.powertools.logging.Logging; -public class PowerLogToolEnabledForStream implements RequestStreamHandler { +public class PowertoolsLogEnabledForStream implements RequestStreamHandler { @Logging @Override diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogError.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogError.java new file mode 100644 index 000000000..a6b1ed915 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogError.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogError implements RequestHandler<Object, Object> { + + @Override + @Logging(logError = true) + public Object handleRequest(Object input, Context context) { + throw new UnsupportedOperationException("This is an error"); + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogErrorNoFlush.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogErrorNoFlush.java new file mode 100644 index 000000000..87515654c --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogErrorNoFlush.java @@ -0,0 +1,15 @@ +package software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogErrorNoFlush implements RequestHandler<String, String> { + + @Override + @Logging(logError = true, flushBufferOnUncaughtError = false) + public String handleRequest(String input, Context context) { + throw new RuntimeException("This is an error without buffer flush"); + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEvent.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEvent.java new file mode 100644 index 000000000..c83692e95 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEvent.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogEvent implements RequestHandler<Object, Object> { + + private static final Logger logger = LoggerFactory.getLogger(PowertoolsLogEvent.class); + + @Override + @Logging(logEvent = true) + public Object handleRequest(Object input, Context context) { + return null; + } + + public static Logger getLogger() { + return logger; + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java new file mode 100644 index 000000000..04d56d38c --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.handlers; + +import static software.amazon.lambda.powertools.logging.CorrelationIdPaths.EVENT_BRIDGE; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogEventBridgeCorrelationId implements RequestStreamHandler { + + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogEventBridgeCorrelationId.class); + + @Override + @Logging(correlationIdPath = EVENT_BRIDGE) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + LOG.info("Test event"); + } +} \ No newline at end of file diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventEnvVar.java similarity index 67% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabled.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventEnvVar.java index e154bbcf3..230394bf7 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabled.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventEnvVar.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,27 +11,26 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.logging.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; -public class PowerLogToolEnabled implements RequestHandler<Object, Object> { - private final Logger LOG = LogManager.getLogger(PowerLogToolEnabled.class); +public class PowertoolsLogEventEnvVar implements RequestHandler<Object, Object> { + + private final Logger logger = LoggerFactory.getLogger(PowertoolsLogEventEnvVar.class); @Override @Logging public Object handleRequest(Object input, Context context) { - LOG.info("Test event"); - LOG.debug("Test debug event"); return null; } - @Logging - public void anotherMethod() { - System.out.println("test"); + public Logger getLogger() { + return logger; } } diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventForStream.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventForStream.java new file mode 100644 index 000000000..6e27f47ce --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventForStream.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogEventForStream implements RequestStreamHandler { + + private static final Logger logger = LoggerFactory.getLogger(PowertoolsLogEventForStream.class); + + @Override + @Logging(logEvent = true) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(outputStream, mapper.readValue(inputStream, Map.class)); + } + + public static Logger getLogger() { + return logger; + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponse.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponse.java new file mode 100644 index 000000000..e7607d3c9 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponse.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogResponse implements RequestHandler<Object, Object> { + private static final Logger logger = LoggerFactory.getLogger(PowertoolsLogResponse.class); + + public static Logger getLogger() { + return logger; + } + + @Override + @Logging(logResponse = true) + public Object handleRequest(Object input, Context context) { + return "Hola mundo"; + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponseForStream.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponseForStream.java new file mode 100644 index 000000000..fe591627b --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponseForStream.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogResponseForStream implements RequestStreamHandler { + + private static final Logger logger = LoggerFactory.getLogger(PowertoolsLogResponseForStream.class); + + @Override + @Logging(logResponse = true) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + byte[] buf = new byte[1024]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + } + + public static Logger getLogger() { + return logger; + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingDisabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingDisabled.java new file mode 100644 index 000000000..5e2a7f148 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingDisabled.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogSamplingDisabled implements RequestHandler<Object, Boolean> { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogSamplingDisabled.class); + + @Override + @Logging(samplingRate = 0.0) + public Boolean handleRequest(Object input, Context context) { + LOG.info("Test event"); + LOG.debug("Test debug event"); + return LOG.isDebugEnabled(); + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolSamplingEnabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingEnabled.java similarity index 69% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolSamplingEnabled.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingEnabled.java index 9d3d68e2e..6a8c37896 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolSamplingEnabled.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingEnabled.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,22 +11,23 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.logging.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; -public class PowerLogToolSamplingEnabled implements RequestHandler<Object, Object> { - private final Logger LOG = LogManager.getLogger(PowerLogToolSamplingEnabled.class); +public class PowertoolsLogSamplingEnabled implements RequestHandler<Object, Boolean> { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogSamplingEnabled.class); @Override @Logging(samplingRate = 1.0) - public Object handleRequest(Object input, Context context) { + public Boolean handleRequest(Object input, Context context) { LOG.info("Test event"); LOG.debug("Test debug event"); - return null; + return LOG.isDebugEnabled(); } } diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/JsonSerializerTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/JsonSerializerTest.java new file mode 100644 index 000000000..8b34d32cb --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/JsonSerializerTest.java @@ -0,0 +1,149 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.JsonNode; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.lambda.powertools.logging.model.Basket; +import software.amazon.lambda.powertools.logging.model.Product; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +class JsonSerializerTest { + private StringBuilder sb; + private JsonSerializer generator; + + @BeforeEach + void setUp() { + sb = new StringBuilder(); + generator = new JsonSerializer(sb); + } + + @Test + void writeString_shouldWriteStringWithQuotes() throws IOException { + generator.writeStringField("key", "StringValue"); + assertThat(sb.toString()).hasToString("\"key\":\"StringValue\""); + } + + @Test + void writeBoolean_shouldWriteBooleanValue() throws IOException { + generator.writeBooleanField("key", true); + assertThat(sb.toString()).hasToString("\"key\":true"); + } + + @Test + void writeNull_shouldWriteNullValue() throws IOException { + generator.writeNullField("key"); + assertThat(sb.toString()).hasToString("\"key\":null"); + } + + @Test + void writeInt_shouldWriteIntValue() throws IOException { + generator.writeNumberField("key", 1); + assertThat(sb.toString()).hasToString("\"key\":1"); + } + + @Test + void writeFloat_shouldWriteFloatValue() throws IOException { + generator.writeNumberField("key", 2.4f); + assertThat(sb.toString()).hasToString("\"key\":2.4"); + assertThat(sb.toString()).doesNotContain("F").doesNotContain("f"); // should not contain the F suffix for floats. + } + + @Test + void writeDouble_shouldWriteDoubleValue() throws IOException { + generator.writeNumberField("key", 4.3); + assertThat(sb.toString()).hasToString("\"key\":4.3"); + } + + @Test + void writeLong_shouldWriteLongValue() throws IOException { + generator.writeNumberField("key", 123456789L); + assertThat(sb.toString()).hasToString("\"key\":123456789"); + assertThat(sb.toString()).doesNotContain("L").doesNotContain("l"); // should not contain the L suffix for longs. + } + + @Test + void writeBigDecimal_shouldWriteBigDecimal() throws IOException { + generator.writeNumberField("key", BigDecimal.valueOf(432.1673254564546)); + assertThat(sb.toString()).hasToString("\"key\":432.1673254564546"); + } + + @Test + void writeStringObject_shouldWriteStringWithQuotes() throws IOException { + generator.writeObjectField("key","StringValue"); + assertThat(sb.toString()).hasToString("\"key\":\"StringValue\""); + } + + @Test + void writeMapObject_shouldWriteMapAsJson() throws IOException { + Map<String, Object> map = new HashMap<>(); + map.put("string","StringValue"); + map.put("number", BigInteger.valueOf(1234567890L)); + generator.writeObjectField("map", map); + assertThat(sb.toString()).hasToString("\"map\":{\"number\":1234567890,\"string\":\"StringValue\"}"); + } + + @Test + void writeListObject_shouldWriteListAsArray() throws IOException { + List<String> list = Arrays.asList("val1", "val2", "val3"); + generator.writeObjectField("list", list); + assertThat(sb.toString()).hasToString("\"list\":[\"val1\",\"val2\",\"val3\"]"); + } + + @Test + void writeCustomObject_shouldWriteObjectAsJson() throws IOException { + Basket basket = new Basket(); + basket.add(new Product(42, "Nintendo DS", 299.45)); + basket.add(new Product(98, "Playstation 5", 499.99)); + generator.writeObjectField("basket", basket); + assertThat(sb.toString()).hasToString("\"basket\":{\"products\":[{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45},{\"id\":98,\"name\":\"Playstation 5\",\"price\":499.99}]}"); + } + + @Test + void writeJsonNodeObject_shouldWriteObjectAsJson() throws IOException { + JsonNode jsonNode = JsonConfig.get().getObjectMapper().readTree( + "[{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45},{\"id\":98,\"name\":\"Playstation 5\",\"price\":499.99}]"); + generator.writeObjectField("basket", jsonNode); + assertThat(sb.toString()).hasToString("\"basket\":[{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45},{\"id\":98,\"name\":\"Playstation 5\",\"price\":499.99}]"); + } + + @Test + void writeTreeNodeArrayObject_shouldWriteObjectAsJson() throws IOException { + TreeNode treeNode = JsonConfig.get().getObjectMapper().readTree( + "[{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45},{\"id\":98,\"name\":\"Playstation 5\",\"price\":499.99}]"); + generator.writeTree(treeNode); + assertThat(sb.toString()).hasToString("[{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45},{\"id\":98,\"name\":\"Playstation 5\",\"price\":499.99}]"); + } + + @Test + void writeTreeNodeObject_shouldWriteObjectAsJson() throws IOException { + TreeNode treeNode = JsonConfig.get().getObjectMapper().readTree( + "{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45}"); + generator.writeTree(treeNode); + assertThat(sb.toString()).hasToString("{\"id\":42,\"name\":\"Nintendo DS\",\"price\":299.45}"); + } + +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/KeyBufferTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/KeyBufferTest.java new file mode 100644 index 000000000..fac85e230 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/KeyBufferTest.java @@ -0,0 +1,361 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.channels.FileChannel; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Deque; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class KeyBufferTest { + + private KeyBuffer<String, String> buffer; + private static final int MAX_BYTES = 20; + + @BeforeEach + void setUp() throws IOException { + buffer = new KeyBuffer<>(MAX_BYTES, String::length); + // Clean up log file before each test + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + } + + @AfterEach + void cleanUp() throws IOException { + // Make sure file is cleaned up after each test + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + } + + @Test + void shouldAddEventToBuffer() { + buffer.add("key1", "test"); + + Deque<String> events = buffer.removeAll("key1"); + assertThat(events).containsExactly("test"); + } + + @Test + void shouldMaintainSeparateBuffersPerKey() { + buffer.add("key1", "event1"); + buffer.add("key2", "event2"); + + Deque<String> events1 = buffer.removeAll("key1"); + Deque<String> events2 = buffer.removeAll("key2"); + + assertThat(events1).containsExactly("event1"); + assertThat(events2).containsExactly("event2"); + } + + @Test + void shouldMaintainFIFOOrder() { + buffer.add("key1", "first"); + buffer.add("key1", "second"); + buffer.add("key1", "third"); + + Deque<String> events = buffer.removeAll("key1"); + assertThat(events).containsExactly("first", "second", "third"); + } + + @Test + void shouldEvictOldestEventsWhenBufferOverflows() { + // Add events that total exactly maxBytes + buffer.add("key1", "12345678901234567890"); // 20 bytes + + // Add another event that causes overflow + buffer.add("key1", "extra"); + + Deque<String> events = buffer.removeAll("key1"); + assertThat(events).containsExactly("extra"); + } + + @Test + void shouldEvictMultipleEventsIfNeeded() { + buffer.add("key1", "1234567890"); // 10 bytes + buffer.add("key1", "1234567890"); // 10 bytes, total 20 + buffer.add("key1", "12345678901234567890"); // 20 bytes, should evict both previous + + Deque<String> events = buffer.removeAll("key1"); + assertThat(events).containsExactly("12345678901234567890"); + } + + @Test + void shouldEvictMultipleSmallEventsForLargeValidEvent() { + // Add many small events that fill the buffer + buffer.add("key1", "12"); // 2 bytes + buffer.add("key1", "34"); // 2 bytes, total 4 + buffer.add("key1", "56"); // 2 bytes, total 6 + buffer.add("key1", "78"); // 2 bytes, total 8 + buffer.add("key1", "90"); // 2 bytes, total 10 + buffer.add("key1", "ab"); // 2 bytes, total 12 + buffer.add("key1", "cd"); // 2 bytes, total 14 + buffer.add("key1", "ef"); // 2 bytes, total 16 + buffer.add("key1", "gh"); // 2 bytes, total 18 + buffer.add("key1", "ij"); // 2 bytes, total 20 (exactly at limit) + + // Add a large event that requires multiple evictions + buffer.add("key1", "123456789012345678"); // 18 bytes, should evict multiple small events + + Deque<String> events = buffer.removeAll("key1"); + // Should only contain the last few small events plus the large event + // 18 bytes for large event leaves 2 bytes, so only "ij" should remain with the large event + assertThat(events).containsExactly("ij", "123456789012345678"); + } + + @Test + void shouldRejectEventLargerThanMaxBytes() { + String largeEvent = "123456789012345678901"; // 21 bytes > 20 max + + buffer.add("key1", largeEvent); + + Deque<String> events = buffer.removeAll("key1"); + assertThat(events).isNull(); + } + + @Test + void shouldNotEvictExistingEventsWhenRejectingLargeEvent() { + buffer.add("key1", "small"); + + String largeEvent = "123456789012345678901"; // 21 bytes > 20 max + buffer.add("key1", largeEvent); + + Deque<String> events = buffer.removeAll("key1"); + assertThat(events).containsExactly("small"); + } + + @Test + void shouldClearSpecificKeyBuffer() { + buffer.add("key1", "event1"); + buffer.add("key2", "event2"); + + buffer.clear("key1"); + + assertThat(buffer.removeAll("key1")).isNull(); + assertThat(buffer.removeAll("key2")).containsExactly("event2"); + } + + @Test + void shouldReturnNullForNonExistentKey() { + Deque<String> events = buffer.removeAll("nonexistent"); + assertThat(events).isNull(); + } + + @Test + void shouldReturnDefensiveCopyOnRemoveAll() { + buffer.add("key1", "event"); + + Deque<String> events1 = buffer.removeAll("key1"); + buffer.add("key1", "event"); + Deque<String> events2 = buffer.removeAll("key1"); + + // Modifying first copy shouldn't affect second + events1.add("modified"); + assertThat(events2).containsExactly("event"); + assertThat(events1).containsExactly("event", "modified"); + } + + @Test + void shouldLogWarningOnOverflow() { + StringBuilder warningCapture = new StringBuilder(); + KeyBuffer<String, String> testBuffer = new KeyBuffer<>(10, String::length, + () -> warningCapture.append("Some logs are not displayed because they were evicted from the buffer")); + + // Cause overflow + testBuffer.add("key1", "1234567890"); // 10 bytes + testBuffer.add("key1", "extra"); // causes overflow + + // Trigger warning by removing + testBuffer.removeAll("key1"); + + assertThat(warningCapture.toString()) + .contains("Some logs are not displayed because they were evicted from the buffer"); + } + + @Test + void shouldLogWarningOnLargeEventRejection() { + StringBuilder warningCapture = new StringBuilder(); + KeyBuffer<String, String> testBuffer = new KeyBuffer<>(10, String::length, + () -> warningCapture.append("Some logs are not displayed because they were evicted from the buffer")); + + // Add large event that gets rejected + testBuffer.add("key1", "12345678901"); // 11 bytes > 10 max + + // Trigger warning by removing + testBuffer.removeAll("key1"); + + assertThat(warningCapture.toString()) + .contains("Some logs are not displayed because they were evicted from the buffer"); + } + + @Test + void shouldNotLogWarningWhenNoOverflow() { + StringBuilder warningCapture = new StringBuilder(); + KeyBuffer<String, String> testBuffer = new KeyBuffer<>(20, String::length, + () -> warningCapture.append("Some logs are not displayed because they were evicted from the buffer")); + + testBuffer.add("key1", "small"); + testBuffer.removeAll("key1"); + + assertThat(warningCapture.toString()) + .doesNotContain("Some logs are not displayed because they were evicted from the buffer"); + } + + @Test + void shouldBeThreadSafeForDifferentKeys() throws InterruptedException, IOException { + int threadCount = 10; + int eventsPerThread = 100; + KeyBuffer<String, String> largeBuffer = new KeyBuffer<>(10000, String::length); + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + try (Closeable ignored = executor::shutdown) { + CountDownLatch latch = new CountDownLatch(threadCount); + + // Each thread works with different key + for (int i = 0; i < threadCount; i++) { + final String key = "key" + i; + executor.submit(() -> { + try { + for (int j = 0; j < eventsPerThread; j++) { + largeBuffer.add(key, "event" + j); + } + } finally { + latch.countDown(); + } + }); + } + + assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue(); + + // Verify each key has its events + for (int i = 0; i < threadCount; i++) { + String key = "key" + i; + Deque<String> events = largeBuffer.removeAll(key); + assertThat(events) + .isNotNull() + .hasSize(eventsPerThread); + } + } + } + + @Test + void shouldBeThreadSafeForSameKey() throws InterruptedException, IOException { + int threadCount = 5; + int eventsPerThread = 20; + KeyBuffer<String, String> largeBuffer = new KeyBuffer<>(10000, String::length); + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + try (Closeable ignored = executor::shutdown) { + CountDownLatch latch = new CountDownLatch(threadCount); + + // All threads work with same key + for (int i = 0; i < threadCount; i++) { + final int threadId = i; + executor.submit(() -> { + try { + for (int j = 0; j < eventsPerThread; j++) { + largeBuffer.add("sharedKey", "t" + threadId + "e" + j); + } + } finally { + latch.countDown(); + } + }); + } + + assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue(); + + Deque<String> events = largeBuffer.removeAll("sharedKey"); + assertThat(events) + .isNotNull() + .hasSize(threadCount * eventsPerThread); + } + } + + @Test + void shouldHandleEmptyBuffer() { + buffer.clear("nonexistent"); + assertThat(buffer.removeAll("nonexistent")).isNull(); + } + + @Test + void shouldHandleZeroSizeEvents() { + KeyBuffer<String, String> zeroBuffer = new KeyBuffer<>(10, s -> 0); + + zeroBuffer.add("key1", "event1"); + zeroBuffer.add("key1", "event2"); + + Deque<String> events = zeroBuffer.removeAll("key1"); + assertThat(events).containsExactly("event1", "event2"); + } + + @Test + void shouldUseCustomWarningLogger() { + StringBuilder customWarning = new StringBuilder(); + KeyBuffer<String, String> testBuffer = new KeyBuffer<>(5, String::length, + () -> customWarning.append("CUSTOM WARNING LOGGED")); + + // Cause overflow + testBuffer.add("key1", "12345"); // 5 bytes + testBuffer.add("key1", "extra"); // causes overflow + + // Trigger warning + testBuffer.removeAll("key1"); + + assertThat(customWarning).hasToString("CUSTOM WARNING LOGGED"); + } + + @Test + void shouldUseDefaultWarningLoggerWhenNotProvided() { + // Capture System.err output + ByteArrayOutputStream errCapture = new ByteArrayOutputStream(); + @SuppressWarnings("PMD.CloseResource") // System.err is not ours to close + PrintStream originalErr = System.err; + try (PrintStream newErr = new PrintStream(errCapture)) { + System.setErr(newErr); + + KeyBuffer<String, String> defaultBuffer = new KeyBuffer<>(5, String::length); + + // Cause overflow + defaultBuffer.add("key1", "12345"); + defaultBuffer.add("key1", "extra"); + defaultBuffer.removeAll("key1"); + + // Assert System.err received the warning + assertThat(errCapture) + .hasToString( + "WARN [KeyBuffer] - Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer.\n"); + } finally { + System.setErr(originalErr); + } + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index e2cb58453..ca47f9097 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,23 +11,46 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.logging.internal; -import java.io.BufferedReader; +import static java.util.Collections.singletonList; +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_ARN; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_NAME; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_REQUEST_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_TRACE_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_VERSION; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SERVICE; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junitpioneer.jupiter.ClearEnvironmentVariable; +import org.junitpioneer.jupiter.SetEnvironmentVariable; +import org.junitpioneer.jupiter.SetSystemProperty; +import org.slf4j.MDC; +import org.slf4j.event.Level; +import org.slf4j.test.TestLogger; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; @@ -35,50 +58,33 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; -import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; import com.amazonaws.services.lambda.runtime.tests.annotations.Event; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.ThreadContext; -import org.json.JSONException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; -import software.amazon.lambda.powertools.core.internal.SystemWrapper; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolAlbCorrelationId; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolApiGatewayHttpApiCorrelationId; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolApiGatewayRestApiCorrelationId; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabled; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabledForStream; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabledWithClearState; -import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabled; -import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabledForStream; -import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabled; -import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabledForStream; -import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabledWithCustomMapper; - -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.RequestParametersEntity; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.ResponseElementsEntity; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3BucketEntity; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3Entity; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3EventNotificationRecord; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3ObjectEntity; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.UserIdentityEntity; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.joining; -import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; -import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; -import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv; + +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; +import software.amazon.lambda.powertools.logging.PowertoolsLogging; +import software.amazon.lambda.powertools.logging.argument.StructuredArgument; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogAlbCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogApiGatewayHttpApiCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogApiGatewayRestApiCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogAppSyncCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogClearState; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogDisabled; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogDisabledForStream; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEnabled; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEnabledForStream; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogError; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogErrorNoFlush; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEvent; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventBridgeCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventEnvVar; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventForStream; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogResponse; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogResponseForStream; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogSamplingDisabled; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogSamplingEnabled; class LambdaLoggingAspectTest { @@ -86,186 +92,446 @@ class LambdaLoggingAspectTest { private RequestStreamHandler requestStreamHandler; private RequestHandler<Object, Object> requestHandler; - @Mock private Context context; @BeforeEach - void setUp() throws IllegalAccessException, IOException, NoSuchMethodException, InvocationTargetException { - openMocks(this); - ThreadContext.clearAll(); - writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true); - setupContext(); - requestHandler = new PowerLogToolEnabled(); - requestStreamHandler = new PowerLogToolEnabledForStream(); - //Make sure file is cleaned up before running full stack logging regression - FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); - resetLogLevel(Level.INFO); + void setUp() throws IllegalAccessException, IOException { + MDC.clear(); + + // Reset cold start state + writeStaticField(LambdaHandlerProcessor.class, "isColdStart", null, true); + writeStaticField(PowertoolsLogging.class, "hasBeenInitialized", new AtomicBoolean(false), true); + + context = new TestLambdaContext(); + requestHandler = new PowertoolsLogEnabled(); + requestStreamHandler = new PowertoolsLogEnabledForStream(); + writeStaticField(LoggingConstants.class, "LAMBDA_LOG_LEVEL", null, true); + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", null, true); + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_EVENT", false, true); + writeStaticField(LoggingConstants.class, "POWERTOOLS_SAMPLING_RATE", null, true); + + // Reset buffer state for clean test isolation + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + if (loggingManager instanceof TestLoggingManager) { + ((TestLoggingManager) loggingManager).resetBufferState(); + } + + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + } + + @AfterEach + void cleanUp() throws IOException { + // Make sure file is cleaned up before running full stack logging regression + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } + + // Reset log level to INFO to ensure test isolation + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + loggingManager.setLogLevel(Level.INFO); } @Test void shouldSetLambdaContextWhenEnabled() { requestHandler.handleRequest(new Object(), context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE) - .containsEntry(DefaultLambdaFields.FUNCTION_ARN.getName(), "testArn") - .containsEntry(DefaultLambdaFields.FUNCTION_MEMORY_SIZE.getName(), "10") - .containsEntry(DefaultLambdaFields.FUNCTION_VERSION.getName(), "1") - .containsEntry(DefaultLambdaFields.FUNCTION_NAME.getName(), "testFunction") - .containsEntry(DefaultLambdaFields.FUNCTION_REQUEST_ID.getName(), "RequestId") - .containsKey("coldStart") - .containsKey("service"); + .containsEntry(FUNCTION_ARN.getName(), "arn:aws:lambda:us-east-1:123456789012:function:test") + .containsEntry(FUNCTION_MEMORY_SIZE.getName(), "128") + .containsEntry(FUNCTION_VERSION.getName(), "1") + .containsEntry(FUNCTION_NAME.getName(), "test-function") + .containsEntry(FUNCTION_REQUEST_ID.getName(), "test-request-id") + .containsKey(FUNCTION_COLD_START.getName()) + .containsKey(SERVICE.getName()); } @Test void shouldSetLambdaContextForStreamHandlerWhenEnabled() throws IOException { - requestStreamHandler = new PowerLogToolEnabledForStream(); + requestStreamHandler = new PowertoolsLogEnabledForStream(); - requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[]{}), new ByteArrayOutputStream(), context); + requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[] {}), new ByteArrayOutputStream(), + context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE) - .containsEntry(DefaultLambdaFields.FUNCTION_ARN.getName(), "testArn") - .containsEntry(DefaultLambdaFields.FUNCTION_MEMORY_SIZE.getName(), "10") - .containsEntry(DefaultLambdaFields.FUNCTION_VERSION.getName(), "1") - .containsEntry(DefaultLambdaFields.FUNCTION_NAME.getName(), "testFunction") - .containsEntry(DefaultLambdaFields.FUNCTION_REQUEST_ID.getName(), "RequestId") - .containsKey("coldStart") - .containsKey("service"); + .containsEntry(FUNCTION_ARN.getName(), "arn:aws:lambda:us-east-1:123456789012:function:test") + .containsEntry(FUNCTION_MEMORY_SIZE.getName(), "128") + .containsEntry(FUNCTION_VERSION.getName(), "1") + .containsEntry(FUNCTION_NAME.getName(), "test-function") + .containsEntry(FUNCTION_REQUEST_ID.getName(), "test-request-id") + .containsKey(FUNCTION_COLD_START.getName()) + .containsKey(SERVICE.getName()); } @Test - void shouldSetColdStartFlag() throws IOException { - requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[]{}), new ByteArrayOutputStream(), context); + void shouldSetColdStartFlagOnFirstCallNotOnSecondCall() throws IOException { + requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[] {}), new ByteArrayOutputStream(), + context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE) - .containsEntry("coldStart", "true"); + .containsEntry(FUNCTION_COLD_START.getName(), "true"); - requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[]{}), new ByteArrayOutputStream(), context); + requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[] {}), new ByteArrayOutputStream(), + context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE) - .containsEntry("coldStart", "false"); + .containsEntry(FUNCTION_COLD_START.getName(), "false"); } @Test void shouldNotSetLambdaContextWhenDisabled() { - requestHandler = new PowerToolDisabled(); + requestHandler = new PowertoolsLogDisabled(); requestHandler.handleRequest(new Object(), context); - assertThat(ThreadContext.getImmutableContext()) - .isEmpty(); + assertThat(MDC.getCopyOfContextMap()).isNull(); } @Test void shouldNotSetLambdaContextForStreamHandlerWhenDisabled() throws IOException { - requestStreamHandler = new PowerToolDisabledForStream(); + requestStreamHandler = new PowertoolsLogDisabledForStream(); requestStreamHandler.handleRequest(null, null, context); - assertThat(ThreadContext.getImmutableContext()) - .isEmpty(); + assertThat(MDC.getCopyOfContextMap()).isNull(); + } + + @Test + void shouldClearStateWhenClearStateIsTrue() { + PowertoolsLogClearState handler = new PowertoolsLogClearState(); + + handler.handleRequest(Collections.singletonMap("mySuperSecret", "P@ssw0Rd"), context); + + assertThat(MDC.getCopyOfContextMap()).isNull(); + } + + @Test + void shouldLogDebugWhenSamplingEqualsOne() { + PowertoolsLogSamplingEnabled handler = new PowertoolsLogSamplingEnabled(); + + Boolean debugEnabled = handler.handleRequest(new Object(), context); + + assertThat(debugEnabled).isTrue(); + } + + @Test + void shouldLogDebugWhenSamplingEnvVarEqualsOne() { + // GIVEN + LoggingConstants.POWERTOOLS_SAMPLING_RATE = "1"; + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + + // WHEN + handler.handleRequest(new Object(), context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("Test debug event"); + } + + @Test + void shouldNotLogDebugWhenSamplingEnvVarIsTooBig() { + // GIVEN + LoggingConstants.POWERTOOLS_SAMPLING_RATE = "42"; + + // WHEN + requestHandler.handleRequest(new Object(), context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).doesNotContain("Test debug event"); + } + + @Test + void shouldNotLogDebugWhenSamplingEnvVarIsInvalid() { + // GIVEN + LoggingConstants.POWERTOOLS_SAMPLING_RATE = "NotANumber"; + + // WHEN + requestHandler.handleRequest(new Object(), context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).doesNotContain("Test debug event"); + assertThat(contentOf(logFile)).contains( + "Skipping sampling rate on environment variable configuration because of invalid value"); + } + + @Test + void shouldNotLogDebugWhenSamplingEqualsZero() { + // GIVEN + LoggingConstants.POWERTOOLS_SAMPLING_RATE = "0"; + PowertoolsLogSamplingDisabled handler = new PowertoolsLogSamplingDisabled(); + + // WHEN + Boolean debugEnabled = handler.handleRequest(new Object(), context); + + // THEN + assertThat(debugEnabled).isFalse(); } @Test void shouldHaveNoEffectIfNotUsedOnLambdaHandler() { - PowerLogToolEnabled handler = new PowerLogToolEnabled(); + // GIVEN + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + // WHEN handler.anotherMethod(); - assertThat(ThreadContext.getImmutableContext()) - .isEmpty(); + // THEN + assertThat(MDC.getCopyOfContextMap()).isNull(); + } + + @Test + void shouldLogServiceNameWhenEnvVarSet() throws IllegalAccessException { + // GIVEN + writeStaticField(LambdaHandlerProcessor.class, "serviceName", "testService", true); + + // WHEN + requestHandler.handleRequest(new Object(), context); + + // THEN + assertThat(MDC.getCopyOfContextMap()) + .hasSize(EXPECTED_CONTEXT_SIZE) + .containsEntry(SERVICE.getName(), "testService"); } @Test - void shouldLogEventForHandler() throws IOException, JSONException { - requestHandler = new PowerToolLogEventEnabled(); - S3EventNotification s3EventNotification = s3EventNotification(); + @ClearEnvironmentVariable(key = "_X_AMZN_TRACE_ID") + @SetSystemProperty(key = "com.amazonaws.xray.traceHeader", value = "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1") + void shouldLogxRayTraceIdSystemPropertySet() { + String xRayTraceId = "1-5759e988-bd862e3fe1be46a994272793"; - requestHandler.handleRequest(s3EventNotification, context); + requestHandler.handleRequest(new Object(), context); - Map<String, Object> log = parseToMap(Files.lines(Paths.get("target/logfile.json")).collect(joining())); + assertThat(MDC.getCopyOfContextMap()) + .hasSize(EXPECTED_CONTEXT_SIZE + 1) + .containsEntry("xray_trace_id", xRayTraceId); + } - String event = (String) log.get("message"); + @Test + @SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1") + void shouldLogxRayTraceIdEnvVarSet() { + // GIVEN + String xRayTraceId = "1-5759e988-bd862e3fe1be46a994272793"; - String expectEvent = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("/s3EventNotification.json"))) - .lines().collect(joining("\n")); + // WHEN + requestHandler.handleRequest(new Object(), context); - assertEquals(expectEvent, event, false); + // THEN + assertThat(MDC.getCopyOfContextMap()) + .hasSize(EXPECTED_CONTEXT_SIZE + 1) + .containsEntry(FUNCTION_TRACE_ID.getName(), xRayTraceId); } @Test - void shouldLogEventForHandlerWithOverriddenObjectMapper() throws IOException, JSONException { - RequestHandler<S3EventNotification, Object> handler = new PowerToolLogEventEnabledWithCustomMapper(); - S3EventNotification s3EventNotification = s3EventNotification(); + void shouldLogEventForHandlerWithLogEventAnnotation() { + // GIVEN + requestHandler = new PowertoolsLogEvent(); + List<String> listOfOneElement = singletonList("ListOfOneElement"); + + // WHEN + requestHandler.handleRequest(listOfOneElement, context); + + // THEN + TestLogger logger = (TestLogger) PowertoolsLogEvent.getLogger(); + assertThat(logger.getArguments()).hasSize(1); + StructuredArgument argument = (StructuredArgument) logger.getArguments()[0]; + assertThat(argument.toString()).hasToString("event=" + listOfOneElement.toString()); + } - handler.handleRequest(s3EventNotification, context); + @Test + void shouldLogEventForHandlerWhenEnvVariableSetToTrue() { + // GIVEN + LoggingConstants.POWERTOOLS_LOG_EVENT = true; - Map<String, Object> log = parseToMap(Files.lines(Paths.get("target/logfile.json")).collect(joining())); + requestHandler = new PowertoolsLogEventEnvVar(); - String event = (String) log.get("message"); + SQSEvent.SQSMessage message = new SQSEvent.SQSMessage(); + message.setBody("body"); + message.setMessageId("1234abcd"); + message.setAwsRegion("eu-west-1"); - String expectEvent = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("/customizedLogEvent.json"))) - .lines().collect(joining("\n")); + // WHEN + requestHandler.handleRequest(message, context); - assertEquals(expectEvent, event, false); + // THEN + TestLogger logger = (TestLogger) ((PowertoolsLogEventEnvVar) requestHandler).getLogger(); + try { + assertThat(logger.getArguments()).hasSize(1); + StructuredArgument argument = (StructuredArgument) logger.getArguments()[0]; + assertThat(argument.toString()).hasToString("event={messageId: 1234abcd,awsRegion: eu-west-1,body: body,}"); + } finally { + LoggingConstants.POWERTOOLS_LOG_EVENT = false; + if (logger != null) { + logger.clearArguments(); + } + } + } + + @Test + void shouldNotLogEventForHandlerWhenEnvVariableSetToFalse() { + // GIVEN + LoggingConstants.POWERTOOLS_LOG_EVENT = false; + + // WHEN + requestHandler = new PowertoolsLogEventEnvVar(); + requestHandler.handleRequest(singletonList("ListOfOneElement"), context); + + // THEN + TestLogger logger = (TestLogger) ((PowertoolsLogEventEnvVar) requestHandler).getLogger(); + assertThat(logger.getArguments()).isNull(); } @Test - void shouldLogEventForStreamAndLambdaStreamIsValid() throws IOException, JSONException { - requestStreamHandler = new PowerToolLogEventEnabledForStream(); + void shouldLogEventForStreamHandler() throws IOException { + // GIVEN + requestStreamHandler = new PowertoolsLogEventForStream(); ByteArrayOutputStream output = new ByteArrayOutputStream(); - S3EventNotification s3EventNotification = s3EventNotification(); + Map<String, String> map = Collections.singletonMap("key", "value"); - requestStreamHandler.handleRequest(new ByteArrayInputStream(new ObjectMapper().writeValueAsBytes(s3EventNotification)), output, context); + // WHEN + requestStreamHandler.handleRequest(new ByteArrayInputStream(new ObjectMapper().writeValueAsBytes(map)), output, + context); + // THEN assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8)) .isNotEmpty(); - Map<String, Object> log = parseToMap(Files.lines(Paths.get("target/logfile.json")).collect(joining())); + TestLogger logger = (TestLogger) PowertoolsLogEventForStream.getLogger(); + try { + assertThat(logger.getArguments()).hasSize(1); + StructuredArgument argument = (StructuredArgument) logger.getArguments()[0]; + assertThat(argument.toString()).hasToString("event={\"key\":\"value\"}"); + } finally { + logger.clearArguments(); + } + } - String event = (String) log.get("message"); + @Test + void shouldLogResponseForHandlerWithLogResponseAnnotation() { + // GIVEN + requestHandler = new PowertoolsLogResponse(); - String expectEvent = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("/s3EventNotification.json"))) - .lines().collect(joining("\n")); + // WHEN + requestHandler.handleRequest("input", context); - assertEquals(expectEvent, event, false); + // THEN + TestLogger logger = (TestLogger) PowertoolsLogResponse.getLogger(); + try { + assertThat(logger.getArguments()).hasSize(1); + StructuredArgument argument = (StructuredArgument) logger.getArguments()[0]; + assertThat(argument.toString()).hasToString("response=Hola mundo"); + } finally { + logger.clearArguments(); + } } @Test - void shouldLogServiceNameWhenEnvVarSet() throws IllegalAccessException { - writeStaticField(LambdaHandlerProcessor.class, "SERVICE_NAME", "testService", true); - requestHandler.handleRequest(new Object(), context); + void shouldLogResponseForHandlerWhenEnvVariableSetToTrue() { + // GIVEN + LoggingConstants.POWERTOOLS_LOG_RESPONSE = true; - assertThat(ThreadContext.getImmutableContext()) - .hasSize(EXPECTED_CONTEXT_SIZE) - .containsEntry("service", "testService"); + requestHandler = new PowertoolsLogEnabled(); + + // WHEN + requestHandler.handleRequest("input", context); + + // THEN + TestLogger logger = (TestLogger) PowertoolsLogEnabled.getLogger(); + try { + assertThat(logger.getArguments()).hasSize(1); + StructuredArgument argument = (StructuredArgument) logger.getArguments()[0]; + assertThat(argument.toString()).hasToString("response=Bonjour le monde"); + } finally { + LoggingConstants.POWERTOOLS_LOG_RESPONSE = false; + logger.clearArguments(); + } } @Test - void shouldLogxRayTraceIdEnvVarSet() { - String xRayTraceId = "1-5759e988-bd862e3fe1be46a994272793"; + void shouldLogResponseForStreamHandler() throws IOException { + // GIVEN + requestStreamHandler = new PowertoolsLogResponseForStream(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + String input = "<user><firstName>Bob</firstName><lastName>The Sponge</lastName></user>"; - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""); + // WHEN + requestStreamHandler.handleRequest(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), output, + context); - requestHandler.handleRequest(new Object(), context); + // THEN + assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8)) + .isEqualTo(input); - assertThat(ThreadContext.getImmutableContext()) - .hasSize(EXPECTED_CONTEXT_SIZE + 1) - .containsEntry("xray_trace_id", xRayTraceId); + TestLogger logger = (TestLogger) PowertoolsLogResponseForStream.getLogger(); + try { + assertThat(logger.getArguments()).hasSize(1); + StructuredArgument argument = (StructuredArgument) logger.getArguments()[0]; + assertThat(argument.toString()).hasToString("response=" + input); + } finally { + logger.clearArguments(); + } + } + + @Test + void shouldLogErrorForHandlerWithLogErrorAnnotation() { + // GIVEN + requestHandler = new PowertoolsLogError(); + + // WHEN + try { + requestHandler.handleRequest("input", context); + } catch (Exception e) { + // ignore + } + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("This is an error"); + } + + @Test + void shouldLogErrorForHandlerWhenEnvVariableSetToTrue() { + try { + // GIVEN + LoggingConstants.POWERTOOLS_LOG_ERROR = true; + + requestHandler = new PowertoolsLogEnabled(true); + + // WHEN + try { + requestHandler.handleRequest("input", context); + } catch (Exception e) { + // ignore + } + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("Something went wrong"); + } finally { + LoggingConstants.POWERTOOLS_LOG_ERROR = false; } } @ParameterizedTest @Event(value = "apiGatewayProxyEventV1.json", type = APIGatewayProxyRequestEvent.class) void shouldLogCorrelationIdOnAPIGatewayProxyRequestEvent(APIGatewayProxyRequestEvent event) { - RequestHandler<APIGatewayProxyRequestEvent, Object> handler = new PowerLogToolApiGatewayRestApiCorrelationId(); + // GIVEN + RequestHandler<APIGatewayProxyRequestEvent, Object> handler = new PowertoolsLogApiGatewayRestApiCorrelationId(); + + // WHEN handler.handleRequest(event, context); - assertThat(ThreadContext.getImmutableContext()) + // THEN + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry("correlation_id", event.getRequestContext().getRequestId()); } @@ -273,10 +539,14 @@ void shouldLogCorrelationIdOnAPIGatewayProxyRequestEvent(APIGatewayProxyRequestE @ParameterizedTest @Event(value = "apiGatewayProxyEventV2.json", type = APIGatewayV2HTTPEvent.class) void shouldLogCorrelationIdOnAPIGatewayV2HTTPEvent(APIGatewayV2HTTPEvent event) { - RequestHandler<APIGatewayV2HTTPEvent, Object> handler = new PowerLogToolApiGatewayHttpApiCorrelationId(); + // GIVEN + RequestHandler<APIGatewayV2HTTPEvent, Object> handler = new PowertoolsLogApiGatewayHttpApiCorrelationId(); + + // WHEN handler.handleRequest(event, context); - assertThat(ThreadContext.getImmutableContext()) + // THEN + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry("correlation_id", event.getRequestContext().getRequestId()); } @@ -284,79 +554,176 @@ void shouldLogCorrelationIdOnAPIGatewayV2HTTPEvent(APIGatewayV2HTTPEvent event) @ParameterizedTest @Event(value = "albEvent.json", type = ApplicationLoadBalancerRequestEvent.class) void shouldLogCorrelationIdOnALBEvent(ApplicationLoadBalancerRequestEvent event) { - RequestHandler<ApplicationLoadBalancerRequestEvent, Object> handler = new PowerLogToolAlbCorrelationId(); + // GIVEN + RequestHandler<ApplicationLoadBalancerRequestEvent, Object> handler = new PowertoolsLogAlbCorrelationId(); + + // WHEN handler.handleRequest(event, context); - assertThat(ThreadContext.getImmutableContext()) + // THEN + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry("correlation_id", event.getHeaders().get("x-amzn-trace-id")); } @Test - void shouldLogAndClearLogContextOnEachRequest() throws IOException { - requestHandler = new PowerLogToolEnabledWithClearState(); - S3EventNotification s3EventNotification = s3EventNotification(); + void shouldLogCorrelationIdOnStreamHandler() throws IOException { + // GIVEN + RequestStreamHandler handler = new PowertoolsLogEventBridgeCorrelationId(); + String eventId = "3"; + String event = "{\"id\":" + eventId + "}"; // CorrelationIdPath.EVENT_BRIDGE + ByteArrayInputStream inputStream = new ByteArrayInputStream(event.getBytes()); + + // WHEN + handler.handleRequest(inputStream, new ByteArrayOutputStream(), context); + + // THEN + assertThat(MDC.getCopyOfContextMap()) + .hasSize(EXPECTED_CONTEXT_SIZE + 1) + .containsEntry("correlation_id", eventId); + } - requestHandler.handleRequest(s3EventNotification, context); - requestHandler.handleRequest(s3EventNotification, context); + @Test + void shouldLogCorrelationIdOnAppSyncEvent() throws IOException { + // GIVEN + RequestStreamHandler handler = new PowertoolsLogAppSyncCorrelationId(); + String eventId = "456"; + String event = "{\"request\":{\"headers\":{\"x-amzn-trace-id\":" + eventId + "}}}"; // CorrelationIdPath.APPSYNC_RESOLVER + ByteArrayInputStream inputStream = new ByteArrayInputStream(event.getBytes()); + + // WHEN + handler.handleRequest(inputStream, new ByteArrayOutputStream(), context); + + // THEN + assertThat(MDC.getCopyOfContextMap()) + .hasSize(EXPECTED_CONTEXT_SIZE + 1) + .containsEntry("correlation_id", eventId); + } - List<String> logLines = Files.lines(Paths.get("target/logfile.json")).collect(Collectors.toList()); - Map<String, Object> invokeLog = parseToMap(logLines.get(0)); + @Test + void shouldClearBufferAfterSuccessfulHandlerExecution() { + // WHEN + requestHandler.handleRequest(new Object(), context); - assertThat(invokeLog) - .containsEntry("TestKey", "TestValue"); + // THEN + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + if (loggingManager instanceof TestLoggingManager) { + assertThat(((TestLoggingManager) loggingManager).isBufferCleared()).isTrue(); + } + } - invokeLog = parseToMap(logLines.get(1)); + @Test + void shouldClearBufferAfterSuccessfulStreamHandlerExecution() throws IOException { + // WHEN + requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[] {}), new ByteArrayOutputStream(), + context); + + // THEN + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + if (loggingManager instanceof TestLoggingManager) { + assertThat(((TestLoggingManager) loggingManager).isBufferCleared()).isTrue(); + } + } + + @Test + void shouldClearBufferAfterHandlerExceptionWithLogError() { + // GIVEN + requestHandler = new PowertoolsLogError(); - assertThat(invokeLog) - .doesNotContainKey("TestKey"); + // WHEN + try { + requestHandler.handleRequest("input", context); + } catch (Exception e) { + // ignore + } + + // THEN + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + if (loggingManager instanceof TestLoggingManager) { + assertThat(((TestLoggingManager) loggingManager).isBufferCleared()).isTrue(); + } } - private void setupContext() { - when(context.getFunctionName()).thenReturn("testFunction"); - when(context.getInvokedFunctionArn()).thenReturn("testArn"); - when(context.getFunctionVersion()).thenReturn("1"); - when(context.getMemoryLimitInMB()).thenReturn(10); - when(context.getAwsRequestId()).thenReturn("RequestId"); + @Test + void shouldClearBufferAfterHandlerExceptionWithEnvVarLogError() { + try { + // GIVEN + LoggingConstants.POWERTOOLS_LOG_ERROR = true; + requestHandler = new PowertoolsLogEnabled(true); + + // WHEN + try { + requestHandler.handleRequest("input", context); + } catch (Exception e) { + // ignore + } + + // THEN + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + if (loggingManager instanceof TestLoggingManager) { + assertThat(((TestLoggingManager) loggingManager).isBufferCleared()).isTrue(); + } + } finally { + LoggingConstants.POWERTOOLS_LOG_ERROR = false; + } } - private void resetLogLevel(Level level) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - Method resetLogLevels = LambdaLoggingAspect.class.getDeclaredMethod("resetLogLevels", Level.class); - resetLogLevels.setAccessible(true); - resetLogLevels.invoke(null, level); - writeStaticField(LambdaLoggingAspect.class, "LEVEL_AT_INITIALISATION", level, true); + @Test + void shouldFlushBufferOnUncaughtErrorWhenEnabled() { + // GIVEN + requestHandler = new PowertoolsLogError(); + + // WHEN + try { + requestHandler.handleRequest("input", context); + } catch (Exception e) { + // ignore + } + + // THEN + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + if (loggingManager instanceof TestLoggingManager) { + assertThat(((TestLoggingManager) loggingManager).isBufferFlushed()).isTrue(); + } } - private S3EventNotification s3EventNotification() { - S3EventNotificationRecord record = new S3EventNotificationRecord("us-west-2", - "ObjectCreated:Put", - "aws:s3", - null, - "2.1", - new RequestParametersEntity("127.0.0.1"), - new ResponseElementsEntity("C3D13FE58DE4C810", "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"), - new S3Entity("testConfigRule", - new S3BucketEntity("mybucket", - new UserIdentityEntity("A3NL1KOZZKExample"), - "arn:aws:s3:::mybucket"), - new S3ObjectEntity("HappyFace.jpg", - 1024L, - "d41d8cd98f00b204e9800998ecf8427e", - "096fKKXTRTtl3on89fVO.nfljtsv6qko", - "0055AED6DCD90281E5"), - "1.0"), - new UserIdentityEntity("AIDAJDPLRKLG7UEXAMPLE") - ); + @Test + void shouldNotFlushBufferOnUncaughtErrorWhenDisabled() { + // GIVEN + PowertoolsLogErrorNoFlush handler = new PowertoolsLogErrorNoFlush(); - return new S3EventNotification(singletonList(record)); + // WHEN + try { + handler.handleRequest("input", context); + } catch (Exception e) { + // ignore + } + + // THEN + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + if (loggingManager instanceof TestLoggingManager) { + assertThat(((TestLoggingManager) loggingManager).isBufferFlushed()).isFalse(); + } } - private Map<String, Object> parseToMap(String stringAsJson) { + @Test + void shouldClearBufferBeforeErrorLoggingWhenFlushBufferOnUncaughtErrorDisabled() { + // GIVEN + PowertoolsLogErrorNoFlush handler = new PowertoolsLogErrorNoFlush(); + + // WHEN try { - return new ObjectMapper().readValue(stringAsJson, Map.class); - } catch (JsonProcessingException e) { - fail("Failed parsing logger line " + stringAsJson); - return emptyMap(); + handler.handleRequest("input", context); + } catch (Exception e) { + // ignore + } + + // THEN - Buffer should be cleared and not flushed + LoggingManager loggingManager = LoggingManagerRegistry.getLoggingManager(); + if (loggingManager instanceof TestLoggingManager) { + assertThat(((TestLoggingManager) loggingManager).isBufferCleared()).isTrue(); + assertThat(((TestLoggingManager) loggingManager).isBufferFlushed()).isFalse(); } } -} \ No newline at end of file + +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LoggingManagerRegistryTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LoggingManagerRegistryTest.java new file mode 100644 index 000000000..6bed0d9c1 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LoggingManagerRegistryTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.Test; + +class LoggingManagerRegistryTest { + + @Test + void testMultipleLoggingManagers_shouldWarnAndSelectFirstOne() throws UnsupportedEncodingException { + // GIVEN + List<LoggingManager> list = new ArrayList<>(); + list.add(new TestLoggingManager()); + list.add(new DefaultLoggingManager()); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(outputStream); + + // WHEN + LoggingManagerRegistry.selectLoggingManager(list, stream); + + // THEN + String output = outputStream.toString("UTF-8"); + assertThat(output) + .contains("WARN. Multiple LoggingManagers were found on the classpath") + .contains( + "WARN. Make sure to have only one of powertools-logging-log4j OR powertools-logging-logback in your dependencies") + .contains("WARN. Using the first LoggingManager found on the classpath: [" + list.get(0) + "]"); + } + + @Test + void testNoLoggingManagers_shouldWarnAndCreateDefault() throws UnsupportedEncodingException { + // GIVEN + List<LoggingManager> list = new ArrayList<>(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(outputStream); + + // WHEN + LoggingManager loggingManager = LoggingManagerRegistry.selectLoggingManager(list, stream); + + // THEN + String output = outputStream.toString("UTF-8"); + assertThat(output) + .contains("ERROR. No LoggingManager was found on the classpath") + .contains("ERROR. Applying default LoggingManager: POWERTOOLS_LOG_LEVEL variable is ignored") + .contains( + "ERROR. Make sure to add either powertools-logging-log4j or powertools-logging-logback to your dependencies"); + + assertThat(loggingManager).isExactlyInstanceOf(DefaultLoggingManager.class); + } + + @Test + void testSingleLoggingManager_shouldReturnWithoutWarning() throws UnsupportedEncodingException { + // GIVEN + List<LoggingManager> list = new ArrayList<>(); + TestLoggingManager testManager = new TestLoggingManager(); + list.add(testManager); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(outputStream); + + // WHEN + LoggingManager loggingManager = LoggingManagerRegistry.selectLoggingManager(list, stream); + + // THEN + String output = outputStream.toString("UTF-8"); + assertThat(output).isEmpty(); + assertThat(loggingManager) + .isSameAs(testManager) + .isInstanceOf(BufferManager.class); + } + + @Test + void testGetLoggingManager_shouldReturnSameInstance() { + // WHEN + LoggingManager first = LoggingManagerRegistry.getLoggingManager(); + LoggingManager second = LoggingManagerRegistry.getLoggingManager(); + + // THEN + assertThat(first) + .isSameAs(second) + .isNotNull() + .isInstanceOf(BufferManager.class); + } + + @Test + void testGetLoggingManager_shouldBeThreadSafe() throws InterruptedException, IOException { + // GIVEN + int threadCount = 10; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + try (Closeable ignored = executor::shutdown) { + CountDownLatch latch = new CountDownLatch(threadCount); + AtomicReference<LoggingManager> sharedInstance = new AtomicReference<>(); + + // WHEN + for (int i = 0; i < threadCount; i++) { + executor.submit(() -> { + try { + LoggingManager instance = LoggingManagerRegistry.getLoggingManager(); + sharedInstance.compareAndSet(null, instance); + assertThat(instance).isSameAs(sharedInstance.get()); + } finally { + latch.countDown(); + } + }); + } + + // THEN + latch.await(5, TimeUnit.SECONDS); + assertThat(sharedInstance.get()).isNotNull(); + } + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java new file mode 100644 index 000000000..f2aa2417e --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java @@ -0,0 +1,57 @@ +package software.amazon.lambda.powertools.logging.internal; + +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; +import org.slf4j.test.TestLogger; +import org.slf4j.test.TestLoggerFactory; + +public class TestLoggingManager implements LoggingManager, BufferManager { + + private final TestLoggerFactory loggerFactory; + private boolean bufferFlushed = false; + private boolean bufferCleared = false; + + public TestLoggingManager() { + ILoggerFactory loggerFactoryInstance = LoggerFactory.getILoggerFactory(); + if (!(loggerFactoryInstance instanceof TestLoggerFactory)) { + throw new RuntimeException( + "LoggerFactory does not match required type: " + TestLoggerFactory.class.getName()); + } + this.loggerFactory = (TestLoggerFactory) loggerFactoryInstance; + } + + @Override + public void setLogLevel(Level logLevel) { + loggerFactory.getLoggers().forEach((key, logger) -> ((TestLogger) logger).setLogLevel(logLevel.toString())); + } + + @Override + public Level getLogLevel(Logger logger) { + return org.slf4j.event.Level.intToLevel(((TestLogger) logger).getLogLevel()); + } + + @Override + public void flushBuffer() { + bufferFlushed = true; + } + + @Override + public void clearBuffer() { + bufferCleared = true; + } + + public boolean isBufferFlushed() { + return bufferFlushed; + } + + public boolean isBufferCleared() { + return bufferCleared; + } + + public void resetBufferState() { + bufferFlushed = false; + bufferCleared = false; + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/model/Basket.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/model/Basket.java new file mode 100644 index 000000000..0fa544cf1 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/model/Basket.java @@ -0,0 +1,67 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.model; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class Basket { + private List<Product> products = new ArrayList<>(); + + public Basket() { + } + + public Basket(Product... p) { + products.addAll(Arrays.asList(p)); + } + + public List<Product> getProducts() { + return products; + } + + public void setProducts(List<Product> products) { + this.products = products; + } + + public void add(Product product) { + products.add(product); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Basket basket = (Basket) o; + return products.equals(basket.products); + } + + @Override + public int hashCode() { + return Objects.hash(products); + } + + @Override + public String toString() { + return "Basket{" + + "products=" + products + + '}'; + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/model/Product.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/model/Product.java new file mode 100644 index 000000000..fee5bc20d --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/model/Product.java @@ -0,0 +1,84 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.logging.model; + +import java.util.Objects; + +public class Product { + private long id; + + private String name; + + private double price; + + public Product() { + } + + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Product product = (Product) o; + return id == product.id && Double.compare(product.price, price) == 0 && Objects.equals(name, product.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, price); + } + + @Override + public String toString() { + return "Product{" + + "id=" + id + + ", name='" + name + '\'' + + ", price=" + price + + '}'; + } +} diff --git a/powertools-logging/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider b/powertools-logging/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider new file mode 100644 index 000000000..ade4bb1e2 --- /dev/null +++ b/powertools-logging/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider @@ -0,0 +1 @@ +org.slf4j.test.TestServiceProvider \ No newline at end of file diff --git a/powertools-logging/src/test/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager b/powertools-logging/src/test/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager new file mode 100644 index 000000000..adbf7ae69 --- /dev/null +++ b/powertools-logging/src/test/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager @@ -0,0 +1,15 @@ +# +# Copyright 2023 Amazon.com, Inc. or its affiliates. +# 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 +# http://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. +# +# + +software.amazon.lambda.powertools.logging.internal.TestLoggingManager \ No newline at end of file diff --git a/powertools-logging/src/test/resources/s3EventNotification.json b/powertools-logging/src/test/resources/s3EventNotification.json deleted file mode 100644 index feb88ec02..000000000 --- a/powertools-logging/src/test/resources/s3EventNotification.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "records":[ - { - "eventVersion":"2.1", - "eventSource":"aws:s3", - "awsRegion":"us-west-2", - "eventName":"ObjectCreated:Put", - "userIdentity":{ - "principalId":"AIDAJDPLRKLG7UEXAMPLE" - }, - "requestParameters":{ - "sourceIPAddress":"127.0.0.1" - }, - "responseElements":{ - "xAmzId2":"C3D13FE58DE4C810", - "xAmzRequestId":"FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" - }, - "s3":{ - "s3SchemaVersion":"1.0", - "configurationId":"testConfigRule", - "bucket":{ - "name":"mybucket", - "ownerIdentity":{ - "principalId":"A3NL1KOZZKExample" - }, - "arn":"arn:aws:s3:::mybucket" - }, - "object":{ - "key":"HappyFace.jpg", - "size":1024, - "eTag":"d41d8cd98f00b204e9800998ecf8427e", - "versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko", - "sequencer":"0055AED6DCD90281E5" - } - } - } - ] -} \ No newline at end of file diff --git a/powertools-logging/src/test/resources/testlogger.properties b/powertools-logging/src/test/resources/testlogger.properties new file mode 100644 index 000000000..84b7beaae --- /dev/null +++ b/powertools-logging/src/test/resources/testlogger.properties @@ -0,0 +1,3 @@ +org.slf4j.simpleLogger.defaultLogLevel=warn +org.slf4j.simpleLogger.log.software.amazon.lambda.powertools=info +org.slf4j.simpleLogger.logFile=target/logfile.json \ No newline at end of file diff --git a/powertools-metrics/pom.xml b/powertools-metrics/pom.xml index 8e1cd3056..5f6c971e3 100644 --- a/powertools-metrics/pom.xml +++ b/powertools-metrics/pom.xml @@ -1,7 +1,21 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + <project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>powertools-metrics</artifactId> @@ -10,41 +24,28 @@ <parent> <artifactId>powertools-parent</artifactId> <groupId>software.amazon.lambda</groupId> - <version>1.10.2</version> + <version>2.9.0</version> </parent> - <name>AWS Lambda Powertools Java library Metrics</name> + <name>Powertools for AWS Lambda (Java) - Metrics</name> <description> A suite of utilities for AWS Lambda Functions that make creating custom metrics via AWS Embedded Metric Format asynchronously easier. </description> - <url>https://aws.amazon.com/lambda/</url> - <issueManagement> - <system>GitHub Issues</system> - <url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url> - </issueManagement> - <scm> - <url>https://github.com/awslabs/aws-lambda-powertools-java.git</url> - </scm> - <developers> - <developer> - <name>AWS Lambda Powertools team</name> - <organization>Amazon Web Services</organization> - <organizationUrl>https://aws.amazon.com/</organizationUrl> - </developer> - </developers> - - <distributionManagement> - <snapshotRepository> - <id>ossrh</id> - <url>https://aws.oss.sonatype.org/content/repositories/snapshots</url> - </snapshotRepository> - </distributionManagement> <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.crac</groupId> + <artifactId>crac</artifactId> + </dependency> <dependency> <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-core</artifactId> + <artifactId>powertools-common</artifactId> </dependency> <dependency> <groupId>com.amazonaws</groupId> @@ -68,10 +69,14 @@ <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> - <dependency> - <groupId>org.aspectj</groupId> - <artifactId>aspectjrt</artifactId> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + <scope>provided</scope> </dependency> <!-- Test dependencies --> @@ -86,18 +91,13 @@ <scope>test</scope> </dependency> <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> <scope>test</scope> </dependency> <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-inline</artifactId> + <groupId>org.junit-pioneer</groupId> + <artifactId>junit-pioneer</artifactId> <scope>test</scope> </dependency> <dependency> @@ -110,6 +110,131 @@ <artifactId>assertj-core</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> </dependencies> -</project> \ No newline at end of file + <profiles> + <profile> + <id>generate-classesloaded-file</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Xlog:class+load=info:classesloaded.txt + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>generate-graalvm-files</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-metrics,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-metrics</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + + <build> + <resources> + <!-- GraalVM Native Image Configuration Files --> + <resource> + <directory>src/main/resources</directory> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <AWS_EMF_ENVIRONMENT>Lambda</AWS_EMF_ENVIRONMENT> + <AWS_LAMBDA_INITIALIZATION_TYPE>on-demand</AWS_LAMBDA_INITIALIZATION_TYPE> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-metrics/src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricsLoggerHelper.java b/powertools-metrics/src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricsLoggerHelper.java deleted file mode 100644 index f59658e9c..000000000 --- a/powertools-metrics/src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricsLoggerHelper.java +++ /dev/null @@ -1,28 +0,0 @@ -package software.amazon.cloudwatchlogs.emf.model; - -import java.lang.reflect.Field; - -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; - -public final class MetricsLoggerHelper { - private MetricsLoggerHelper() { - } - - public static boolean hasNoMetrics() { - return metricsContext().getRootNode().getAws().isEmpty(); - } - - public static long dimensionsCount() { - return metricsContext().getDimensions().size(); - } - - public static MetricsContext metricsContext() { - try { - Field f = metricsLogger().getClass().getDeclaredField("context"); - f.setAccessible(true); - return (MetricsContext) f.get(metricsLogger()); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } -} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/FlushMetrics.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/FlushMetrics.java new file mode 100644 index 000000000..952625f5b --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/FlushMetrics.java @@ -0,0 +1,71 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@code FlushMetrics} is used to signal that the annotated Lambda handler method should be + * extended with Metrics flushing functionality. Will have no effect when used on a method that is not a Lambda handler. + * + * <p>{@code FlushMetrics} allows users to asynchronously create Amazon + * CloudWatch metrics by using the CloudWatch <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html">Embedded Metrics Format</a>. + * {@code FlushMetrics} manages the life-cycle and configuration of Metrics to simplify the user experience when used with AWS Lambda. + * + * <p>{@code FlushMetrics} should be used with the handleRequest method of a class + * which implements either + * {@code com.amazonaws.services.lambda.runtime.RequestHandler} or + * {@code com.amazonaws.services.lambda.runtime.RequestStreamHandler}.</p> + * + * <p>{@code FlushMetrics} creates Amazon CloudWatch custom metrics. You can find + * pricing information on the <a href="https://aws.amazon.com/cloudwatch/pricing/">CloudWatch pricing documentation</a> page.</p> + * + * <p>To enable creation of custom metrics for cold starts you can add {@code @FlushMetrics(captureColdStart = true)}. + * </br>This will create a metric with the key {@code "ColdStart"} and the unit type {@code COUNT}. + * </p> + * + * <p>To raise exception if no metrics are emitted, use {@code @FlushMetrics(raiseOnEmptyMetrics = true)}. + * </br>This will create an exception if no metrics are emitted. By default its value is set to false. + * </p> + * + * <p>By default the service name associated with metrics created will be + * "service_undefined". This can be overridden with the environment variable {@code POWERTOOLS_SERVICE_NAME} + * or the annotation variable {@code @FlushMetrics(service = "Service Name")}. + * If both are specified then the value of the annotation variable will be used.</p> + * + * <p>A namespace must be specified for metrics. This can be set with the environment variable {@code POWERTOOLS_METRICS_NAMESPACE} + * or the annotation variable {@code @FlushMetrics(namespace = "Namespace")}. If not specified, an IllegalStateException will be thrown. + * If both are specified then the value of the annotation variable will be used.</p> + * + * <p>You can specify a custom function name with {@code @FlushMetrics(functionName = "MyFunction")}. + * If specified, this will be used instead of the function name from the Lambda context. + * </p> + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface FlushMetrics { + String namespace() default ""; + + String service() default ""; + + String functionName() default ""; + + boolean captureColdStart() default false; + + boolean raiseOnEmptyMetrics() default false; +} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/Metrics.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/Metrics.java index 2a4f4d472..77db2aba0 100644 --- a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/Metrics.java +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/Metrics.java @@ -1,50 +1,207 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.metrics; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import com.amazonaws.services.lambda.runtime.Context; +import java.time.Instant; +import java.util.function.Consumer; + +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricResolution; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; /** - * {@code Metrics} is used to signal that the annotated method should be - * extended with Metrics functionality. - * - * <p>{@code Metrics} allows users to asynchronously create Amazon - * CloudWatch metrics by using the CloudWatch <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html">Embedded Metrics Format</a>. - * {@code Metrics} manages the life-cycle of the MetricsLogger class, - * to simplify the user experience when used with AWS Lambda. - * - * <p>{@code Metrics} should be used with the handleRequest method of a class - * which implements either - * {@code com.amazonaws.services.lambda.runtime.RequestHandler} or - * {@code com.amazonaws.services.lambda.runtime.RequestStreamHandler}.</p> - * - * <p>{@code Metrics} creates Amazon CloudWatch custom metrics. You can find - * pricing information on the <a href="https://aws.amazon.com/cloudwatch/pricing/">CloudWatch pricing documentation</a> page.</p> - * - * <p>To enable creation of custom metrics for cold starts you can add {@code @Metrics(captureColdStart = true)}. - * </br>This will create a metric with the key {@code "ColdStart"} and the unit type {@code COUNT}. - * </p> - * - * <p>To raise exception if no metrics are emitted, use {@code @Metrics(raiseOnEmptyMetrics = true)}. - * </br>This will create a create a exception of type {@link ValidationException}. By default its value is set to false. - * </p> - * - * <p>By default the service name associated with metrics created will be - * "service_undefined". This can be overridden with the environment variable {@code POWERTOOLS_SERVICE_NAME} - * or the annotation variable {@code @Metrics(service = "Service Name")}. - * If both are specified then the value of the annotation variable will be used.</p> - * - * <p>By default the namespace associated with metrics created will be "aws-embedded-metrics". - * This can be overridden with the environment variable {@code POWERTOOLS_METRICS_NAMESPACE} - * or the annotation variable {@code @Metrics(namespace = "Namespace")}. - * If both are specified then the value of the annotation variable will be used.</p> + * Interface for metrics implementations. + * This interface is used to collect metrics in the Lambda function. + * It provides methods to add metrics, dimensions, and metadata. */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface Metrics { - String namespace() default ""; - String service() default ""; - boolean captureColdStart() default false; - boolean raiseOnEmptyMetrics() default false; +public interface Metrics { + + /** + * Add a metric + * + * @param key the name of the metric + * @param value the value of the metric + * @param unit the unit of the metric + * @param resolution the resolution of the metric + */ + void addMetric(String key, double value, MetricUnit unit, MetricResolution resolution); + + /** + * Add a metric with default resolution + * + * @param key the name of the metric + * @param value the value of the metric + * @param unit the unit of the metric + */ + default void addMetric(String key, double value, MetricUnit unit) { + addMetric(key, value, unit, MetricResolution.STANDARD); + } + + /** + * Add a metric with default unit and resolution + * + * @param key the name of the metric + * @param value the value of the metric + */ + default void addMetric(String key, double value) { + addMetric(key, value, MetricUnit.NONE, MetricResolution.STANDARD); + } + + /** + * Add a dimension + * This is equivalent to calling {@code addDimension(DimensionSet.of(key, value))} + * + * @param key the name of the dimension + * @param value the value of the dimension + */ + default void addDimension(String key, String value) { + addDimension(DimensionSet.of(key, value)); + } + + /** + * Add a dimension set + * + * @param dimensionSet the dimension set to add + */ + void addDimension(DimensionSet dimensionSet); + + /** + * Set a custom timestamp for the metrics + * + * @param timestamp the timestamp to use for the metrics + */ + void setTimestamp(Instant timestamp); + + /** + * Add metadata + * + * @param key the name of the metadata + * @param value the value of the metadata + */ + void addMetadata(String key, Object value); + + /** + * Set default dimensions + * + * @param dimensionSet the dimension set to use as default dimensions + */ + void setDefaultDimensions(DimensionSet dimensionSet); + + /** + * Get the default dimensions + * + * @return the default dimensions as a DimensionSet + */ + DimensionSet getDefaultDimensions(); + + /** + * Set the namespace + * + * @param namespace the namespace + */ + void setNamespace(String namespace); + + /** + * Set whether to raise an exception if no metrics are emitted + * + * @param raiseOnEmptyMetrics true to raise an exception, false otherwise + */ + void setRaiseOnEmptyMetrics(boolean raiseOnEmptyMetrics); + + /** + * Clear default dimensions + */ + void clearDefaultDimensions(); + + /** + * Flush metrics to the configured sink + */ + void flush(); + + /** + * Capture cold start metric and flush immediately + * + * @param context Lambda context + * @param dimensions custom dimensions for this metric (optional) + */ + void captureColdStartMetric(Context context, DimensionSet dimensions); + + /** + * Capture cold start metric and flush immediately + * + * @param context Lambda context + */ + default void captureColdStartMetric(Context context) { + captureColdStartMetric(context, null); + } + + /** + * Capture cold start metric without Lambda context and flush immediately + * + * @param dimensions custom dimensions for this metric (optional) + */ + void captureColdStartMetric(DimensionSet dimensions); + + /** + * Capture cold start metric without Lambda context and flush immediately + */ + default void captureColdStartMetric() { + captureColdStartMetric((DimensionSet) null); + } + + /** + * Flush a separate metrics context that inherits the namespace, default dimensions, and metadata. This creates a separate metrics context + * that doesn't affect the default metrics context. + * + * @param metricsConsumer the consumer to use to edit the metrics instance (e.g. add metrics, override namespace, set or add custom dimensions) before flushing + */ + void flushMetrics(Consumer<Metrics> metricsConsumer); + + /** + * Flush a single metric with custom namespace and dimensions. This creates a separate metrics context + * that doesn't affect the default metrics context. + * + * @param name the name of the metric + * @param value the value of the metric + * @param unit the unit of the metric + * @param namespace the namespace for the metric + * @param dimensions custom dimensions for this metric (optional) + */ + default void flushSingleMetric(String name, double value, MetricUnit unit, String namespace, DimensionSet dimensions) { + flushMetrics(metrics -> { + metrics.setNamespace(namespace); + metrics.setDefaultDimensions(dimensions); + metrics.addMetric(name, value, unit); + }); + + } + + /** + * Flush a single metric with custom namespace. This creates a separate metrics context + * that doesn't affect the default metrics context. + * + * @param name the name of the metric + * @param value the value of the metric + * @param unit the unit of the metric + * @param namespace the namespace for the metric + */ + default void flushSingleMetric(String name, double value, MetricUnit unit, String namespace) { + flushMetrics(metrics -> { + metrics.setNamespace(namespace); + metrics.addMetric(name, value, unit); + }); + } } diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsBuilder.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsBuilder.java new file mode 100644 index 000000000..2eb127b00 --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsBuilder.java @@ -0,0 +1,144 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics; + +import java.util.LinkedHashMap; +import java.util.Map; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; + +import software.amazon.lambda.powertools.metrics.provider.MetricsProvider; + +/** + * Builder for configuring the singleton Metrics instance + */ +public class MetricsBuilder { + private MetricsProvider provider; + private String namespace; + private String service; + private boolean raiseOnEmptyMetrics = false; + private final Map<String, String> defaultDimensions = new LinkedHashMap<>(); + + private MetricsBuilder() { + } + + /** + * Create a new builder instance + * + * @return a new builder instance + */ + public static MetricsBuilder builder() { + return new MetricsBuilder(); + } + + /** + * Set the metrics provider + * + * @param provider the metrics provider + * @return this builder + */ + public MetricsBuilder withMetricsProvider(MetricsProvider provider) { + this.provider = provider; + return this; + } + + /** + * Set the namespace + * + * @param namespace the namespace + * @return this builder + */ + public MetricsBuilder withNamespace(String namespace) { + this.namespace = namespace; + return this; + } + + /** + * Set the service name. Does not apply if used in combination with default dimensions. If you would like to use a + * service name with default dimensions, use {@link #withDefaultDimension(String, String)} instead. + * + * @param service the service name + * @return this builder + */ + public MetricsBuilder withService(String service) { + this.service = service; + return this; + } + + /** + * Set whether to raise an exception if no metrics are emitted + * + * @param raiseOnEmptyMetrics true to raise an exception, false otherwise + * @return this builder + */ + public MetricsBuilder withRaiseOnEmptyMetrics(boolean raiseOnEmptyMetrics) { + this.raiseOnEmptyMetrics = raiseOnEmptyMetrics; + return this; + } + + /** + * Add a default dimension. + * + * @param key the dimension key + * @param value the dimension value + * @return this builder + */ + public MetricsBuilder withDefaultDimension(String key, String value) { + this.defaultDimensions.put(key, value); + return this; + } + + /** + * Add default dimensions + * + * @param dimensionSet the dimension set to add + * @return this builder + */ + public MetricsBuilder withDefaultDimensions(DimensionSet dimensionSet) { + if (dimensionSet != null) { + this.defaultDimensions.putAll(dimensionSet.getDimensions()); + } + return this; + } + + /** + * Configure and return the singleton Metrics instance + * + * @return the configured singleton Metrics instance + */ + public Metrics build() { + if (provider != null) { + MetricsFactory.setMetricsProvider(provider); + } + + Metrics metrics = MetricsFactory.getMetricsInstance(); + + if (namespace != null) { + metrics.setNamespace(namespace); + } + + metrics.setRaiseOnEmptyMetrics(raiseOnEmptyMetrics); + + if (service != null) { + metrics.setDefaultDimensions(DimensionSet.of("Service", service)); + } + + // If the user provided default dimension, we overwrite the default Service dimension again + if (!defaultDimensions.isEmpty()) { + metrics.setDefaultDimensions(DimensionSet.of(defaultDimensions)); + } + + return metrics; + } +} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsFactory.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsFactory.java new file mode 100644 index 000000000..0644329c9 --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsFactory.java @@ -0,0 +1,92 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics; + +import org.crac.Core; +import org.crac.Resource; + +import software.amazon.lambda.powertools.common.internal.ClassPreLoader; +import software.amazon.lambda.powertools.common.internal.LambdaConstants; +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.metrics.internal.RequestScopedMetricsProxy; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.provider.EmfMetricsProvider; +import software.amazon.lambda.powertools.metrics.provider.MetricsProvider; + +/** + * Factory for accessing the singleton Metrics instance + */ +public final class MetricsFactory implements Resource { + private static MetricsProvider provider = new EmfMetricsProvider(); + private static RequestScopedMetricsProxy metricsProxy; + + // Dummy instance to register MetricsFactory with CRaC + private static final MetricsFactory INSTANCE = new MetricsFactory(); + + // Static block to ensure CRaC registration happens at class loading time + static { + Core.getGlobalContext().register(INSTANCE); + } + + /** + * Get the singleton instance of the Metrics + * + * @return the singleton Metrics instance + */ + public static synchronized Metrics getMetricsInstance() { + if (metricsProxy == null) { + metricsProxy = new RequestScopedMetricsProxy(provider); + + // Apply default configuration from environment variables + String envNamespace = System.getenv("POWERTOOLS_METRICS_NAMESPACE"); + if (envNamespace != null) { + metricsProxy.setNamespace(envNamespace); + } + + // Only set Service dimension if it's not the default undefined value + String serviceName = LambdaHandlerProcessor.serviceName(); + if (!LambdaConstants.SERVICE_UNDEFINED.equals(serviceName)) { + metricsProxy.setDefaultDimensions(DimensionSet.of("Service", serviceName)); + } + } + + return metricsProxy; + } + + /** + * Set the metrics provider + * + * @param metricsProvider the metrics provider + */ + public static synchronized void setMetricsProvider(MetricsProvider metricsProvider) { + if (metricsProvider == null) { + throw new IllegalArgumentException("Metrics provider cannot be null"); + } + provider = metricsProvider; + // Reset the metrics proxy so it will be recreated with the new provider + metricsProxy = null; + } + + @Override + public void beforeCheckpoint(org.crac.Context<? extends Resource> context) throws Exception { + MetricsFactory.getMetricsInstance(); + ClassPreLoader.preloadClasses(); + } + + @Override + public void afterRestore(org.crac.Context<? extends Resource> context) throws Exception { + // No action needed after restore + } +} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsUtils.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsUtils.java deleted file mode 100644 index 443ef3976..000000000 --- a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsUtils.java +++ /dev/null @@ -1,161 +0,0 @@ -package software.amazon.lambda.powertools.metrics; - -import java.util.Arrays; -import java.util.Optional; -import java.util.function.Consumer; - -import software.amazon.cloudwatchlogs.emf.config.SystemWrapper; -import software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.cloudwatchlogs.emf.model.DimensionSet; -import software.amazon.cloudwatchlogs.emf.model.MetricsContext; -import software.amazon.cloudwatchlogs.emf.model.MetricsLoggerHelper; -import software.amazon.cloudwatchlogs.emf.model.Unit; - -import static java.util.Objects.requireNonNull; -import static java.util.Optional.ofNullable; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.getXrayTraceId; -import static software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspect.REQUEST_ID_PROPERTY; -import static software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspect.TRACE_ID_PROPERTY; - -/** - * A class used to retrieve the instance of the {@code MetricsLogger} used by - * {@code Metrics}. - * - * {@see Metrics} - */ -public final class MetricsUtils { - private static final MetricsLogger metricsLogger = new MetricsLogger(); - private static DimensionSet[] defaultDimensions; - - private MetricsUtils() { - } - - /** - * The instance of the {@code MetricsLogger} used by {@code Metrics}. - * - * @return The instance of the MetricsLogger used by Metrics. - */ - public static MetricsLogger metricsLogger() { - return metricsLogger; - } - - /** - * Configure default dimension to be used by logger. - * By default, @{@link Metrics} annotation captures configured service as a dimension <i>Service</i> - * @param dimensionSets Default value of dimensions set for logger - */ - public static void defaultDimensions(final DimensionSet... dimensionSets) { - MetricsUtils.defaultDimensions = dimensionSets; - } - - /** - * Configure default dimension to be used by logger. - * By default, @{@link Metrics} annotation captures configured service as a dimension <i>Service</i> - * @param dimensionSet Default value of dimension set for logger - * @deprecated use {@link #defaultDimensions(DimensionSet...)} instead - * - */ - @Deprecated - public static void defaultDimensionSet(final DimensionSet dimensionSet) { - requireNonNull(dimensionSet, "Null dimension set not allowed"); - - if(dimensionSet.getDimensionKeys().size() > 0) { - defaultDimensions(dimensionSet); - } - } - - - /** - * Add and immediately flush a single metric. It will use the default namespace - * specified either on {@link Metrics} annotation or via POWERTOOLS_METRICS_NAMESPACE env var. - * It by default captures function_request_id as property if used together with {@link Metrics} annotation. It will also - * capture xray_trace_id as property if tracing is enabled. - * - * @param name the name of the metric - * @param value the value of the metric - * @param unit the unit type of the metric - * @param logger the MetricsLogger - */ - public static void withSingleMetric(final String name, - final double value, - final Unit unit, - final Consumer<MetricsLogger> logger) { - MetricsLogger metricsLogger = logger(); - - try { - metricsLogger.setNamespace(defaultNameSpace()); - metricsLogger.putMetric(name, value, unit); - captureRequestAndTraceId(metricsLogger); - logger.accept(metricsLogger); - } finally { - metricsLogger.flush(); - } - } - - /** - * Add and immediately flush a single metric. - * It by default captures function_request_id as property if used together with {@link Metrics} annotation. It will also - * capture xray_trace_id as property if tracing is enabled. - * - * @param name the name of the metric - * @param value the value of the metric - * @param unit the unit type of the metric - * @param namespace the namespace associated with the metric - * @param logger the MetricsLogger - */ - public static void withSingleMetric(final String name, - final double value, - final Unit unit, - final String namespace, - final Consumer<MetricsLogger> logger) { - MetricsLogger metricsLogger = logger(); - - try { - metricsLogger.setNamespace(namespace); - metricsLogger.putMetric(name, value, unit); - captureRequestAndTraceId(metricsLogger); - logger.accept(metricsLogger); - } finally { - metricsLogger.flush(); - } - } - - public static DimensionSet[] getDefaultDimensions() { - return Arrays.copyOf(defaultDimensions, defaultDimensions.length); - } - - public static boolean hasDefaultDimension() { - return null != defaultDimensions; - } - - private static void captureRequestAndTraceId(MetricsLogger metricsLogger) { - awsRequestId(). - ifPresent(requestId -> metricsLogger.putProperty(REQUEST_ID_PROPERTY, requestId)); - - getXrayTraceId() - .ifPresent(traceId -> metricsLogger.putProperty(TRACE_ID_PROPERTY, traceId)); - } - - private static String defaultNameSpace() { - MetricsContext context = MetricsLoggerHelper.metricsContext(); - return "aws-embedded-metrics".equals(context.getNamespace()) ? - SystemWrapper.getenv("POWERTOOLS_METRICS_NAMESPACE"): context.getNamespace(); - } - - private static Optional<String> awsRequestId() { - MetricsContext context = MetricsLoggerHelper.metricsContext(); - return ofNullable(context.getProperty(REQUEST_ID_PROPERTY)) - .map(Object::toString); - } - - private static MetricsLogger logger() { - MetricsContext metricsContext = new MetricsContext(); - - if (hasDefaultDimension()) { - metricsContext.setDimensions(defaultDimensions); - } - - return new MetricsLogger(new EnvironmentProvider(), metricsContext); - } -} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/ValidationException.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/ValidationException.java deleted file mode 100644 index 2da9a539c..000000000 --- a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/ValidationException.java +++ /dev/null @@ -1,8 +0,0 @@ -package software.amazon.lambda.powertools.metrics; - -public class ValidationException extends RuntimeException { - - public ValidationException(String message) { - super(message); - } -} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/EmfMetricsLogger.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/EmfMetricsLogger.java new file mode 100644 index 000000000..611d4dcc6 --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/EmfMetricsLogger.java @@ -0,0 +1,296 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics.internal; + +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.isColdStart; + +import java.time.Instant; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.services.lambda.runtime.Context; + +import software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider; +import software.amazon.cloudwatchlogs.emf.model.DimensionSet; +import software.amazon.cloudwatchlogs.emf.model.MetricsContext; +import software.amazon.cloudwatchlogs.emf.model.StorageResolution; +import software.amazon.cloudwatchlogs.emf.model.Unit; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.model.MetricResolution; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; + +/** + * Implementation of Metrics that uses the EMF library. Proxies Metrics interface calls to underlying + * library {@link software.amazon.cloudwatchlogs.emf.logger.MetricsLogger}. + */ +public class EmfMetricsLogger implements Metrics { + private static final Logger LOGGER = LoggerFactory.getLogger(EmfMetricsLogger.class); + private static final String COLD_START_METRIC = "ColdStart"; + private static final String METRICS_DISABLED_ENV_VAR = "POWERTOOLS_METRICS_DISABLED"; + + private final software.amazon.cloudwatchlogs.emf.logger.MetricsLogger emfLogger; + private final EnvironmentProvider environmentProvider; + private final AtomicBoolean raiseOnEmptyMetrics = new AtomicBoolean(false); + private String namespace; + private Map<String, String> defaultDimensions = new HashMap<>(); + private final Map<String, Object> properties = new HashMap<>(); + private final AtomicBoolean hasMetrics = new AtomicBoolean(false); + + public EmfMetricsLogger(EnvironmentProvider environmentProvider, MetricsContext metricsContext) { + this.emfLogger = new software.amazon.cloudwatchlogs.emf.logger.MetricsLogger(environmentProvider, + metricsContext); + this.environmentProvider = environmentProvider; + } + + @Override + public void addMetric(String key, double value, MetricUnit unit, MetricResolution resolution) { + StorageResolution storageResolution = resolution == MetricResolution.HIGH ? StorageResolution.HIGH + : StorageResolution.STANDARD; + emfLogger.putMetric(key, value, convertUnit(unit), storageResolution); + hasMetrics.set(true); + } + + @Override + public void addDimension(software.amazon.lambda.powertools.metrics.model.DimensionSet dimensionSet) { + if (dimensionSet == null) { + throw new IllegalArgumentException("DimensionSet cannot be null"); + } + + DimensionSet emfDimensionSet = new DimensionSet(); + dimensionSet.getDimensions().forEach((key, val) -> { + try { + emfDimensionSet.addDimension(key, val); + } catch (Exception e) { + // Ignore dimension errors + } + }); + + emfLogger.putDimensions(emfDimensionSet); + } + + @Override + public void addMetadata(String key, Object value) { + emfLogger.putProperty(key, value); + properties.put(key, value); + } + + @Override + public void setDefaultDimensions(software.amazon.lambda.powertools.metrics.model.DimensionSet dimensionSet) { + if (dimensionSet == null) { + throw new IllegalArgumentException("DimensionSet cannot be null"); + } + + DimensionSet emfDimensionSet = new DimensionSet(); + Map<String, String> dimensions = dimensionSet.getDimensions(); + dimensions.forEach((key, value) -> { + try { + emfDimensionSet.addDimension(key, value); + } catch (Exception e) { + // Ignore dimension errors + } + }); + emfLogger.setDimensions(emfDimensionSet); + // Store a copy of the default dimensions + this.defaultDimensions = new LinkedHashMap<>(dimensions); + } + + @Override + public software.amazon.lambda.powertools.metrics.model.DimensionSet getDefaultDimensions() { + return software.amazon.lambda.powertools.metrics.model.DimensionSet.of(defaultDimensions); + } + + @Override + public void setNamespace(String namespace) { + Validator.validateNamespace(namespace); + + this.namespace = namespace; + try { + emfLogger.setNamespace(namespace); + } catch (Exception e) { + LOGGER.error("Namespace cannot be set due to an error in EMF", e); + } + } + + @Override + public void setRaiseOnEmptyMetrics(boolean raiseOnEmptyMetrics) { + this.raiseOnEmptyMetrics.set(raiseOnEmptyMetrics); + } + + @Override + public void setTimestamp(Instant timestamp) { + Validator.validateTimestamp(timestamp); + + emfLogger.setTimestamp(timestamp); + } + + @Override + public void clearDefaultDimensions() { + emfLogger.resetDimensions(false); + defaultDimensions.clear(); + } + + @Override + public void flush() { + if (isMetricsDisabled()) { + LOGGER.debug("Metrics are disabled, skipping flush"); + return; + } + + Validator.validateNamespace(namespace); + + if (!hasMetrics.get()) { + if (raiseOnEmptyMetrics.get()) { + throw new IllegalStateException("No metrics were emitted"); + } else { + LOGGER.warn("No metrics were emitted"); + } + } else { + emfLogger.flush(); + + // Clear custom dimensions after flush while preserving default dimensions + clearCustomDimensions(); + } + } + + private void clearCustomDimensions() { + // Reset all dimensions in the EMF logger + emfLogger.resetDimensions(false); + + // Re-apply default dimensions if they exist + if (!defaultDimensions.isEmpty()) { + DimensionSet emfDimensionSet = new DimensionSet(); + defaultDimensions.forEach((key, value) -> { + try { + emfDimensionSet.addDimension(key, value); + } catch (Exception e) { + // Ignore dimension errors + } + }); + emfLogger.setDimensions(emfDimensionSet); + } + } + + @Override + public void captureColdStartMetric(Context context, + software.amazon.lambda.powertools.metrics.model.DimensionSet dimensions) { + if (isColdStart()) { + flushMetrics(metrics -> { + MetricsUtils.addRequestIdAndXrayTraceIdIfAvailable(context, metrics); + if (dimensions != null) { + metrics.setDefaultDimensions(dimensions); + } + metrics.addMetric(COLD_START_METRIC, 1, MetricUnit.COUNT); + }); + } + } + + @Override + public void captureColdStartMetric(software.amazon.lambda.powertools.metrics.model.DimensionSet dimensions) { + captureColdStartMetric(null, dimensions); + } + + @Override + public void flushMetrics(Consumer<Metrics> metricsConsumer) { + if (isMetricsDisabled()) { + LOGGER.debug("Metrics are disabled, skipping single metric flush"); + return; + } + // Create a new instance, inheriting namespace, default dimensions, and metadata + EmfMetricsLogger metrics = new EmfMetricsLogger(environmentProvider, new MetricsContext()); + if (namespace != null) { + metrics.setNamespace(this.namespace); + } + if (!defaultDimensions.isEmpty()) { + metrics.setDefaultDimensions( + software.amazon.lambda.powertools.metrics.model.DimensionSet.of(defaultDimensions)); + } + properties.forEach(metrics::addMetadata); + + metricsConsumer.accept(metrics); + + metrics.flush(); + } + + private boolean isMetricsDisabled() { + String disabledValue = System.getenv(METRICS_DISABLED_ENV_VAR); + return "true".equalsIgnoreCase(disabledValue); + } + + private Unit convertUnit(MetricUnit unit) { + switch (unit) { + case SECONDS: + return Unit.SECONDS; + case MICROSECONDS: + return Unit.MICROSECONDS; + case MILLISECONDS: + return Unit.MILLISECONDS; + case BYTES: + return Unit.BYTES; + case KILOBYTES: + return Unit.KILOBYTES; + case MEGABYTES: + return Unit.MEGABYTES; + case GIGABYTES: + return Unit.GIGABYTES; + case TERABYTES: + return Unit.TERABYTES; + case BITS: + return Unit.BITS; + case KILOBITS: + return Unit.KILOBITS; + case MEGABITS: + return Unit.MEGABITS; + case GIGABITS: + return Unit.GIGABITS; + case TERABITS: + return Unit.TERABITS; + case PERCENT: + return Unit.PERCENT; + case COUNT: + return Unit.COUNT; + case BYTES_SECOND: + return Unit.BYTES_SECOND; + case KILOBYTES_SECOND: + return Unit.KILOBYTES_SECOND; + case MEGABYTES_SECOND: + return Unit.MEGABYTES_SECOND; + case GIGABYTES_SECOND: + return Unit.GIGABYTES_SECOND; + case TERABYTES_SECOND: + return Unit.TERABYTES_SECOND; + case BITS_SECOND: + return Unit.BITS_SECOND; + case KILOBITS_SECOND: + return Unit.KILOBITS_SECOND; + case MEGABITS_SECOND: + return Unit.MEGABITS_SECOND; + case GIGABITS_SECOND: + return Unit.GIGABITS_SECOND; + case TERABITS_SECOND: + return Unit.TERABITS_SECOND; + case COUNT_SECOND: + return Unit.COUNT_SECOND; + case NONE: + default: + return Unit.NONE; + } + } +} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspect.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspect.java index 09fd5d87d..1f0e3ec8c 100644 --- a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspect.java +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspect.java @@ -1,141 +1,139 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.metrics.internal; -import java.lang.reflect.Field; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.coldStartDone; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.extractContext; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.isHandlerMethod; -import com.amazonaws.services.lambda.runtime.Context; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.cloudwatchlogs.emf.model.DimensionSet; -import software.amazon.cloudwatchlogs.emf.model.MetricsContext; -import software.amazon.cloudwatchlogs.emf.model.Unit; -import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; + +import com.amazonaws.services.lambda.runtime.Context; + +import software.amazon.lambda.powertools.common.internal.LambdaConstants; +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.metrics.FlushMetrics; import software.amazon.lambda.powertools.metrics.Metrics; -import software.amazon.lambda.powertools.metrics.MetricsUtils; -import software.amazon.lambda.powertools.metrics.ValidationException; - -import static software.amazon.cloudwatchlogs.emf.model.MetricsLoggerHelper.dimensionsCount; -import static software.amazon.cloudwatchlogs.emf.model.MetricsLoggerHelper.hasNoMetrics; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.coldStartDone; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.extractContext; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isColdStart; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isHandlerMethod; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnRequestHandler; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnStreamHandler; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.serviceName; -import static software.amazon.lambda.powertools.metrics.MetricsUtils.hasDefaultDimension; -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; @Aspect public class LambdaMetricsAspect { - private static final String NAMESPACE = System.getenv("POWERTOOLS_METRICS_NAMESPACE"); - public static final String TRACE_ID_PROPERTY = "xray_trace_id"; - public static final String REQUEST_ID_PROPERTY = "function_request_id"; + private static final String SERVICE_DIMENSION = "Service"; + private static final String FUNCTION_NAME_ENV_VAR = "POWERTOOLS_METRICS_FUNCTION_NAME"; + + private String functionName(FlushMetrics metrics, Context context) { + if (!"".equals(metrics.functionName())) { + return metrics.functionName(); + } + + String envFunctionName = System.getenv(FUNCTION_NAME_ENV_VAR); + if (envFunctionName != null && !envFunctionName.isEmpty()) { + return envFunctionName; + } + + return context != null ? context.getFunctionName() : null; + } + + private String serviceNameWithFallback(FlushMetrics metrics) { + if (!"".equals(metrics.service())) { + return metrics.service(); + } + return LambdaHandlerProcessor.serviceName(); + } - @SuppressWarnings({"EmptyMethod"}) + @SuppressWarnings({ "EmptyMethod" }) @Pointcut("@annotation(metrics)") - public void callAt(Metrics metrics) { + public void callAt(FlushMetrics metrics) { + // AspectJ point cut referenced in around() method } - @Around(value = "callAt(metrics) && execution(@Metrics * *.*(..))", argNames = "pjp,metrics") + @Around(value = "callAt(metrics) && execution(@FlushMetrics * *.*(..))", argNames = "pjp,metrics") public Object around(ProceedingJoinPoint pjp, - Metrics metrics) throws Throwable { + FlushMetrics metrics) throws Throwable { Object[] proceedArgs = pjp.getArgs(); - if (isHandlerMethod(pjp) - && (placedOnRequestHandler(pjp) - || placedOnStreamHandler(pjp))) { + if (isHandlerMethod(pjp)) { + Metrics metricsInstance = MetricsFactory.getMetricsInstance(); - MetricsLogger logger = metricsLogger(); + // The MetricsFactory applies default settings from the environment or can be configured by the + // MetricsBuilder. We only overwrite settings if they are explicitly set in the @FlushMetrics + // annotation. + if (!"".equals(metrics.namespace())) { + metricsInstance.setNamespace(metrics.namespace()); + } - refreshMetricsContext(metrics); + // We only overwrite the default dimensions if the user didn't overwrite them previously. This means that + // they are either empty or only contain the default "Service" dimension. + if (!"".equals(metrics.service().trim()) + && (metricsInstance.getDefaultDimensions().getDimensionKeys().size() <= 1 + || metricsInstance.getDefaultDimensions().getDimensionKeys().contains(SERVICE_DIMENSION))) { + metricsInstance.setDefaultDimensions(DimensionSet.of(SERVICE_DIMENSION, metrics.service())); + } - logger.setNamespace(namespace(metrics)); + metricsInstance.setRaiseOnEmptyMetrics(metrics.raiseOnEmptyMetrics()); Context extractedContext = extractContext(pjp); + MetricsUtils.addRequestIdAndXrayTraceIdIfAvailable(extractedContext, metricsInstance); - if( null != extractedContext) { - coldStartSingleMetricIfApplicable(extractedContext.getAwsRequestId(), extractedContext.getFunctionName(), metrics); - logger.putProperty(REQUEST_ID_PROPERTY, extractedContext.getAwsRequestId()); - } - - LambdaHandlerProcessor.getXrayTraceId() - .ifPresent(traceId -> logger.putProperty(TRACE_ID_PROPERTY, traceId)); + captureColdStartMetricIfEnabled(extractedContext, metrics); try { return pjp.proceed(proceedArgs); - } finally { coldStartDone(); - validateMetricsAndRefreshOnFailure(metrics); - logger.flush(); - refreshMetricsContext(metrics); + metricsInstance.flush(); } } return pjp.proceed(proceedArgs); } - private void coldStartSingleMetricIfApplicable(final String awsRequestId, - final String functionName, - final Metrics metrics) { - if (metrics.captureColdStart() - && isColdStart()) { - MetricsLogger metricsLogger = new MetricsLogger(); - metricsLogger.setNamespace(namespace(metrics)); - metricsLogger.putMetric("ColdStart", 1, Unit.COUNT); - metricsLogger.setDimensions(DimensionSet.of("Service", service(metrics), "FunctionName", functionName)); - metricsLogger.putProperty(REQUEST_ID_PROPERTY, awsRequestId); - metricsLogger.flush(); - } - - } - - private void validateBeforeFlushingMetrics(Metrics metrics) { - if (metrics.raiseOnEmptyMetrics() && hasNoMetrics()) { - throw new ValidationException("No metrics captured, at least one metrics must be emitted"); - } - - if (dimensionsCount() > 9) { - throw new ValidationException(String.format("Number of Dimensions must be in range of 0-9." + - " Actual size: %d.", dimensionsCount())); + private void captureColdStartMetricIfEnabled(Context extractedContext, FlushMetrics flushMetrics) { + if (extractedContext == null) { + return; } - } - private String namespace(Metrics metrics) { - return !"".equals(metrics.namespace()) ? metrics.namespace() : NAMESPACE; - } + Metrics metrics = MetricsFactory.getMetricsInstance(); - private static String service(Metrics metrics) { - return !"".equals(metrics.service()) ? metrics.service() : serviceName(); - } + // Only capture cold start metrics if enabled on annotation + if (flushMetrics.captureColdStart()) { + // Get function name from annotation or context + String funcName = functionName(flushMetrics, extractedContext); - private void validateMetricsAndRefreshOnFailure(Metrics metrics) { - try { - validateBeforeFlushingMetrics(metrics); - } catch (ValidationException e){ - refreshMetricsContext(metrics); - throw e; - } - } + DimensionSet dimensionSet = new DimensionSet(); - // This can be simplified after this issues https://github.com/awslabs/aws-embedded-metrics-java/issues/35 is fixed - public static void refreshMetricsContext(Metrics metrics) { - try { - Field f = metricsLogger().getClass().getDeclaredField("context"); - f.setAccessible(true); - MetricsContext context = new MetricsContext(); + // Get service name from metrics instance default dimensions or fallback + String serviceName = metrics.getDefaultDimensions().getDimensions().getOrDefault( + SERVICE_DIMENSION, + serviceNameWithFallback(flushMetrics)); - DimensionSet[] defaultDimensions = hasDefaultDimension() ? MetricsUtils.getDefaultDimensions() - : new DimensionSet[]{DimensionSet.of("Service", service(metrics))}; + // Only add service if it is not undefined + if (!LambdaConstants.SERVICE_UNDEFINED.equals(serviceName)) { + dimensionSet.addDimension(SERVICE_DIMENSION, serviceName); + } - context.setDimensions(defaultDimensions); + // Add function name + if (funcName != null) { + dimensionSet.addDimension("FunctionName", funcName); + } - f.set(metricsLogger(), context); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); + metrics.captureColdStartMetric(extractedContext, dimensionSet); } } } diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/MetricsUserAgentInterceptor.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/MetricsUserAgentInterceptor.java new file mode 100644 index 000000000..5a466cc3a --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/MetricsUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.metrics.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the metrics module is on the classpath. + */ +public final class MetricsUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("metrics"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/MetricsUtils.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/MetricsUtils.java new file mode 100644 index 000000000..246f6effc --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/MetricsUtils.java @@ -0,0 +1,22 @@ +package software.amazon.lambda.powertools.metrics.internal; + +import com.amazonaws.services.lambda.runtime.Context; +import software.amazon.lambda.powertools.metrics.Metrics; + +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.getXrayTraceId; + +final class MetricsUtils { + private static final String TRACE_ID_PROPERTY = "xray_trace_id"; + private static final String REQUEST_ID_PROPERTY = "function_request_id"; + + private MetricsUtils() { + // Utility class + } + + static void addRequestIdAndXrayTraceIdIfAvailable(Context context, Metrics metrics) { + if (context != null && context.getAwsRequestId() != null) { + metrics.addMetadata(REQUEST_ID_PROPERTY, context.getAwsRequestId()); + } + getXrayTraceId().ifPresent(traceId -> metrics.addMetadata(TRACE_ID_PROPERTY, traceId)); + } +} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/RequestScopedMetricsProxy.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/RequestScopedMetricsProxy.java new file mode 100644 index 000000000..61c83be17 --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/RequestScopedMetricsProxy.java @@ -0,0 +1,156 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics.internal; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import com.amazonaws.services.lambda.runtime.Context; + +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricResolution; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import software.amazon.lambda.powertools.metrics.provider.MetricsProvider; + +public class RequestScopedMetricsProxy implements Metrics { + private static final String DEFAULT_TRACE_ID = "DEFAULT"; + private final ConcurrentMap<String, Metrics> metricsMap = new ConcurrentHashMap<>(); + private final MetricsProvider provider; + private final AtomicReference<String> initialNamespace = new AtomicReference<>(); + private final AtomicReference<DimensionSet> initialDefaultDimensions = new AtomicReference<>(); + private final AtomicBoolean initialRaiseOnEmptyMetrics = new AtomicBoolean(false); + + public RequestScopedMetricsProxy(MetricsProvider provider) { + this.provider = provider; + } + + private String getTraceId() { + return LambdaHandlerProcessor.getXrayTraceId().orElse(DEFAULT_TRACE_ID); + } + + private Metrics getOrCreateMetrics() { + String traceId = getTraceId(); + return metricsMap.computeIfAbsent(traceId, key -> { + Metrics metrics = provider.getMetricsInstance(); + String namespace = initialNamespace.get(); + if (namespace != null) { + metrics.setNamespace(namespace); + } + DimensionSet dimensions = initialDefaultDimensions.get(); + if (dimensions != null) { + metrics.setDefaultDimensions(dimensions); + } + metrics.setRaiseOnEmptyMetrics(initialRaiseOnEmptyMetrics.get()); + return metrics; + }); + } + + // Configuration methods - called by MetricsFactory and MetricsBuilder + // These methods DO NOT eagerly create instances because they are typically called + // outside the Lambda handler (e.g., during class initialization) potentially on a different thread. + // We delay instance creation until the first operation that needs the metrics backend (e.g., addMetric). + // See {@link software.amazon.lambda.powertools.metrics.MetricsFactory#getMetricsInstance()} + // and {@link software.amazon.lambda.powertools.metrics.MetricsBuilder#build()} + + @Override + public void setNamespace(String namespace) { + this.initialNamespace.set(namespace); + Optional.ofNullable(metricsMap.get(getTraceId())).ifPresent(m -> m.setNamespace(namespace)); + } + + @Override + public void setDefaultDimensions(DimensionSet dimensionSet) { + if (dimensionSet == null) { + throw new IllegalArgumentException("DimensionSet cannot be null"); + } + this.initialDefaultDimensions.set(dimensionSet); + Optional.ofNullable(metricsMap.get(getTraceId())).ifPresent(m -> m.setDefaultDimensions(dimensionSet)); + } + + @Override + public void setRaiseOnEmptyMetrics(boolean raiseOnEmptyMetrics) { + this.initialRaiseOnEmptyMetrics.set(raiseOnEmptyMetrics); + Optional.ofNullable(metricsMap.get(getTraceId())).ifPresent(m -> m.setRaiseOnEmptyMetrics(raiseOnEmptyMetrics)); + } + + @Override + public DimensionSet getDefaultDimensions() { + Metrics metrics = metricsMap.get(getTraceId()); + if (metrics != null) { + return metrics.getDefaultDimensions(); + } + DimensionSet dimensions = initialDefaultDimensions.get(); + return dimensions != null ? dimensions : DimensionSet.of(new HashMap<>()); + } + + // Metrics operations - these eagerly create instances + + @Override + public void addMetric(String key, double value, MetricUnit unit, MetricResolution resolution) { + getOrCreateMetrics().addMetric(key, value, unit, resolution); + } + + @Override + public void addDimension(DimensionSet dimensionSet) { + getOrCreateMetrics().addDimension(dimensionSet); + } + + @Override + public void setTimestamp(Instant timestamp) { + getOrCreateMetrics().setTimestamp(timestamp); + } + + @Override + public void addMetadata(String key, Object value) { + getOrCreateMetrics().addMetadata(key, value); + } + + @Override + public void clearDefaultDimensions() { + getOrCreateMetrics().clearDefaultDimensions(); + } + + @Override + public void flush() { + // Always create instance to ensure validation and warnings are triggered. E.g. when raiseOnEmptyMetrics + // is enabled. + Metrics metrics = getOrCreateMetrics(); + metrics.flush(); + metricsMap.remove(getTraceId()); + } + + @Override + public void captureColdStartMetric(Context context, DimensionSet dimensions) { + getOrCreateMetrics().captureColdStartMetric(context, dimensions); + } + + @Override + public void captureColdStartMetric(DimensionSet dimensions) { + getOrCreateMetrics().captureColdStartMetric(dimensions); + } + + @Override + public void flushMetrics(Consumer<Metrics> metricsConsumer) { + getOrCreateMetrics().flushMetrics(metricsConsumer); + } +} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/Validator.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/Validator.java new file mode 100644 index 000000000..eebb54739 --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/Validator.java @@ -0,0 +1,135 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics.internal; + +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang3.StringUtils; + +/** + * Utility class for validating metrics-related parameters. + */ +public class Validator { + private static final int MAX_DIMENSION_NAME_LENGTH = 250; + private static final int MAX_DIMENSION_VALUE_LENGTH = 1024; + private static final int MAX_NAMESPACE_LENGTH = 255; + private static final String NAMESPACE_REGEX = "^[a-zA-Z0-9._#:/-]+$"; + public static final long MAX_TIMESTAMP_PAST_AGE_SECONDS = TimeUnit.DAYS.toSeconds(14); + public static final long MAX_TIMESTAMP_FUTURE_AGE_SECONDS = TimeUnit.HOURS.toSeconds(2); + + private Validator() { + // Private constructor to prevent instantiation + } + + /** + * Validates that a namespace is properly specified. + * + * @param namespace The namespace to validate + * @throws IllegalArgumentException if the namespace is invalid + */ + public static void validateNamespace(String namespace) { + if (namespace == null || namespace.trim().isEmpty()) { + throw new IllegalArgumentException("Namespace must be specified before flushing metrics"); + } + + if (namespace.length() > MAX_NAMESPACE_LENGTH) { + throw new IllegalArgumentException( + "Namespace exceeds maximum length of " + MAX_NAMESPACE_LENGTH + ": " + namespace); + } + + if (!namespace.matches(NAMESPACE_REGEX)) { + throw new IllegalArgumentException("Namespace contains invalid characters: " + namespace); + } + } + + /** + * Validates Timestamp. + * + * @see <a + * href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#about_timestamp">CloudWatch + * Timestamp</a> + * @param timestamp Timestamp + * @throws IllegalArgumentException if timestamp is invalid + */ + public static void validateTimestamp(Instant timestamp) { + if (timestamp == null) { + throw new IllegalArgumentException("Timestamp cannot be null"); + } + + if (timestamp.isAfter( + Instant.now().plusSeconds(MAX_TIMESTAMP_FUTURE_AGE_SECONDS))) { + throw new IllegalArgumentException( + "Timestamp cannot be more than " + + MAX_TIMESTAMP_FUTURE_AGE_SECONDS + + " seconds in the future"); + } + + if (timestamp.isBefore( + Instant.now().minusSeconds(MAX_TIMESTAMP_PAST_AGE_SECONDS))) { + throw new IllegalArgumentException( + "Timestamp cannot be more than " + + MAX_TIMESTAMP_PAST_AGE_SECONDS + + " seconds in the past"); + } + } + + /** + * Validates a dimension key-value pair. + * + * @param key The dimension key to validate + * @param value The dimension value to validate + * @throws IllegalArgumentException if the key or value is invalid + */ + public static void validateDimension(String key, String value) { + if (key == null || key.trim().isEmpty()) { + throw new IllegalArgumentException("Dimension key cannot be null or empty"); + } + + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException("Dimension value cannot be null or empty"); + } + + if (StringUtils.containsWhitespace(key)) { + throw new IllegalArgumentException("Dimension key cannot contain whitespaces: " + key); + } + + if (StringUtils.containsWhitespace(value)) { + throw new IllegalArgumentException("Dimension value cannot contain whitespaces: " + value); + } + + if (key.startsWith(":")) { + throw new IllegalArgumentException("Dimension key cannot start with colon: " + key); + } + + if (key.length() > MAX_DIMENSION_NAME_LENGTH) { + throw new IllegalArgumentException( + "Dimension name exceeds maximum length of " + MAX_DIMENSION_NAME_LENGTH + ": " + key); + } + + if (value.length() > MAX_DIMENSION_VALUE_LENGTH) { + throw new IllegalArgumentException( + "Dimension value exceeds maximum length of " + MAX_DIMENSION_VALUE_LENGTH + ": " + value); + } + + if (!StringUtils.isAsciiPrintable(key)) { + throw new IllegalArgumentException("Dimension name has invalid characters: " + key); + } + + if (!StringUtils.isAsciiPrintable(value)) { + throw new IllegalArgumentException("Dimension value has invalid characters: " + value); + } + } +} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/model/DimensionSet.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/model/DimensionSet.java new file mode 100644 index 000000000..e93f34237 --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/model/DimensionSet.java @@ -0,0 +1,193 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics.model; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import software.amazon.lambda.powertools.metrics.internal.Validator; + +/** + * Represents a set of dimensions for CloudWatch metrics + */ +public class DimensionSet { + private static final int MAX_DIMENSION_SET_SIZE = 30; + + private final Map<String, String> dimensions = new LinkedHashMap<>(); + + /** + * Create a dimension set with a single key-value pair + * + * @param key dimension key + * @param value dimension value + * @return a new DimensionSet + */ + public static DimensionSet of(String key, String value) { + DimensionSet dimensionSet = new DimensionSet(); + dimensionSet.addDimension(key, value); + return dimensionSet; + } + + /** + * Create a dimension set with two key-value pairs + * + * @param key1 first dimension key + * @param value1 first dimension value + * @param key2 second dimension key + * @param value2 second dimension value + * @return a new DimensionSet + */ + public static DimensionSet of(String key1, String value1, String key2, String value2) { + DimensionSet dimensionSet = new DimensionSet(); + dimensionSet.addDimension(key1, value1); + dimensionSet.addDimension(key2, value2); + return dimensionSet; + } + + /** + * Create a dimension set with three key-value pairs + * + * @param key1 first dimension key + * @param value1 first dimension value + * @param key2 second dimension key + * @param value2 second dimension value + * @param key3 third dimension key + * @param value3 third dimension value + * @return a new DimensionSet + */ + public static DimensionSet of(String key1, String value1, String key2, String value2, String key3, String value3) { + DimensionSet dimensionSet = new DimensionSet(); + dimensionSet.addDimension(key1, value1); + dimensionSet.addDimension(key2, value2); + dimensionSet.addDimension(key3, value3); + return dimensionSet; + } + + /** + * Create a dimension set with four key-value pairs + * + * @param key1 first dimension key + * @param value1 first dimension value + * @param key2 second dimension key + * @param value2 second dimension value + * @param key3 third dimension key + * @param value3 third dimension value + * @param key4 fourth dimension key + * @param value4 fourth dimension value + * @return a new DimensionSet + */ + public static DimensionSet of(String key1, String value1, String key2, String value2, + String key3, String value3, String key4, String value4) { + DimensionSet dimensionSet = new DimensionSet(); + dimensionSet.addDimension(key1, value1); + dimensionSet.addDimension(key2, value2); + dimensionSet.addDimension(key3, value3); + dimensionSet.addDimension(key4, value4); + return dimensionSet; + } + + /** + * Create a dimension set with five key-value pairs + * + * @param key1 first dimension key + * @param value1 first dimension value + * @param key2 second dimension key + * @param value2 second dimension value + * @param key3 third dimension key + * @param value3 third dimension value + * @param key4 fourth dimension key + * @param value4 fourth dimension value + * @param key5 fifth dimension key + * @param value5 fifth dimension value + * @return a new DimensionSet + */ + public static DimensionSet of(String key1, String value1, String key2, String value2, + String key3, String value3, String key4, String value4, + String key5, String value5) { + DimensionSet dimensionSet = new DimensionSet(); + dimensionSet.addDimension(key1, value1); + dimensionSet.addDimension(key2, value2); + dimensionSet.addDimension(key3, value3); + dimensionSet.addDimension(key4, value4); + dimensionSet.addDimension(key5, value5); + return dimensionSet; + } + + /** + * Create a dimension set from a map of key-value pairs + * + * @param dimensions map of dimension key-value pairs + * @return a new DimensionSet + */ + public static DimensionSet of(Map<String, String> dimensions) { + DimensionSet dimensionSet = new DimensionSet(); + dimensions.forEach(dimensionSet::addDimension); + return dimensionSet; + } + + /** + * Add a dimension to this dimension set + * + * @param key dimension key + * @param value dimension value + * @return this dimension set for chaining + * @throws IllegalArgumentException if key or value is invalid + * @throws IllegalStateException if adding would exceed the maximum number of dimensions + */ + public DimensionSet addDimension(String key, String value) { + validateDimension(key, value); + + if (dimensions.size() >= MAX_DIMENSION_SET_SIZE) { + throw new IllegalStateException( + "Cannot exceed " + MAX_DIMENSION_SET_SIZE + " dimensions per dimension set"); + } + + dimensions.put(key, value); + return this; + } + + /** + * Get the dimension keys in this dimension set + * + * @return set of dimension keys + */ + public Set<String> getDimensionKeys() { + return dimensions.keySet(); + } + + /** + * Get the value for a dimension key + * + * @param key dimension key + * @return dimension value or null if not found + */ + public String getDimensionValue(String key) { + return dimensions.get(key); + } + + /** + * Get the dimensions as a map. Creates a shallow copy + * + * @return map of dimensions + */ + public Map<String, String> getDimensions() { + return new LinkedHashMap<>(dimensions); + } + + private void validateDimension(String key, String value) { + Validator.validateDimension(key, value); + } +} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/model/MetricResolution.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/model/MetricResolution.java new file mode 100644 index 000000000..db514c8b7 --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/model/MetricResolution.java @@ -0,0 +1,32 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics.model; + +/** + * Resolution for metrics + */ +public enum MetricResolution { + STANDARD(60), HIGH(1); + + private final int seconds; + + MetricResolution(int seconds) { + this.seconds = seconds; + } + + public int getSeconds() { + return seconds; + } +} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/model/MetricUnit.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/model/MetricUnit.java new file mode 100644 index 000000000..445d950b2 --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/model/MetricUnit.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics.model; + +/** + * Metric units supported by CloudWatch + */ +public enum MetricUnit { + SECONDS("Seconds"), + MICROSECONDS("Microseconds"), + MILLISECONDS("Milliseconds"), + BYTES("Bytes"), + KILOBYTES("Kilobytes"), + MEGABYTES("Megabytes"), + GIGABYTES("Gigabytes"), + TERABYTES("Terabytes"), + BITS("Bits"), + KILOBITS("Kilobits"), + MEGABITS("Megabits"), + GIGABITS("Gigabits"), + TERABITS("Terabits"), + PERCENT("Percent"), + COUNT("Count"), + BYTES_SECOND("Bytes/Second"), + KILOBYTES_SECOND("Kilobytes/Second"), + MEGABYTES_SECOND("Megabytes/Second"), + GIGABYTES_SECOND("Gigabytes/Second"), + TERABYTES_SECOND("Terabytes/Second"), + BITS_SECOND("Bits/Second"), + KILOBITS_SECOND("Kilobits/Second"), + MEGABITS_SECOND("Megabits/Second"), + GIGABITS_SECOND("Gigabits/Second"), + TERABITS_SECOND("Terabits/Second"), + COUNT_SECOND("Count/Second"), + NONE("None"); + + private final String name; + + MetricUnit(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/provider/EmfMetricsProvider.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/provider/EmfMetricsProvider.java new file mode 100644 index 000000000..12c99b18f --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/provider/EmfMetricsProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics.provider; + +import software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider; +import software.amazon.cloudwatchlogs.emf.model.MetricsContext; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.internal.EmfMetricsLogger; + +/** + * Provider implementation for EMF metrics + */ +public class EmfMetricsProvider implements MetricsProvider { + + @Override + public Metrics getMetricsInstance() { + return new EmfMetricsLogger(new EnvironmentProvider(), new MetricsContext()); + } +} diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/provider/MetricsProvider.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/provider/MetricsProvider.java new file mode 100644 index 000000000..e6c79e000 --- /dev/null +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/provider/MetricsProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics.provider; + +import software.amazon.lambda.powertools.metrics.Metrics; + +/** + * Interface for metrics provider implementations + */ +public interface MetricsProvider { + + /** + * Get a new instance of a metrics implementation + * + * @return a new metrics instance + */ + Metrics getMetricsInstance(); +} diff --git a/powertools-metrics/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-metrics/jni-config.json b/powertools-metrics/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-metrics/jni-config.json new file mode 100644 index 000000000..75ee9e5f5 --- /dev/null +++ b/powertools-metrics/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-metrics/jni-config.json @@ -0,0 +1,33 @@ +[ + { + "name": "java.lang.Boolean", + "methods": [{ "name": "getBoolean", "parameterTypes": ["java.lang.String"] }] + }, + { + "name": "java.lang.String", + "methods": [ + { "name": "lastIndexOf", "parameterTypes": ["int"] }, + { "name": "substring", "parameterTypes": ["int"] } + ] + }, + { + "name": "java.lang.System", + "methods": [ + { "name": "getProperty", "parameterTypes": ["java.lang.String"] }, + { "name": "setProperty", "parameterTypes": ["java.lang.String", "java.lang.String"] } + ] + }, + { + "name": "sun.management.VMManagementImpl", + "fields": [ + { "name": "compTimeMonitoringSupport" }, + { "name": "currentThreadCpuTimeSupport" }, + { "name": "objectMonitorUsageSupport" }, + { "name": "otherThreadCpuTimeSupport" }, + { "name": "remoteDiagnosticCommandsSupport" }, + { "name": "synchronizerUsageSupport" }, + { "name": "threadAllocatedMemorySupport" }, + { "name": "threadContentionMonitoringSupport" } + ] + } +] diff --git a/powertools-metrics/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-metrics/reflect-config.json b/powertools-metrics/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-metrics/reflect-config.json new file mode 100644 index 000000000..a0ac5bfec --- /dev/null +++ b/powertools-metrics/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-metrics/reflect-config.json @@ -0,0 +1,156 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.Context", + "allDeclaredClasses": true, + "queryAllPublicMethods": true + }, + { + "name": "com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "java.io.Serializable", + "queryAllDeclaredMethods": true + }, + { + "name": "java.lang.Comparable", + "queryAllDeclaredMethods": true + }, + { + "name": "java.lang.Double", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "queryAllDeclaredConstructors": true + }, + { + "name": "java.lang.Number", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true + }, + { + "name": "java.lang.ProcessEnvironment", + "fields": [{ "name": "theCaseInsensitiveEnvironment" }, { "name": "theEnvironment" }] + }, + { + "name": "java.lang.String" + }, + { + "name": "java.lang.constant.Constable", + "queryAllDeclaredMethods": true + }, + { + "name": "java.lang.constant.ConstantDesc", + "queryAllDeclaredMethods": true + }, + { + "name": "java.util.Collections$UnmodifiableMap", + "fields": [{ "name": "m" }] + }, + { + "name": "java.util.Map" + }, + { + "name": "java.util.concurrent.atomic.AtomicBoolean", + "fields": [{ "name": "value" }] + }, + { + "name": "java.util.concurrent.atomic.AtomicReference", + "fields": [{ "name": "value" }] + }, + { + "name": "java.util.function.Consumer", + "queryAllPublicMethods": true + }, + { + "name": "org.apiguardian.api.API", + "queryAllPublicMethods": true + }, + { + "name": "software.amazon.cloudwatchlogs.emf.model.Metadata", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "queryAllDeclaredConstructors": true, + "methods": [ + { "name": "getCloudWatchMetrics", "parameterTypes": [] }, + { "name": "getCustomMetadata", "parameterTypes": [] }, + { "name": "getTimestamp", "parameterTypes": [] } + ] + }, + { + "name": "software.amazon.cloudwatchlogs.emf.model.MetricDefinition", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "queryAllDeclaredConstructors": true, + "methods": [ + { "name": "getName", "parameterTypes": [] }, + { "name": "getStorageResolution", "parameterTypes": [] }, + { "name": "getUnit", "parameterTypes": [] } + ] + }, + { + "name": "software.amazon.cloudwatchlogs.emf.model.MetricDirective", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "queryAllDeclaredConstructors": true, + "methods": [ + { "name": "getAllDimensionKeys", "parameterTypes": [] }, + { "name": "getAllMetrics", "parameterTypes": [] }, + { "name": "getNamespace", "parameterTypes": [] } + ] + }, + { + "name": "software.amazon.cloudwatchlogs.emf.model.RootNode", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "queryAllDeclaredConstructors": true, + "methods": [ + { "name": "getAws", "parameterTypes": [] }, + { "name": "getTargetMembers", "parameterTypes": [] } + ] + }, + { + "name": "software.amazon.cloudwatchlogs.emf.serializers.InstantSerializer", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "software.amazon.cloudwatchlogs.emf.serializers.StorageResolutionFilter", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "software.amazon.cloudwatchlogs.emf.serializers.StorageResolutionSerializer", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "software.amazon.cloudwatchlogs.emf.serializers.UnitSerializer", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + }, + { + "name": "software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor", + "fields": [{ "name": "isColdStart" }], + "methods": [{ "name": "resetServiceName", "parameterTypes": [] }] + }, + { + "name": "software.amazon.lambda.powertools.metrics.Metrics", + "allDeclaredClasses": true, + "queryAllPublicMethods": true + }, + { + "name": "software.amazon.lambda.powertools.metrics.MetricsFactory", + "fields": [{ "name": "metrics" }, { "name": "provider" }] + }, + { + "name": "software.amazon.lambda.powertools.metrics.internal.EmfMetricsLogger", + "methods": [ + { "name": "convertUnit", "parameterTypes": ["software.amazon.lambda.powertools.metrics.model.MetricUnit"] } + ] + }, + { + "name": "software.amazon.lambda.powertools.metrics.provider.MetricsProvider", + "allDeclaredClasses": true, + "queryAllPublicMethods": true + }, + { + "name": "software.amazon.lambda.powertools.metrics.internal.MetricsUserAgentInterceptor", + "methods": [{ "name": "<init>", "parameterTypes": [] }] + } +] diff --git a/powertools-metrics/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-metrics/resource-config.json b/powertools-metrics/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-metrics/resource-config.json new file mode 100644 index 000000000..0667ee26a --- /dev/null +++ b/powertools-metrics/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-metrics/resource-config.json @@ -0,0 +1,16 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, + { + "pattern": "\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, + { + "pattern": "\\Qsoftware/amazon/awssdk/global/handlers/execution.interceptors\\E" + } + ] + }, + "bundles": [] +} diff --git a/powertools-metrics/src/main/resources/classesloaded.txt b/powertools-metrics/src/main/resources/classesloaded.txt new file mode 100644 index 000000000..f56d6af78 --- /dev/null +++ b/powertools-metrics/src/main/resources/classesloaded.txt @@ -0,0 +1,3651 @@ +java.lang.Object +java.io.Serializable +java.lang.Comparable +java.lang.CharSequence +java.lang.constant.Constable +java.lang.constant.ConstantDesc +java.lang.String +java.lang.reflect.AnnotatedElement +java.lang.reflect.GenericDeclaration +java.lang.reflect.Type +java.lang.invoke.TypeDescriptor +java.lang.invoke.TypeDescriptor$OfField +java.lang.Class +java.lang.Cloneable +java.lang.ClassLoader +java.lang.System +java.lang.Throwable +java.lang.Error +java.lang.ThreadDeath +java.lang.Exception +java.lang.RuntimeException +java.lang.SecurityManager +java.security.ProtectionDomain +java.security.AccessControlContext +java.security.AccessController +java.security.SecureClassLoader +java.lang.ReflectiveOperationException +java.lang.ClassNotFoundException +java.lang.Record +java.lang.LinkageError +java.lang.NoClassDefFoundError +java.lang.ClassCastException +java.lang.ArrayStoreException +java.lang.VirtualMachineError +java.lang.InternalError +java.lang.OutOfMemoryError +java.lang.StackOverflowError +java.lang.IllegalMonitorStateException +java.lang.ref.Reference +java.lang.ref.SoftReference +java.lang.ref.WeakReference +java.lang.ref.FinalReference +java.lang.ref.PhantomReference +java.lang.ref.Finalizer +java.lang.Runnable +java.lang.Thread +java.lang.Thread$UncaughtExceptionHandler +java.lang.ThreadGroup +java.util.Dictionary +java.util.Map +java.util.Hashtable +java.util.Properties +java.lang.Module +java.lang.reflect.AccessibleObject +java.lang.reflect.Member +java.lang.reflect.Field +java.lang.reflect.Parameter +java.lang.reflect.Executable +java.lang.reflect.Method +java.lang.reflect.Constructor +jdk.internal.reflect.MagicAccessorImpl +jdk.internal.reflect.MethodAccessor +jdk.internal.reflect.MethodAccessorImpl +jdk.internal.reflect.ConstructorAccessor +jdk.internal.reflect.ConstructorAccessorImpl +jdk.internal.reflect.DelegatingClassLoader +jdk.internal.reflect.ConstantPool +jdk.internal.reflect.FieldAccessor +jdk.internal.reflect.FieldAccessorImpl +jdk.internal.reflect.UnsafeFieldAccessorImpl +jdk.internal.reflect.UnsafeStaticFieldAccessorImpl +java.lang.annotation.Annotation +jdk.internal.reflect.CallerSensitive +jdk.internal.reflect.NativeConstructorAccessorImpl +java.lang.invoke.MethodHandle +java.lang.invoke.DirectMethodHandle +java.lang.invoke.VarHandle +java.lang.invoke.MemberName +java.lang.invoke.ResolvedMethodName +java.lang.invoke.MethodHandleNatives +java.lang.invoke.LambdaForm +java.lang.invoke.TypeDescriptor$OfMethod +java.lang.invoke.MethodType +java.lang.BootstrapMethodError +java.lang.invoke.CallSite +jdk.internal.invoke.NativeEntryPoint +java.lang.invoke.MethodHandleNatives$CallSiteContext +java.lang.invoke.ConstantCallSite +java.lang.invoke.MutableCallSite +java.lang.invoke.VolatileCallSite +java.lang.AssertionStatusDirectives +java.lang.Appendable +java.lang.AbstractStringBuilder +java.lang.StringBuffer +java.lang.StringBuilder +jdk.internal.misc.UnsafeConstants +jdk.internal.misc.Unsafe +jdk.internal.module.Modules +java.lang.AutoCloseable +java.io.Closeable +java.io.InputStream +java.io.ByteArrayInputStream +java.net.URL +java.util.jar.Manifest +jdk.internal.loader.BuiltinClassLoader +jdk.internal.loader.ClassLoaders +jdk.internal.loader.ClassLoaders$AppClassLoader +jdk.internal.loader.ClassLoaders$PlatformClassLoader +java.security.CodeSource +java.util.AbstractMap +java.util.concurrent.ConcurrentMap +java.util.concurrent.ConcurrentHashMap +java.lang.Iterable +java.util.Collection +java.util.AbstractCollection +java.util.List +java.util.AbstractList +java.util.RandomAccess +java.util.ArrayList +java.lang.StackTraceElement +java.nio.Buffer +java.lang.StackWalker +java.lang.StackStreamFactory$AbstractStackWalker +java.lang.StackWalker$StackFrame +java.lang.StackFrameInfo +java.lang.LiveStackFrame +java.lang.LiveStackFrameInfo +java.util.concurrent.locks.AbstractOwnableSynchronizer +java.lang.Boolean +java.lang.Character +java.lang.Number +java.lang.Float +java.lang.Double +java.lang.Byte +java.lang.Short +java.lang.Integer +java.lang.Long +java.util.Iterator +java.lang.reflect.RecordComponent +jdk.internal.vm.vector.VectorSupport +jdk.internal.vm.vector.VectorSupport$VectorPayload +jdk.internal.vm.vector.VectorSupport$Vector +jdk.internal.vm.vector.VectorSupport$VectorMask +jdk.internal.vm.vector.VectorSupport$VectorShuffle +java.lang.Integer$IntegerCache +java.lang.Long$LongCache +java.lang.Byte$ByteCache +java.lang.Short$ShortCache +java.lang.Character$CharacterCache +java.util.jar.Attributes$Name +java.util.ImmutableCollections$AbstractImmutableMap +java.util.ImmutableCollections$MapN +sun.util.locale.BaseLocale +jdk.internal.module.ArchivedModuleGraph +java.lang.module.ModuleFinder +jdk.internal.module.SystemModuleFinders$SystemModuleFinder +java.util.ImmutableCollections$AbstractImmutableCollection +java.util.Set +java.util.ImmutableCollections$AbstractImmutableSet +java.util.ImmutableCollections$SetN +java.lang.module.ModuleReference +jdk.internal.module.ModuleReferenceImpl +java.lang.module.ModuleDescriptor +java.lang.module.ModuleDescriptor$Version +java.util.ImmutableCollections$Set12 +java.lang.module.ModuleDescriptor$Requires +java.lang.Enum +java.lang.module.ModuleDescriptor$Requires$Modifier +java.lang.module.ModuleDescriptor$Exports +java.net.URI +java.util.function.Supplier +jdk.internal.module.SystemModuleFinders$2 +jdk.internal.module.ModuleHashes$HashSupplier +jdk.internal.module.SystemModuleFinders$3 +java.lang.module.ModuleDescriptor$Provides +java.util.ImmutableCollections$AbstractImmutableList +java.util.ImmutableCollections$List12 +java.util.ImmutableCollections$ListN +java.lang.module.ModuleDescriptor$Opens +jdk.internal.module.ModuleTarget +jdk.internal.module.ModuleHashes +java.util.Collections$UnmodifiableMap +java.util.HashMap +java.util.Map$Entry +java.util.HashMap$Node +java.lang.module.Configuration +java.lang.module.ResolvedModule +java.util.function.Function +jdk.internal.module.ModuleLoaderMap$Mapper +java.util.ImmutableCollections +java.lang.ModuleLayer +jdk.internal.math.FDBigInteger +java.lang.NullPointerException +java.lang.ArithmeticException +java.io.ObjectStreamField +java.util.Comparator +java.lang.String$CaseInsensitiveComparator +java.lang.Module$ArchivedData +jdk.internal.misc.CDS +java.util.Objects +jdk.internal.access.JavaLangReflectAccess +java.lang.reflect.ReflectAccess +jdk.internal.access.SharedSecrets +java.lang.invoke.MethodHandles +java.lang.invoke.MemberName$Factory +java.security.Guard +java.security.Permission +java.security.BasicPermission +java.lang.reflect.ReflectPermission +java.lang.StringLatin1 +java.lang.invoke.MethodHandles$Lookup +jdk.internal.reflect.Reflection +java.lang.Math +java.util.AbstractSet +java.util.ImmutableCollections$MapN$1 +java.util.ImmutableCollections$MapN$MapNIterator +java.util.KeyValueHolder +java.util.LinkedHashMap$Entry +java.util.HashMap$TreeNode +java.lang.Runtime +java.util.concurrent.locks.Lock +java.util.concurrent.locks.ReentrantLock +java.util.concurrent.ConcurrentHashMap$Segment +java.util.concurrent.ConcurrentHashMap$CounterCell +java.util.concurrent.ConcurrentHashMap$Node +java.util.concurrent.locks.LockSupport +java.util.concurrent.ConcurrentHashMap$ReservationNode +java.security.PrivilegedAction +jdk.internal.reflect.ReflectionFactory$GetReflectionFactoryAction +jdk.internal.reflect.ReflectionFactory +java.lang.ref.Reference$ReferenceHandler +jdk.internal.ref.Cleaner +java.lang.ref.ReferenceQueue +java.lang.ref.ReferenceQueue$Null +java.lang.ref.ReferenceQueue$Lock +jdk.internal.access.JavaLangRefAccess +java.lang.ref.Reference$1 +java.lang.ref.Finalizer$FinalizerThread +jdk.internal.access.JavaLangAccess +java.lang.System$2 +jdk.internal.misc.VM +jdk.internal.util.SystemProps +jdk.internal.util.SystemProps$Raw +java.lang.StringConcatHelper +java.lang.VersionProps +java.util.Arrays +java.lang.CharacterData +java.lang.CharacterDataLatin1 +java.util.HashMap$EntrySet +java.util.HashMap$HashIterator +java.util.HashMap$EntryIterator +jdk.internal.util.StaticProperty +java.io.FileInputStream +java.io.FileDescriptor +jdk.internal.access.JavaIOFileDescriptorAccess +java.io.FileDescriptor$1 +java.io.Flushable +java.io.OutputStream +java.io.FileOutputStream +java.io.FilterInputStream +java.io.BufferedInputStream +java.io.FilterOutputStream +java.io.PrintStream +java.io.BufferedOutputStream +java.io.Writer +java.io.OutputStreamWriter +java.nio.charset.Charset +java.nio.charset.spi.CharsetProvider +sun.nio.cs.StandardCharsets +java.lang.ThreadLocal +java.util.concurrent.atomic.AtomicInteger +sun.security.action.GetPropertyAction +sun.nio.cs.HistoricallyNamedCharset +sun.nio.cs.Unicode +sun.nio.cs.UTF_8 +sun.nio.cs.StreamEncoder +java.nio.charset.CharsetEncoder +sun.nio.cs.UTF_8$Encoder +java.nio.charset.CodingErrorAction +java.nio.ByteBuffer +jdk.internal.misc.ScopedMemoryAccess +jdk.internal.access.JavaNioAccess +java.nio.Buffer$1 +java.nio.HeapByteBuffer +java.nio.ByteOrder +java.io.BufferedWriter +java.lang.Terminator +jdk.internal.misc.Signal$Handler +java.lang.Terminator$1 +jdk.internal.misc.Signal +java.util.Hashtable$Entry +jdk.internal.misc.Signal$NativeHandler +jdk.internal.misc.OSEnvironment +java.util.Collections +java.util.Collections$EmptySet +java.util.Collections$EmptyList +java.util.Collections$EmptyMap +java.lang.IllegalArgumentException +java.lang.invoke.MethodHandleStatics +jdk.internal.module.ModuleBootstrap +sun.invoke.util.VerifyAccess +java.lang.reflect.Modifier +jdk.internal.access.JavaLangModuleAccess +java.lang.module.ModuleDescriptor$1 +java.io.File +java.io.DefaultFileSystem +java.io.FileSystem +java.io.UnixFileSystem +jdk.internal.util.ArraysSupport +jdk.internal.module.ModulePatcher +jdk.internal.module.ModuleBootstrap$Counters +jdk.internal.module.ArchivedBootLayer +jdk.internal.access.JavaNetUriAccess +java.net.URI$1 +jdk.internal.loader.ArchivedClassLoaders +jdk.internal.loader.ClassLoaders$BootClassLoader +java.security.cert.Certificate +java.lang.ClassLoader$ParallelLoaders +java.util.WeakHashMap +java.util.WeakHashMap$Entry +java.util.Collections$SetFromMap +java.util.WeakHashMap$KeySet +jdk.internal.access.JavaSecurityAccess +java.security.ProtectionDomain$JavaSecurityAccessImpl +java.security.ProtectionDomain$Key +java.security.Principal +jdk.internal.loader.NativeLibraries +jdk.internal.loader.ClassLoaderHelper +java.util.HashSet +java.util.Queue +java.util.Deque +java.util.ArrayDeque +jdk.internal.loader.URLClassPath +java.net.URLStreamHandlerFactory +java.net.URL$DefaultFactory +jdk.internal.access.JavaNetURLAccess +java.net.URL$3 +java.io.File$PathStatus +sun.net.www.ParseUtil +java.util.HexFormat +java.net.URLStreamHandler +sun.net.www.protocol.file.Handler +sun.net.util.IPAddressUtil +jdk.internal.util.Preconditions +sun.net.www.protocol.jar.Handler +jdk.internal.module.ServicesCatalog +jdk.internal.loader.AbstractClassLoaderValue +jdk.internal.loader.ClassLoaderValue +java.util.Optional +jdk.internal.loader.BootLoader +jdk.internal.loader.BuiltinClassLoader$LoadedModule +java.util.ImmutableCollections$SetN$SetNIterator +java.util.ImmutableCollections$Set12$1 +java.util.ListIterator +java.util.ImmutableCollections$ListItr +jdk.internal.module.ModuleLoaderMap +jdk.internal.loader.AbstractClassLoaderValue$Memoizer +jdk.internal.module.ServicesCatalog$ServiceProvider +java.util.concurrent.CopyOnWriteArrayList +java.util.HashMap$KeySet +java.util.HashMap$KeyIterator +java.lang.ModuleLayer$Controller +java.lang.invoke.LambdaMetafactory +java.lang.invoke.MethodType$ConcurrentWeakInternSet +java.lang.Void +java.lang.invoke.MethodTypeForm +java.lang.invoke.MethodType$ConcurrentWeakInternSet$WeakEntry +sun.invoke.util.Wrapper +sun.invoke.util.Wrapper$Format +java.lang.invoke.LambdaForm$NamedFunction +java.lang.invoke.DirectMethodHandle$Holder +sun.invoke.util.ValueConversions +java.lang.invoke.MethodHandleImpl +java.lang.invoke.Invokers +java.lang.invoke.LambdaForm$Kind +java.lang.NoSuchMethodException +java.lang.invoke.LambdaForm$BasicType +java.lang.reflect.Array +java.lang.invoke.LambdaForm$Name +java.lang.invoke.LambdaForm$Holder +java.lang.invoke.InvokerBytecodeGenerator +java.lang.invoke.InvokerBytecodeGenerator$2 +java.lang.invoke.MethodHandleImpl$Intrinsic +java.lang.invoke.BootstrapMethodInvoker +java.lang.invoke.VarHandle$AccessMode +java.lang.invoke.VarHandle$AccessType +java.lang.invoke.Invokers$Holder +jdk.internal.access.JavaLangInvokeAccess +java.lang.invoke.MethodHandleImpl$1 +java.lang.invoke.AbstractValidatingLambdaMetafactory +java.lang.invoke.InnerClassLambdaMetafactory +jdk.internal.org.objectweb.asm.Type +sun.security.action.GetBooleanAction +jdk.internal.org.objectweb.asm.Handle +sun.invoke.util.BytecodeDescriptor +jdk.internal.org.objectweb.asm.ConstantDynamic +java.lang.invoke.MethodHandleInfo +java.lang.invoke.InfoFromMemberName +jdk.internal.org.objectweb.asm.ClassVisitor +jdk.internal.org.objectweb.asm.ClassWriter +jdk.internal.org.objectweb.asm.SymbolTable +jdk.internal.org.objectweb.asm.Symbol +jdk.internal.org.objectweb.asm.SymbolTable$Entry +jdk.internal.org.objectweb.asm.ByteVector +java.lang.invoke.LambdaProxyClassArchive +jdk.internal.org.objectweb.asm.MethodVisitor +jdk.internal.org.objectweb.asm.MethodWriter +jdk.internal.org.objectweb.asm.Label +java.lang.invoke.TypeConvertingMethodAdapter +java.lang.invoke.InnerClassLambdaMetafactory$ForwardingMethodGenerator +jdk.internal.org.objectweb.asm.Handler +jdk.internal.org.objectweb.asm.Attribute +jdk.internal.org.objectweb.asm.AnnotationVisitor +jdk.internal.org.objectweb.asm.AnnotationWriter +java.lang.invoke.MethodHandles$Lookup$ClassOption +java.lang.invoke.MethodHandles$Lookup$ClassFile +jdk.internal.org.objectweb.asm.ClassReader +java.lang.StringUTF16 +java.lang.invoke.MethodHandles$Lookup$ClassDefiner +jdk.internal.module.ModuleBootstrap$$Lambda$1/0x0000007001040850 +java.lang.invoke.InnerClassLambdaMetafactory$1 +java.lang.Class$ReflectionData +java.lang.Class$Atomic +jdk.internal.reflect.DelegatingConstructorAccessorImpl +java.lang.invoke.BoundMethodHandle +java.lang.invoke.ClassSpecializer +java.lang.invoke.BoundMethodHandle$Specializer +java.lang.invoke.ClassSpecializer$1 +java.lang.invoke.ClassSpecializer$SpeciesData +java.lang.invoke.BoundMethodHandle$SpeciesData +java.lang.invoke.ClassSpecializer$Factory +java.lang.invoke.BoundMethodHandle$Specializer$Factory +java.lang.invoke.SimpleMethodHandle +java.lang.NoSuchFieldException +java.lang.invoke.BoundMethodHandle$Species_L +sun.invoke.util.VerifyType +sun.invoke.empty.Empty +java.lang.invoke.DirectMethodHandle$2 +java.lang.invoke.DirectMethodHandle$Accessor +java.lang.invoke.DelegatingMethodHandle +java.lang.invoke.MethodHandleImpl$IntrinsicMethodHandle +java.lang.invoke.DelegatingMethodHandle$Holder +sun.invoke.util.Wrapper$1 +java.lang.invoke.LambdaFormEditor +java.lang.invoke.LambdaFormEditor$TransformKey +java.lang.invoke.LambdaFormBuffer +java.lang.invoke.LambdaFormEditor$Transform +jdk.internal.org.objectweb.asm.Frame +java.lang.invoke.InvokerBytecodeGenerator$ClassData +java.util.ArrayList$Itr +jdk.internal.org.objectweb.asm.FieldVisitor +jdk.internal.org.objectweb.asm.FieldWriter +java.lang.invoke.LambdaForm$MH/0x0000007001000400 +jdk.internal.ref.CleanerFactory +java.util.concurrent.ThreadFactory +jdk.internal.ref.CleanerFactory$1 +java.lang.ref.Cleaner +java.lang.ref.Cleaner$1 +jdk.internal.ref.CleanerImpl +java.lang.ref.Cleaner$Cleanable +jdk.internal.ref.PhantomCleanable +jdk.internal.ref.CleanerImpl$PhantomCleanableRef +jdk.internal.ref.CleanerImpl$CleanerCleanable +jdk.internal.misc.InnocuousThread +java.util.ArrayList$SubList +java.lang.Module$ReflectionData +java.lang.WeakPairMap +java.lang.WeakPairMap$Pair +java.lang.WeakPairMap$Pair$Lookup +java.util.function.BiFunction +java.lang.Module$$Lambda$2/0x0000007001040a90 +java.lang.WeakPairMap$WeakRefPeer +java.lang.WeakPairMap$Pair$Weak +java.lang.WeakPairMap$Pair$Weak$1 +java.lang.WeakPairMap$$Lambda$3/0x00000070010413e8 +java.lang.invoke.DirectMethodHandle$Constructor +java.lang.invoke.StringConcatFactory +java.lang.invoke.StringConcatFactory$1 +java.lang.invoke.StringConcatFactory$2 +java.lang.invoke.StringConcatFactory$3 +sun.launcher.LauncherHelper +java.lang.StringCoding +java.util.zip.ZipConstants +java.util.zip.ZipFile +java.util.jar.JarFile +jdk.internal.access.JavaUtilZipFileAccess +java.util.zip.ZipFile$1 +jdk.internal.access.JavaUtilJarAccess +java.util.jar.JavaUtilJarAccessImpl +java.lang.Runtime$Version +java.util.zip.ZipFile$CleanableResource +java.util.zip.ZipCoder +java.util.zip.ZipCoder$UTF8ZipCoder +java.util.zip.ZipFile$Source +sun.nio.fs.DefaultFileSystemProvider +java.nio.file.spi.FileSystemProvider +sun.nio.fs.AbstractFileSystemProvider +sun.nio.fs.UnixFileSystemProvider +sun.nio.fs.BsdFileSystemProvider +sun.nio.fs.MacOSXFileSystemProvider +java.nio.file.OpenOption +java.nio.file.StandardOpenOption +java.nio.file.FileSystem +sun.nio.fs.UnixFileSystem +sun.nio.fs.BsdFileSystem +sun.nio.fs.MacOSXFileSystem +java.nio.file.Watchable +java.nio.file.Path +sun.nio.fs.UnixPath +sun.nio.fs.Util +sun.nio.fs.UnixNativeDispatcher +jdk.internal.loader.NativeLibraries$LibraryPaths +jdk.internal.loader.NativeLibraries$1 +java.util.ArrayDeque$DeqIterator +jdk.internal.loader.NativeLibrary +jdk.internal.loader.NativeLibraries$NativeLibraryImpl +jdk.internal.loader.NativeLibraries$NativeLibraryImpl$1 +java.util.concurrent.ConcurrentHashMap$CollectionView +java.util.concurrent.ConcurrentHashMap$ValuesView +java.util.concurrent.ConcurrentHashMap$Traverser +java.util.concurrent.ConcurrentHashMap$BaseIterator +java.util.Enumeration +java.util.concurrent.ConcurrentHashMap$ValueIterator +java.nio.file.attribute.BasicFileAttributes +java.nio.file.attribute.PosixFileAttributes +sun.nio.fs.UnixFileAttributes +sun.nio.fs.UnixFileStoreAttributes +sun.nio.fs.UnixMountEntry +java.util.zip.ZipFile$Source$Key +java.nio.file.CopyOption +java.nio.file.LinkOption +java.nio.file.Files +java.nio.file.attribute.AttributeView +java.nio.file.attribute.FileAttributeView +java.nio.file.attribute.BasicFileAttributeView +java.nio.file.attribute.UserDefinedFileAttributeView +sun.nio.fs.UnixFileAttributeViews +sun.nio.fs.DynamicFileAttributeView +sun.nio.fs.AbstractBasicFileAttributeView +sun.nio.fs.UnixFileAttributeViews$Basic +sun.nio.fs.NativeBuffers +jdk.internal.misc.TerminatingThreadLocal +sun.nio.fs.NativeBuffers$1 +jdk.internal.misc.TerminatingThreadLocal$1 +java.lang.ThreadLocal$ThreadLocalMap +java.lang.ThreadLocal$ThreadLocalMap$Entry +java.util.IdentityHashMap +java.util.IdentityHashMap$KeySet +sun.nio.fs.NativeBuffer +sun.nio.fs.NativeBuffer$Deallocator +sun.nio.fs.UnixFileAttributes$UnixAsBasicFileAttributes +java.io.DataOutput +java.io.DataInput +java.io.RandomAccessFile +jdk.internal.access.JavaIORandomAccessFileAccess +java.io.RandomAccessFile$2 +java.io.FileCleanable +java.util.zip.ZipFile$Source$End +java.util.zip.ZipUtils +java.util.concurrent.TimeUnit +java.nio.file.attribute.FileTime +jdk.internal.perf.PerfCounter +jdk.internal.perf.Perf$GetPerfAction +jdk.internal.perf.Perf +jdk.internal.perf.PerfCounter$CoreCounters +sun.nio.ch.DirectBuffer +java.nio.MappedByteBuffer +java.nio.DirectByteBuffer +java.nio.Bits +java.util.concurrent.atomic.AtomicLong +jdk.internal.misc.VM$BufferPool +java.nio.Bits$1 +java.nio.LongBuffer +java.nio.DirectLongBufferU +java.util.zip.ZipEntry +java.util.jar.JarEntry +java.util.jar.JarFile$JarFileEntry +java.util.zip.ZipFile$ZipFileInputStream +java.util.zip.InflaterInputStream +java.util.zip.ZipFile$ZipFileInflaterInputStream +java.util.zip.Inflater +java.util.zip.Inflater$InflaterZStreamRef +java.util.zip.ZipFile$InflaterCleanupAction +sun.security.util.SignatureFileVerifier +sun.security.util.Debug +java.util.Locale +sun.util.locale.LocaleUtils +sun.security.action.GetIntegerAction +java.util.jar.JarVerifier +java.security.CodeSigner +java.io.ByteArrayOutputStream +java.util.jar.Attributes +java.util.LinkedHashMap +java.util.jar.Manifest$FastInputStream +java.io.RandomAccessFile$1 +sun.net.util.URLUtil +java.security.PrivilegedExceptionAction +jdk.internal.loader.URLClassPath$3 +jdk.internal.loader.URLClassPath$Loader +jdk.internal.loader.URLClassPath$JarLoader +jdk.internal.loader.URLClassPath$JarLoader$1 +jdk.internal.loader.FileURLMapper +jdk.internal.util.jar.JarIndex +java.util.StringTokenizer +jdk.internal.loader.Resource +jdk.internal.loader.URLClassPath$JarLoader$2 +java.lang.NamedPackage +java.lang.Package +java.lang.Package$VersionInfo +sun.nio.ByteBuffered +java.util.zip.Checksum +java.util.zip.CRC32 +java.util.zip.Checksum$1 +java.security.SecureClassLoader$CodeSourceKey +java.security.SecureClassLoader$1 +java.security.PermissionCollection +sun.security.util.LazyCodeSourcePermissionCollection +java.security.Permissions +java.lang.RuntimePermission +java.security.BasicPermissionCollection +java.security.AllPermission +java.security.UnresolvedPermission +java.security.SecureClassLoader$DebugHolder +org.apache.maven.surefire.booter.ForkedBooter +org.apache.maven.surefire.api.fork.ForkNodeArguments +org.apache.maven.plugin.surefire.log.api.ConsoleLogger +java.lang.SecurityException +java.security.AccessControlException +java.io.IOException +org.apache.maven.surefire.api.provider.CommandListener +java.lang.InterruptedException +java.util.concurrent.ConcurrentHashMap$ForwardingNode +java.util.concurrent.Executor +java.util.concurrent.ExecutorService +java.util.concurrent.ScheduledExecutorService +org.apache.maven.surefire.api.report.ReporterFactory +org.apache.maven.surefire.api.provider.CommandChainReader +java.lang.PublicMethods$MethodList +java.lang.PublicMethods$Key +java.util.concurrent.Semaphore +java.util.concurrent.locks.AbstractQueuedSynchronizer +java.util.concurrent.Semaphore$Sync +java.util.concurrent.Semaphore$NonfairSync +org.apache.maven.surefire.booter.BooterDeserializer +org.apache.maven.surefire.booter.SystemPropertyManager +java.util.Properties$LineReader +java.util.Properties$EntrySet +java.util.concurrent.ConcurrentHashMap$EntrySetView +java.util.Collections$SynchronizedCollection +java.util.Collections$SynchronizedSet +java.util.concurrent.ConcurrentHashMap$EntryIterator +java.util.concurrent.ConcurrentHashMap$MapEntry +java.util.Collections$UnmodifiableCollection +java.util.Collections$UnmodifiableSet +java.util.Collections$UnmodifiableCollection$1 +org.apache.maven.surefire.booter.KeyValueSource +org.apache.maven.surefire.booter.PropertiesWrapper +java.lang.IllegalStateException +java.io.FileInputStream$1 +org.apache.maven.surefire.booter.TypeEncodedValue +org.apache.maven.surefire.api.testset.DirectoryScannerParameters +org.apache.maven.surefire.api.util.RunOrder +org.apache.maven.surefire.api.testset.RunOrderParameters +org.apache.maven.surefire.api.testset.TestArtifactInfo +org.apache.maven.surefire.api.testset.TestRequest +org.apache.maven.surefire.api.testset.TestFilter +org.apache.maven.surefire.api.testset.GenericTestPattern +org.apache.maven.surefire.api.testset.TestListResolver +java.util.Collections$SingletonSet +org.apache.maven.surefire.api.testset.IncludedExcludedPatterns +java.util.LinkedHashSet +java.util.Collections$1 +org.apache.maven.surefire.shared.utils.StringUtils +java.lang.IndexOutOfBoundsException +java.lang.StringIndexOutOfBoundsException +org.apache.maven.surefire.api.testset.ResolvedTest +org.apache.maven.surefire.api.testset.ResolvedTest$Type +org.apache.maven.surefire.api.testset.ResolvedTest$ClassMatcher +org.apache.maven.surefire.api.testset.ResolvedTest$MethodMatcher +org.apache.maven.surefire.api.report.ReporterConfiguration +org.apache.maven.surefire.api.booter.Shutdown +java.lang.Class$3 +jdk.internal.reflect.NativeMethodAccessorImpl +jdk.internal.reflect.DelegatingMethodAccessorImpl +org.apache.maven.surefire.booter.ProviderConfiguration +org.apache.maven.surefire.api.cli.CommandLineOption +org.apache.maven.surefire.api.booter.DumpErrorSingleton +org.apache.maven.surefire.api.util.internal.DumpFileUtils +java.lang.management.ManagementFactory +java.lang.IncompatibleClassChangeError +java.lang.NoSuchMethodError +java.lang.invoke.LambdaForm$DMH/0x0000007001006000 +java.lang.management.ManagementFactory$$Lambda$4/0x00000070010443a0 +java.lang.management.PlatformManagedObject +java.lang.management.RuntimeMXBean +java.lang.management.ManagementFactory$PlatformMBeanFinder +java.lang.management.ManagementFactory$PlatformMBeanFinder$1 +java.io.FilePermission +jdk.internal.access.JavaIOFilePermissionAccess +java.io.FilePermission$1 +sun.security.util.FilePermCompat +sun.security.util.SecurityProperties +java.security.Security +java.security.Security$1 +jdk.internal.access.JavaSecurityPropertiesAccess +java.security.Security$2 +sun.management.spi.PlatformMBeanProvider +java.util.ServiceLoader +java.util.ServiceLoader$ModuleServicesLookupIterator +java.util.ServiceLoader$LazyClassPathLookupIterator +java.util.ServiceLoader$2 +java.util.ServiceLoader$3 +java.util.concurrent.CopyOnWriteArrayList$COWIterator +com.sun.management.internal.PlatformMBeanProviderImpl +java.util.ServiceLoader$1 +java.util.ServiceLoader$Provider +java.util.ServiceLoader$ProviderImpl +com.sun.management.internal.PlatformMBeanProviderImpl$$Lambda$5/0x0000007001045a48 +sun.management.spi.PlatformMBeanProvider$PlatformComponent +com.sun.management.internal.PlatformMBeanProviderImpl$1 +java.util.stream.BaseStream +java.util.stream.Stream +java.util.Spliterators +java.util.Spliterators$EmptySpliterator +java.util.Spliterator +java.util.Spliterators$EmptySpliterator$OfRef +java.util.Spliterator$OfPrimitive +java.util.Spliterator$OfInt +java.util.Spliterators$EmptySpliterator$OfInt +java.util.Spliterator$OfLong +java.util.Spliterators$EmptySpliterator$OfLong +java.util.Spliterator$OfDouble +java.util.Spliterators$EmptySpliterator$OfDouble +java.util.Spliterators$ArraySpliterator +java.util.stream.StreamSupport +java.util.stream.PipelineHelper +java.util.stream.AbstractPipeline +java.util.stream.ReferencePipeline +java.util.stream.ReferencePipeline$Head +java.util.stream.StreamOpFlag +java.util.stream.StreamOpFlag$Type +java.util.stream.StreamOpFlag$MaskBuilder +java.util.EnumMap +java.util.EnumMap$1 +sun.reflect.annotation.AnnotationParser +java.util.stream.Collectors +java.util.stream.Collector$Characteristics +java.util.EnumSet +java.util.RegularEnumSet +java.util.stream.Collector +java.util.stream.Collectors$CollectorImpl +java.util.stream.Collectors$$Lambda$31/0x800000046 +java.util.function.BiConsumer +java.lang.invoke.DirectMethodHandle$Interface +java.util.stream.Collectors$$Lambda$23/0x80000003a +java.util.function.BinaryOperator +java.util.stream.Collectors$$Lambda$26/0x800000041 +java.util.stream.Collectors$$Lambda$28/0x800000043 +java.util.stream.ReduceOps +java.util.stream.TerminalOp +java.util.stream.ReduceOps$ReduceOp +java.util.stream.ReduceOps$3 +java.util.stream.StreamShape +java.util.stream.ReduceOps$Box +java.util.function.Consumer +java.util.stream.Sink +java.util.stream.TerminalSink +java.util.stream.ReduceOps$AccumulatingSink +java.util.stream.ReduceOps$3ReducingSink +com.sun.management.internal.PlatformMBeanProviderImpl$2 +com.sun.management.internal.PlatformMBeanProviderImpl$3 +com.sun.management.internal.PlatformMBeanProviderImpl$4 +javax.management.DynamicMBean +com.sun.management.DiagnosticCommandMBean +javax.management.NotificationBroadcaster +javax.management.NotificationEmitter +sun.management.NotificationEmitterSupport +com.sun.management.internal.DiagnosticCommandImpl +sun.management.ManagementFactoryHelper +sun.management.VMManagement +sun.management.VMManagementImpl +com.sun.management.internal.PlatformMBeanProviderImpl$5 +java.util.Collections$UnmodifiableList +java.util.Collections$UnmodifiableRandomAccessList +jdk.management.jfr.internal.FlightRecorderMXBeanProvider +java.util.concurrent.Callable +java.util.Collections$EmptyEnumeration +java.lang.management.DefaultPlatformMBeanProvider +java.lang.management.DefaultPlatformMBeanProvider$1 +java.lang.management.DefaultPlatformMBeanProvider$2 +java.lang.management.DefaultPlatformMBeanProvider$3 +java.lang.management.DefaultPlatformMBeanProvider$4 +java.lang.management.DefaultPlatformMBeanProvider$5 +java.lang.management.DefaultPlatformMBeanProvider$6 +java.lang.management.DefaultPlatformMBeanProvider$7 +java.lang.management.DefaultPlatformMBeanProvider$8 +sun.management.ManagementFactoryHelper$LoggingMXBeanAccess +sun.management.ManagementFactoryHelper$LoggingMXBeanAccess$1 +java.util.logging.LogManager +java.lang.management.DefaultPlatformMBeanProvider$9 +java.lang.management.DefaultPlatformMBeanProvider$10 +java.lang.management.DefaultPlatformMBeanProvider$11 +jdk.management.jfr.FlightRecorderMXBean +jdk.management.jfr.internal.FlightRecorderMXBeanProvider$SingleMBeanComponent +java.util.Collections$SingletonList +java.util.HashMap$Values +java.util.HashMap$HashMapSpliterator +java.util.HashMap$ValueSpliterator +java.util.function.Predicate +java.lang.management.ManagementFactory$PlatformMBeanFinder$$Lambda$10/0x000000700104b430 +java.util.stream.ReferencePipeline$StatelessOp +java.util.stream.ReferencePipeline$2 +java.lang.management.ManagementFactory$PlatformMBeanFinder$$Lambda$11/0x000000700104b688 +java.util.stream.ReduceOps$2 +java.util.stream.ReduceOps$2ReducingSink +java.util.stream.Sink$ChainedReference +java.util.stream.ReferencePipeline$2$1 +sun.management.RuntimeImpl +java.util.Collections$SingletonMap +java.util.Collections$2 +java.lang.invoke.LambdaForm$DMH/0x0000007001006400 +sun.management.spi.PlatformMBeanProvider$PlatformComponent$$Lambda$12/0x000000700104c478 +sun.management.spi.PlatformMBeanProvider$PlatformComponent$$Lambda$13/0x000000700104c6d0 +java.util.stream.ReferencePipeline$3 +java.util.stream.Collectors$$Lambda$14/0x000000700104c918 +java.util.stream.Collectors$$Lambda$15/0x000000700104cb38 +java.util.stream.Collectors$$Lambda$16/0x000000700104cd68 +java.util.stream.ReferencePipeline$3$1 +sun.management.Util +java.lang.management.ManagementPermission +java.util.Arrays$ArrayList +java.util.Arrays$ArrayItr +org.apache.maven.surefire.booter.ClassLoaderConfiguration +org.apache.maven.surefire.booter.AbstractPathConfiguration +org.apache.maven.surefire.booter.ClasspathConfiguration +org.apache.maven.surefire.booter.Classpath +java.net.MalformedURLException +java.net.URLClassLoader +org.apache.maven.surefire.booter.IsolatedClassLoader +org.apache.maven.surefire.booter.SurefireExecutionException +org.apache.maven.surefire.booter.ProcessCheckerType +org.apache.maven.surefire.booter.StartupConfiguration +org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory +java.util.Spliterators$1Adapter +java.util.HashMap$ValueIterator +jdk.internal.module.Resources +jdk.internal.loader.BuiltinClassLoader$2 +jdk.internal.loader.BuiltinClassLoader$5 +java.lang.module.ModuleReader +jdk.internal.module.SystemModuleFinders$SystemModuleReader +jdk.internal.module.SystemModuleFinders$SystemImage +jdk.internal.jimage.ImageReaderFactory +java.nio.file.Paths +java.nio.file.FileSystems +java.nio.file.FileSystems$DefaultFileSystemHolder +java.nio.file.FileSystems$DefaultFileSystemHolder$1 +java.net.URI$Parser +jdk.internal.jimage.ImageReaderFactory$1 +jdk.internal.jimage.ImageReader +jdk.internal.jimage.BasicImageReader +jdk.internal.jimage.ImageReader$SharedImageReader +jdk.internal.jimage.BasicImageReader$1 +jdk.internal.jimage.NativeImageBuffer +jdk.internal.jimage.NativeImageBuffer$1 +jdk.internal.jimage.ImageHeader +java.nio.IntBuffer +java.nio.DirectIntBufferU +java.nio.DirectByteBufferR +java.nio.DirectIntBufferRU +jdk.internal.jimage.ImageStrings +jdk.internal.jimage.ImageStringsReader +jdk.internal.jimage.decompressor.Decompressor +jdk.internal.jimage.ImageLocation +java.util.Collections$EmptyIterator +jdk.internal.loader.BuiltinClassLoader$1 +java.lang.CompoundEnumeration +jdk.internal.loader.URLClassPath$1 +jdk.internal.loader.URLClassPath$FileLoader +java.util.SortedSet +java.util.NavigableSet +java.util.TreeSet +java.util.SortedMap +java.util.NavigableMap +java.util.TreeMap +java.util.TreeMap$Entry +java.util.TreeMap$KeySet +java.util.TreeMap$PrivateEntryIterator +java.util.TreeMap$KeyIterator +java.lang.Readable +java.io.Reader +java.io.BufferedReader +java.io.InputStreamReader +sun.nio.cs.StreamDecoder +java.nio.charset.CharsetDecoder +sun.nio.cs.UTF_8$Decoder +java.util.Vector +java.nio.CharBuffer +java.nio.HeapCharBuffer +java.nio.charset.CoderResult +java.util.AbstractSequentialList +java.util.LinkedList +java.util.LinkedList$Node +java.net.URLConnection +java.net.JarURLConnection +sun.net.www.protocol.jar.JarURLConnection +sun.net.www.protocol.jar.URLJarFile$URLJarFileCloseController +sun.net.www.protocol.jar.JarFileFactory +sun.net.www.URLConnection +sun.net.www.protocol.file.FileURLConnection +sun.net.www.MessageHeader +sun.net.www.protocol.jar.URLJarFile +sun.nio.fs.UnixFileKey +sun.net.www.protocol.jar.URLJarFile$URLJarFileEntry +sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream +java.util.LinkedHashMap$LinkedKeySet +java.util.LinkedHashMap$LinkedHashIterator +java.util.LinkedHashMap$LinkedKeyIterator +org.apache.maven.surefire.booter.spi.AbstractMasterProcessChannelProcessorFactory +org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory +org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder +org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder +org.apache.maven.surefire.api.util.internal.DaemonThreadFactory +java.util.concurrent.Executors +java.util.concurrent.Executors$DefaultThreadFactory +org.apache.maven.surefire.api.util.internal.DaemonThreadFactory$NamedThreadFactory +java.util.concurrent.AbstractExecutorService +java.util.concurrent.ThreadPoolExecutor +java.util.concurrent.ScheduledThreadPoolExecutor +java.util.concurrent.RejectedExecutionHandler +java.util.concurrent.ThreadPoolExecutor$AbortPolicy +java.util.concurrent.BlockingQueue +java.util.AbstractQueue +java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue +java.util.concurrent.Future +java.util.concurrent.RunnableFuture +java.util.concurrent.Delayed +java.util.concurrent.ScheduledFuture +java.util.concurrent.RunnableScheduledFuture +java.util.concurrent.locks.ReentrantLock$Sync +java.util.concurrent.locks.ReentrantLock$NonfairSync +java.util.concurrent.locks.Condition +java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject +org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory +java.net.URISyntaxException +java.util.concurrent.ExecutionException +java.net.SocketAddress +java.net.InetSocketAddress +java.nio.channels.Channel +java.nio.channels.AsynchronousChannel +java.nio.channels.AsynchronousByteChannel +org.apache.maven.surefire.booter.ForkedNodeArg +java.lang.UnsupportedOperationException +org.apache.maven.plugin.surefire.log.api.NullConsoleLogger +org.apache.maven.surefire.api.util.internal.Channels +org.apache.maven.surefire.api.util.internal.Channels$2 +org.apache.maven.surefire.api.util.internal.Channels$1 +java.nio.channels.ReadableByteChannel +java.nio.channels.WritableByteChannel +org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel +org.apache.maven.surefire.api.util.internal.AbstractNoninterruptibleWritableChannel +org.apache.maven.surefire.api.util.internal.Channels$4 +java.nio.channels.ClosedChannelException +java.nio.channels.NonWritableChannelException +org.apache.maven.surefire.booter.spi.AbstractMasterProcessChannelProcessorFactory$1 +java.util.concurrent.FutureTask +java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask +java.lang.invoke.VarHandles +java.lang.invoke.VarHandleInts$FieldInstanceReadOnly +java.lang.invoke.VarHandleInts$FieldInstanceReadWrite +java.lang.invoke.VarHandle$1 +jdk.internal.util.Preconditions$1 +java.lang.invoke.VarHandleGuards +java.lang.invoke.VarForm +java.lang.invoke.VarHandleReferences$FieldInstanceReadOnly +java.lang.invoke.VarHandleReferences$FieldInstanceReadWrite +java.util.concurrent.FutureTask$WaitNode +java.util.concurrent.Executors$RunnableAdapter +java.util.concurrent.ThreadPoolExecutor$Worker +java.lang.Thread$State +java.util.concurrent.TimeUnit$1 +java.time.temporal.TemporalUnit +java.time.temporal.ChronoUnit +java.time.temporal.TemporalAmount +java.time.Duration +java.math.BigInteger +java.lang.invoke.VarHandle$AccessDescriptor +org.apache.maven.surefire.api.stream.AbstractStreamEncoder +org.apache.maven.surefire.booter.stream.EventEncoder +org.apache.maven.surefire.booter.spi.EventChannelEncoder +java.lang.AssertionError +java.util.concurrent.ForkJoinPool$ManagedBlocker +java.util.concurrent.locks.AbstractQueuedSynchronizer$Node +java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode +org.apache.maven.surefire.api.booter.ForkedProcessEventType +org.apache.maven.surefire.api.report.ReportEntry +java.util.concurrent.atomic.AtomicBoolean +org.apache.maven.surefire.booter.spi.CommandChannelDecoder +org.apache.maven.surefire.api.stream.MalformedChannelException +org.apache.maven.surefire.api.stream.AbstractStreamDecoder +org.apache.maven.surefire.booter.stream.CommandDecoder +org.apache.maven.surefire.api.util.internal.AbstractNoninterruptibleReadableChannel +org.apache.maven.surefire.api.util.internal.Channels$3 +java.nio.channels.NonReadableChannelException +java.io.EOFException +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$MalformedFrameException +org.apache.maven.surefire.api.stream.SegmentType +java.io.FileNotFoundException +org.apache.maven.surefire.api.booter.Constants +java.nio.charset.StandardCharsets +sun.nio.cs.US_ASCII +sun.nio.cs.ISO_8859_1 +sun.nio.cs.UTF_16BE +sun.nio.cs.UTF_16LE +sun.nio.cs.UTF_16 +org.apache.maven.surefire.api.booter.MasterProcessCommand +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$Segment +org.apache.maven.surefire.booter.ForkedBooter$8 +org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils +java.lang.ApplicationShutdownHooks +java.lang.ApplicationShutdownHooks$1 +java.lang.Shutdown +java.lang.Shutdown$Lock +org.apache.maven.surefire.api.booter.ForkingReporterFactory +org.apache.maven.surefire.api.report.RunListener +org.apache.maven.surefire.api.report.TestOutputReceiver +org.apache.maven.surefire.api.report.TestReportListener +org.apache.maven.surefire.api.booter.ForkingRunListener +org.apache.maven.surefire.booter.CommandReader +org.apache.maven.surefire.api.testset.TestSetFailedException +java.util.concurrent.ConcurrentLinkedQueue +java.util.concurrent.ConcurrentLinkedQueue$Node +org.apache.maven.surefire.booter.CommandReader$CommandRunnable +java.util.concurrent.atomic.AtomicReference +java.util.concurrent.CountDownLatch +java.util.concurrent.CountDownLatch$Sync +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$Memento +org.apache.maven.surefire.booter.PpidChecker +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$BufferedStream +org.apache.maven.surefire.booter.PpidChecker$ProcessInfoConsumer +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$StreamReadStatus +org.apache.maven.surefire.booter.PpidChecker$1 +org.apache.maven.surefire.booter.stream.CommandDecoder$1 +org.apache.maven.surefire.booter.PpidChecker$2 +java.lang.NoSuchFieldError +org.apache.maven.surefire.api.booter.Command +org.apache.maven.surefire.booter.CommandReader$1 +java.util.concurrent.ConcurrentLinkedQueue$Itr +org.apache.maven.surefire.shared.lang3.SystemUtils +org.apache.maven.surefire.shared.lang3.JavaVersion +org.apache.maven.surefire.shared.lang3.math.NumberUtils +java.lang.NumberFormatException +java.math.BigDecimal +jdk.internal.math.FloatingDecimal +jdk.internal.math.FloatingDecimal$BinaryToASCIIConverter +jdk.internal.math.FloatingDecimal$ExceptionalBinaryToASCIIBuffer +jdk.internal.math.FloatingDecimal$BinaryToASCIIBuffer +jdk.internal.math.FloatingDecimal$1 +jdk.internal.math.FloatingDecimal$ASCIIToBinaryConverter +jdk.internal.math.FloatingDecimal$PreparedASCIIToBinaryBuffer +jdk.internal.math.FloatingDecimal$ASCIIToBinaryBuffer +org.apache.maven.surefire.shared.lang3.StringUtils +java.util.regex.Pattern +java.util.regex.Pattern$Node +java.util.regex.Pattern$LastNode +java.util.regex.Pattern$GroupHead +java.util.regex.CharPredicates +java.lang.Character$Subset +java.lang.Character$UnicodeBlock +java.util.regex.Pattern$CharPredicate +java.util.regex.CharPredicates$$Lambda$17/0x000000700105cff8 +java.util.regex.Pattern$BmpCharPredicate +java.util.regex.Pattern$CharProperty +java.util.regex.Pattern$Qtype +java.util.regex.Pattern$BmpCharProperty +java.util.regex.Pattern$CharPropertyGreedy +java.util.regex.Pattern$SliceNode +java.util.regex.Pattern$Slice +java.util.regex.Pattern$Begin +java.util.regex.Pattern$First +java.util.regex.Pattern$Start +java.util.regex.Pattern$StartS +java.util.regex.Pattern$TreeInfo +java.util.regex.Pattern$GroupTail +java.util.regex.CharPredicates$$Lambda$17/0x800000025 +java.util.regex.Pattern$BmpCharPropertyGreedy +java.util.regex.Pattern$$Lambda$19/0x800000029 +java.util.regex.Pattern$Ques +java.util.regex.Pattern$BranchConn +java.util.regex.Pattern$Branch +java.util.regex.ASCII +java.util.regex.Pattern$Curly +java.util.regex.CharPredicates$$Lambda$18/0x800000026 +java.util.regex.Pattern$Dollar +java.util.regex.Pattern$BitClass +org.apache.maven.surefire.booter.ForkedBooter$4 +org.apache.maven.surefire.api.booter.BiProperty +org.apache.maven.surefire.booter.ForkedBooter$3 +org.apache.maven.surefire.booter.ForkedBooter$PingScheduler +org.apache.maven.surefire.api.provider.ProviderParameters +org.apache.maven.surefire.api.booter.BaseProviderFactory +org.apache.maven.surefire.api.util.DirectoryScanner +org.apache.maven.surefire.api.util.ScanResult +org.apache.maven.surefire.api.util.RunOrderCalculator +org.apache.maven.surefire.api.util.ReflectionUtils +org.apache.maven.surefire.api.util.SurefireReflectionException +java.lang.reflect.InvocationTargetException +java.lang.IllegalAccessException +org.apache.maven.surefire.api.provider.SurefireProvider +org.apache.maven.surefire.api.provider.AbstractProvider +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +org.junit.platform.launcher.Launcher +org.apache.maven.surefire.api.util.ScannerFilter +java.io.StringReader +java.io.UncheckedIOException +org.apache.maven.surefire.junitplatform.LazyLauncher +org.junit.platform.launcher.TagFilter +org.junit.platform.commons.JUnitException +org.junit.platform.commons.util.PreconditionViolationException +org.junit.platform.commons.PreconditionViolationException +org.junit.platform.engine.Filter +org.junit.platform.launcher.PostDiscoveryFilter +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$21/0x00000070010136c8 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$22/0x0000007001013908 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$23/0x0000007001013b40 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$24/0x0000007001013d80 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$25/0x0000007001013fb8 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$26/0x0000007001014208 +org.apache.maven.surefire.junitplatform.TestMethodFilter +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$27/0x00000070010146b8 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$28/0x00000070010148f8 +org.junit.platform.launcher.EngineFilter +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$29/0x0000007001014d88 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$30/0x0000007001014fc8 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$31/0x0000007001015200 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$32/0x0000007001015440 +org.junit.platform.launcher.TestExecutionListener +org.apache.maven.surefire.report.RunModeSetter +org.apache.maven.surefire.junitplatform.RunListenerAdapter +org.apache.maven.surefire.api.report.OutputReportEntry +org.apache.maven.surefire.api.report.TestSetReportEntry +org.apache.maven.surefire.api.report.StackTraceWriter +org.apache.maven.surefire.report.ClassMethodIndexer +org.apache.maven.surefire.api.report.RunMode +org.apache.maven.surefire.api.report.ConsoleOutputCapture +org.apache.maven.surefire.api.report.ConsoleOutputCapture$ForwardingPrintStream +org.apache.maven.surefire.api.report.ConsoleOutputCapture$NullOutputStream +java.util.logging.Logger +java.util.logging.Handler +java.util.logging.Level +java.util.logging.Level$KnownLevel +java.util.logging.Level$KnownLevel$$Lambda$14/0x800000022 +java.util.logging.Level$KnownLevel$$Lambda$15/0x800000023 +java.util.logging.Logger$LoggerBundle +java.util.logging.Logger$ConfigurationData +java.util.logging.LogManager$1 +java.util.logging.LogManager$LoggerContext +java.util.logging.LogManager$SystemLoggerContext +java.util.logging.LogManager$LogNode +java.util.Collections$SynchronizedMap +java.util.logging.LogManager$Cleaner +java.util.logging.LoggingPermission +sun.util.logging.internal.LoggingProviderImpl$LogManagerAccess +java.util.logging.LogManager$LoggingProviderAccess +java.lang.System$LoggerFinder +jdk.internal.logger.DefaultLoggerFinder +sun.util.logging.internal.LoggingProviderImpl +java.util.logging.LogManager$2 +java.util.logging.LogManager$RootLogger +java.util.logging.LogManager$LoggerWeakRef +java.lang.invoke.MethodHandleImpl$AsVarargsCollector +java.lang.invoke.BoundMethodHandle$Species_LL +java.lang.invoke.LambdaForm$MH/0x0000007001018000 +java.util.logging.LogManager$VisitedLoggers +java.util.logging.LogManager$LoggerContext$1 +java.util.concurrent.ConcurrentHashMap$KeySetView +java.util.Collections$3 +java.util.concurrent.ConcurrentHashMap$KeyIterator +java.util.Hashtable$Enumerator +java.util.logging.Level$$Lambda$13/0x800000011 +java.util.ArrayList$ArrayListSpliterator +java.util.logging.Level$KnownLevel$$Lambda$16/0x800000024 +java.util.stream.ReferencePipeline$7 +java.util.stream.FindOps +java.util.stream.FindOps$FindSink +java.util.stream.FindOps$FindSink$OfRef +java.util.stream.FindOps$FindOp +java.util.stream.FindOps$FindSink$OfRef$$Lambda$41/0x800000050 +java.util.stream.FindOps$FindSink$OfRef$$Lambda$39/0x80000004e +java.util.stream.FindOps$FindSink$OfRef$$Lambda$40/0x80000004f +java.util.stream.FindOps$FindSink$OfRef$$Lambda$38/0x80000004d +java.util.stream.ReferencePipeline$7$1 +java.util.stream.Streams$AbstractStreamBuilderImpl +java.util.stream.Stream$Builder +java.util.stream.Streams$StreamBuilderImpl +java.util.stream.Streams +java.util.IdentityHashMap$Values +java.lang.System$Logger +sun.util.logging.PlatformLogger$Bridge +sun.util.logging.PlatformLogger$ConfigurableBridge +jdk.internal.logger.BootstrapLogger +jdk.internal.logger.BootstrapLogger$DetectBackend +jdk.internal.logger.BootstrapLogger$DetectBackend$1 +jdk.internal.logger.BootstrapLogger$LoggingBackend +jdk.internal.logger.BootstrapLogger$RedirectedLoggers +jdk.internal.logger.BootstrapLogger$BootstrapExecutors +java.util.logging.LogManager$4 +java.util.logging.Logger$SystemLoggerHelper +java.util.logging.Logger$SystemLoggerHelper$1 +jdk.internal.logger.DefaultLoggerFinder$1 +org.apache.maven.surefire.junitplatform.TestPlanScannerFilter +org.apache.maven.surefire.api.util.DefaultScanResult +jdk.internal.loader.URLClassPath$FileLoader$1 +software.amazon.lambda.powertools.metrics.MetricsBuilderTest +org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder +org.junit.platform.engine.ConfigurationParameters +org.junit.platform.engine.EngineDiscoveryRequest +org.junit.platform.launcher.LauncherDiscoveryRequest +org.junit.platform.engine.DiscoverySelector +org.junit.platform.engine.discovery.DiscoverySelectors +org.junit.platform.commons.util.Preconditions +org.junit.platform.commons.util.StringUtils +java.util.regex.CharPredicates$$Lambda$41/0x000000700105e690 +org.junit.platform.engine.discovery.ClassSelector +java.lang.invoke.LambdaForm$DMH/0x0000007001018400 +org.junit.platform.commons.util.Preconditions$$Lambda$42/0x000000700101d038 +org.junit.platform.commons.util.Preconditions$$Lambda$43/0x000000700101d270 +java.lang.invoke.LambdaForm$DMH/0x0000007001018800 +java.lang.invoke.DirectMethodHandle$Special +org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder$$Lambda$44/0x000000700101d4a8 +org.junit.platform.launcher.core.LauncherConfigurationParameters +org.junit.platform.commons.logging.LoggerFactory +org.junit.platform.commons.logging.Logger +org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger +org.junit.platform.launcher.core.LauncherConfigurationParameters$Builder +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$2 +org.junit.platform.commons.util.ClassLoaderUtils +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$3 +org.junit.platform.launcher.core.LauncherConfigurationParameters$$Lambda$45/0x000000700101ea88 +org.junit.platform.launcher.core.LauncherConfigurationParameters$$Lambda$46/0x000000700101ecd0 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners +org.junit.platform.engine.EngineDiscoveryListener +org.junit.platform.launcher.LauncherDiscoveryListener +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$LauncherDiscoveryListenerType +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$LauncherDiscoveryListenerType$$Lambda$47/0x000000700101f770 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$LauncherDiscoveryListenerType$$Lambda$48/0x000000700101f990 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$$Lambda$49/0x000000700101fda8 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$$Lambda$50/0x000000700101a000 +org.junit.platform.launcher.listeners.discovery.AbortOnFailureLauncherDiscoveryListener +org.junit.platform.engine.EngineDiscoveryListener$1 +org.junit.platform.launcher.LauncherDiscoveryListener$1 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$$Lambda$51/0x000000700101a958 +org.junit.platform.launcher.core.DefaultDiscoveryRequest +org.junit.platform.launcher.LauncherSession +org.junit.platform.launcher.core.LauncherFactory +org.junit.platform.launcher.core.LauncherConfig +org.junit.platform.launcher.core.LauncherConfig$Builder +org.junit.platform.launcher.core.DefaultLauncherConfig +org.junit.platform.launcher.core.DefaultLauncherSession +org.junit.platform.launcher.LauncherInterceptor +org.junit.platform.launcher.core.DefaultLauncherSession$1 +org.junit.platform.launcher.core.LauncherConfigurationParameters$$Lambda$52/0x0000007001019000 +org.junit.platform.launcher.LauncherSessionListener +org.junit.platform.launcher.core.LauncherFactory$$Lambda$53/0x0000007001019440 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$54/0x0000007001019668 +org.junit.platform.launcher.core.ListenerRegistry +org.junit.platform.launcher.listeners.session.LauncherSessionListeners +org.junit.platform.launcher.core.ListenerRegistry$$Lambda$55/0x0000007001019cc8 +org.junit.platform.launcher.core.ServiceLoaderRegistry +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$56/0x0000007001020000 +java.lang.invoke.LambdaForm$DMH/0x0000007001024000 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$57/0x0000007001020228 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$58/0x0000007001020460 +org.junit.platform.launcher.LauncherSessionListener$1 +org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry +org.junit.platform.engine.TestEngine +org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry$$Lambda$59/0x0000007001020cd8 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$60/0x0000007001020f00 +org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine +org.junit.jupiter.engine.JupiterTestEngine +org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService +org.junit.jupiter.engine.config.JupiterConfiguration +org.junit.platform.engine.TestDescriptor +org.junit.platform.engine.support.hierarchical.EngineExecutionContext +org.junit.platform.launcher.core.LauncherFactory$$Lambda$61/0x0000007001021e48 +org.junit.platform.launcher.core.DefaultLauncher +org.junit.platform.launcher.TestPlan +org.junit.platform.launcher.core.InternalTestPlan +org.junit.platform.launcher.core.LauncherListenerRegistry +org.junit.platform.launcher.core.ListenerRegistry$$Lambda$62/0x0000007001022a18 +org.junit.platform.launcher.core.CompositeTestExecutionListener +org.junit.platform.launcher.core.ListenerRegistry$$Lambda$63/0x0000007001022ee8 +org.junit.platform.launcher.core.EngineExecutionOrchestrator +org.junit.platform.engine.EngineExecutionListener +org.junit.platform.launcher.TestPlan$Visitor +org.junit.platform.launcher.core.DefaultLauncher$$Lambda$64/0x0000007001023750 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator +org.junit.platform.launcher.core.EngineDiscoveryResultValidator +org.junit.platform.launcher.core.EngineIdValidator +org.junit.platform.launcher.core.LauncherFactory$$Lambda$65/0x0000007001026000 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$66/0x0000007001026238 +java.util.Spliterators$IteratorSpliterator +org.junit.platform.commons.util.ClassNamePatternFilterUtils +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$67/0x0000007001026678 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$68/0x00000070010268b8 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$69/0x0000007001026b08 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$70/0x0000007001026d48 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$71/0x0000007001026f90 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$72/0x00000070010271e0 +java.util.stream.ForEachOps +java.util.stream.ForEachOps$ForEachOp +java.util.stream.ForEachOps$ForEachOp$OfRef +org.junitpioneer.jupiter.issue.IssueExtensionExecutionListener +org.junitpioneer.jupiter.IssueProcessor +org.junit.platform.launcher.listeners.UniqueIdTrackingListener +org.junit.platform.launcher.core.LauncherFactory$$Lambda$73/0x0000007001027b40 +org.junit.platform.launcher.core.DelegatingLauncher +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$Phase +org.junit.platform.engine.UniqueId +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$74/0x0000007001025670 +org.junit.platform.launcher.core.EngineFilterer +org.junit.platform.engine.FilterResult +org.junit.platform.launcher.core.EngineFilterer$$Lambda$75/0x0000007001025cf8 +java.lang.invoke.LambdaForm$DMH/0x0000007001024400 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$76/0x0000007001024800 +java.util.stream.MatchOps$MatchKind +java.util.stream.MatchOps +java.util.stream.MatchOps$MatchOp +java.util.stream.MatchOps$BooleanTerminalSink +java.util.stream.MatchOps$$Lambda$77/0x000000700105f7f0 +java.util.stream.MatchOps$1MatchSink +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$78/0x0000007001024a50 +org.junit.platform.engine.UniqueIdFormat +java.io.UnsupportedEncodingException +java.util.Formatter +java.util.regex.Pattern$$Lambda$20/0x80000002a +java.util.regex.Pattern$$Lambda$22/0x800000032 +java.util.Locale$Category +java.util.Formatter$Conversion +java.util.Formatter$FormatString +java.util.Formatter$FormatSpecifier +java.util.Formatter$Flags +java.util.Formatter$FixedString +java.util.Formattable +java.util.regex.Pattern$$Lambda$81/0x0000007001060378 +java.lang.invoke.LambdaForm$DMH/0x0000007001028000 +org.junit.platform.engine.UniqueIdFormat$$Lambda$82/0x000000700102c000 +java.net.URLEncoder +java.util.BitSet +java.io.CharArrayWriter +org.junit.platform.engine.UniqueIdFormat$$Lambda$83/0x000000700102c240 +org.junit.platform.engine.UniqueIdFormat$$Lambda$84/0x000000700102c480 +org.junit.platform.engine.UniqueIdFormat$$Lambda$85/0x000000700102c6c0 +org.junit.platform.engine.UniqueIdFormat$$Lambda$86/0x000000700102c900 +org.junit.platform.engine.UniqueIdFormat$$Lambda$87/0x000000700102cb40 +org.junit.platform.engine.UniqueId$Segment +org.junit.jupiter.engine.config.CachingJupiterConfiguration +org.junit.jupiter.engine.config.DefaultJupiterConfiguration +org.junit.jupiter.api.parallel.ExecutionMode +org.junit.jupiter.api.TestInstance$Lifecycle +org.junit.jupiter.api.io.CleanupMode +org.junit.jupiter.engine.config.EnumConfigurationParameterConverter +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter +org.junit.jupiter.api.DisplayNameGenerator +org.junit.jupiter.api.MethodOrderer +org.junit.jupiter.api.ClassOrderer +org.junit.jupiter.api.io.TempDirFactory +org.junit.platform.engine.support.hierarchical.Node +org.junit.platform.engine.support.descriptor.AbstractTestDescriptor +org.junit.platform.engine.support.descriptor.EngineDescriptor +org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor +org.junit.jupiter.api.extension.ExecutableInvoker +org.junit.jupiter.api.extension.ExtensionContext +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver +org.junit.platform.engine.support.discovery.SelectorResolver +org.junit.platform.engine.TestDescriptor$Visitor +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$InitializationContext +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$Builder +org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod +org.junit.jupiter.engine.discovery.predicates.IsTestMethod +org.junit.jupiter.api.Test +org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod +org.junit.jupiter.api.TestFactory +org.junit.jupiter.engine.discovery.predicates.IsTestTemplateMethod +org.junit.jupiter.api.TestTemplate +java.lang.invoke.LambdaForm$DMH/0x0000007001028400 +java.util.function.Predicate$$Lambda$88/0x0000007001060df0 +org.junit.jupiter.engine.discovery.predicates.IsPotentialTestContainer +org.junit.jupiter.engine.discovery.predicates.IsNestedTestClass +org.junit.jupiter.engine.discovery.predicates.IsInnerClass +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$Builder$$Lambda$89/0x0000007001029000 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$90/0x0000007001029248 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$91/0x0000007001029488 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$92/0x00000070010296c8 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$93/0x0000007001029908 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$94/0x0000007001029b48 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$DefaultInitializationContext +org.junit.platform.engine.DiscoveryFilter +org.junit.platform.engine.discovery.ClassNameFilter +org.junit.platform.launcher.core.DefaultDiscoveryRequest$$Lambda$95/0x0000007001028c00 +org.junit.platform.launcher.core.DefaultDiscoveryRequest$$Lambda$96/0x0000007001030000 +org.junit.platform.engine.discovery.PackageNameFilter +org.junit.platform.engine.CompositeFilter +org.junit.platform.engine.CompositeFilter$1 +org.junit.platform.engine.CompositeFilter$1$$Lambda$97/0x00000070010308c0 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$$Lambda$98/0x0000007001030b10 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$$Lambda$99/0x0000007001030d58 +java.util.stream.Collectors$$Lambda$100/0x0000007001061048 +java.util.stream.Collectors$$Lambda$101/0x0000007001061278 +org.junit.platform.engine.support.discovery.ClassContainerSelectorResolver +org.junit.jupiter.engine.discovery.ClassSelectorResolver +org.junit.jupiter.engine.discovery.MethodSelectorResolver +org.junit.jupiter.engine.discovery.MethodFinder +java.util.regex.Pattern$$Lambda$102/0x00000070010614c0 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor +org.junit.jupiter.engine.discovery.ClassOrderingVisitor +org.junit.jupiter.api.ClassOrdererContext +org.junit.jupiter.engine.discovery.MethodOrderingVisitor +org.junit.jupiter.api.MethodOrdererContext +java.lang.invoke.LambdaForm$DMH/0x0000007001034000 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$103/0x0000007001032558 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution +org.junit.platform.engine.support.discovery.SelectorResolver$Context +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$DefaultContext +org.junit.platform.engine.support.discovery.SelectorResolver$Match +org.junit.platform.engine.support.discovery.SelectorResolver$Match$$Lambda$104/0x0000007001033008 +org.junit.platform.engine.support.discovery.SelectorResolver$Match$Type +org.junit.platform.launcher.core.DefaultDiscoveryRequest$$Lambda$105/0x0000007001033668 +org.junit.platform.launcher.core.DefaultDiscoveryRequest$$Lambda$106/0x00000070010338c0 +java.util.ArrayDeque$$Lambda$107/0x0000007001061720 +org.junit.platform.engine.discovery.UniqueIdSelector +org.junit.platform.engine.support.discovery.SelectorResolver$Resolution +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$108/0x0000007001036000 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$109/0x0000007001036248 +org.junit.platform.engine.discovery.ClasspathResourceSelector +org.junit.platform.engine.discovery.ClasspathRootSelector +org.junit.platform.commons.util.ReflectionUtils +java.util.regex.Pattern$$Lambda$21/0x800000031 +org.junit.platform.commons.util.ClasspathScanner +java.nio.file.FileVisitor +org.junit.platform.commons.util.ReflectionUtils$$Lambda$111/0x0000007001036cf8 +org.junit.platform.commons.function.Try +java.lang.invoke.LambdaForm$DMH/0x0000007001034400 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$112/0x0000007001037168 +java.lang.invoke.LambdaForm$DMH/0x0000007001034800 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$113/0x0000007001037398 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$114/0x00000070010375d0 +org.junit.platform.commons.function.Try$Failure +org.junit.platform.commons.function.Try$Success +org.junit.platform.commons.function.Try$$Lambda$115/0x0000007001037ca8 +java.util.regex.MatchResult +java.util.regex.Matcher +java.util.regex.IntHashSet +java.util.regex.Pattern$1 +org.junit.platform.engine.discovery.ClassSelector$$Lambda$116/0x0000007001035000 +org.junit.platform.commons.util.ReflectionUtils$HierarchyTraversalMode +java.lang.PublicMethods +software.amazon.lambda.powertools.metrics.provider.MetricsProvider +java.util.LinkedHashMap$LinkedValues +java.util.LinkedHashMap$LinkedValueIterator +org.junit.platform.commons.util.ReflectionUtils$$Lambda$117/0x0000007001035888 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$118/0x0000007001035ad8 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$119/0x0000007001035cf8 +java.util.stream.SortedOps +java.util.stream.ReferencePipeline$StatefulOp +java.util.stream.SortedOps$OfRef +org.junit.platform.commons.util.ReflectionUtils$$Lambda$120/0x0000007001034c00 +java.util.stream.SortedOps$AbstractRefSortingSink +java.util.stream.SortedOps$SizedRefSortingSink +java.util.Arrays$LegacyMergeSort +java.util.TimSort +org.junit.platform.commons.util.AnnotationUtils +java.lang.annotation.Inherited +sun.reflect.generics.parser.SignatureParser +sun.reflect.generics.tree.Tree +sun.reflect.generics.tree.TypeTree +sun.reflect.generics.tree.TypeArgument +sun.reflect.generics.tree.ReturnType +sun.reflect.generics.tree.TypeSignature +sun.reflect.generics.tree.BaseType +sun.reflect.generics.tree.FieldTypeSignature +sun.reflect.generics.tree.SimpleClassTypeSignature +sun.reflect.generics.tree.ClassTypeSignature +sun.reflect.generics.scope.Scope +sun.reflect.generics.scope.AbstractScope +sun.reflect.generics.scope.ClassScope +sun.reflect.generics.factory.GenericsFactory +sun.reflect.generics.factory.CoreReflectionFactory +sun.reflect.generics.visitor.TypeTreeVisitor +sun.reflect.generics.visitor.Reifier +java.lang.annotation.Target +java.lang.reflect.GenericArrayType +sun.reflect.annotation.AnnotationType +sun.reflect.annotation.AnnotationType$1 +java.lang.annotation.ElementType +java.lang.annotation.Retention +java.lang.annotation.Documented +java.lang.annotation.RetentionPolicy +sun.reflect.annotation.ExceptionProxy +sun.reflect.annotation.AnnotationTypeMismatchExceptionProxy +sun.reflect.annotation.AnnotationParser$1 +java.lang.reflect.InvocationHandler +sun.reflect.annotation.AnnotationInvocationHandler +java.lang.reflect.Proxy +java.lang.ClassValue +java.lang.reflect.Proxy$1 +java.lang.ClassValue$Entry +java.lang.ClassValue$Identity +java.lang.ClassValue$Version +jdk.internal.loader.AbstractClassLoaderValue$Sub +java.lang.reflect.Proxy$$Lambda$121/0x000000700106b350 +java.lang.reflect.Proxy$ProxyBuilder +java.lang.reflect.Proxy$ProxyBuilder$$Lambda$122/0x000000700106b790 +java.lang.module.ModuleDescriptor$Modifier +java.lang.module.ModuleDescriptor$Builder +jdk.internal.module.Checks +java.lang.module.ModuleDescriptor$Builder$$Lambda$1/0x800000002 +java.lang.reflect.Proxy$$Lambda$124/0x000000700106b9c0 +java.lang.reflect.ProxyGenerator +java.lang.reflect.ProxyGenerator$ProxyMethod +java.util.StringJoiner +java.lang.reflect.ProxyGenerator$$Lambda$125/0x000000700106c110 +java.lang.reflect.ProxyGenerator$$Lambda$126/0x000000700106c350 +java.lang.reflect.ProxyGenerator$PrimitiveTypeInfo +jdk.internal.org.objectweb.asm.Edge +jdk.proxy1.$Proxy0 +java.lang.reflect.Proxy$ProxyBuilder$1 +sun.reflect.annotation.AnnotationParser$$Lambda$127/0x000000700106ce40 +java.lang.invoke.LambdaForm$DMH/0x000000700103c000 +jdk.proxy1.$Proxy1 +jdk.proxy1.$Proxy2 +org.apiguardian.api.API +org.apiguardian.api.API$Status +jdk.proxy2.$Proxy3 +java.lang.reflect.UndeclaredThrowableException +org.junit.platform.commons.annotation.Testable +jdk.proxy1.$Proxy4 +jdk.proxy2.$Proxy5 +java.lang.Class$AnnotationData +org.junit.jupiter.api.AfterEach +jdk.proxy2.$Proxy6 +java.util.LinkedHashMap$LinkedEntrySet +java.util.LinkedHashMap$LinkedEntryIterator +jdk.internal.misc.ScopedMemoryAccess$Scope +jdk.proxy2.$Proxy7 +java.lang.invoke.MethodHandle$1 +java.lang.invoke.LambdaForm$DMH/0x000000700103c400 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$128/0x00000070010395a0 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor +org.junit.jupiter.engine.descriptor.ClassTestDescriptor +org.junit.jupiter.engine.extension.ExtensionRegistry +org.junit.platform.engine.TestSource +org.junit.jupiter.engine.extension.ExtensionRegistrar +org.junit.jupiter.api.extension.TestInstanceFactoryContext +org.junit.jupiter.api.extension.ExtensionConfigurationException +org.junit.jupiter.api.extension.TestInstantiationException +org.junit.jupiter.engine.execution.ConditionEvaluator +org.junit.jupiter.engine.execution.ConditionEvaluationException +org.junit.jupiter.api.extension.ConditionEvaluationResult +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker +org.junit.jupiter.api.extension.ReflectiveInvocationContext +org.junit.jupiter.api.extension.InvocationInterceptor$Invocation +org.junit.jupiter.engine.execution.InvocationInterceptorChain +org.junit.jupiter.engine.descriptor.DisplayNameUtils +org.junit.jupiter.api.DisplayNameGenerator$Standard +org.junit.jupiter.api.DisplayNameGenerator$Simple +org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores +org.junit.jupiter.api.DisplayNameGenerator$IndicativeSentences +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$129/0x000000700103ed38 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$130/0x000000700103ef80 +org.junit.platform.engine.support.descriptor.ClassSource +org.junit.jupiter.api.DisplayName +org.junit.jupiter.api.DisplayNameGeneration +java.lang.invoke.LambdaForm$DMH/0x000000700103c800 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$131/0x000000700103f7c8 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$132/0x000000700103fa08 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$133/0x000000700103fc48 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$134/0x000000700103d000 +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter$$Lambda$135/0x000000700103d248 +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter$$Lambda$136/0x000000700103d488 +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter$$Lambda$137/0x000000700103d6d8 +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter$$Lambda$138/0x000000700103d920 +org.junit.jupiter.engine.config.DefaultJupiterConfiguration$$Lambda$139/0x000000700103db40 +org.junit.jupiter.api.Tag +java.lang.annotation.Repeatable +org.junit.jupiter.api.Tags +jdk.proxy1.$Proxy8 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$140/0x0000007001080000 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$141/0x0000007001080228 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$142/0x0000007001080468 +org.junit.platform.engine.TestTag +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$143/0x00000070010808d0 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$144/0x0000007001080b10 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$145/0x0000007001080d30 +java.lang.invoke.LambdaForm$DMH/0x0000007001084000 +java.util.function.Function$$Lambda$146/0x000000700106e488 +org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils +org.junit.jupiter.api.TestInstance +jdk.internal.reflect.ClassFileConstants +jdk.internal.reflect.AccessorGenerator +jdk.internal.reflect.MethodAccessorGenerator +jdk.internal.reflect.ByteVectorFactory +jdk.internal.reflect.ByteVector +jdk.internal.reflect.ByteVectorImpl +jdk.internal.reflect.ClassFileAssembler +jdk.internal.reflect.UTF8 +jdk.internal.reflect.Label +jdk.internal.reflect.Label$PatchInfo +jdk.internal.reflect.MethodAccessorGenerator$1 +jdk.internal.reflect.ClassDefiner +jdk.internal.reflect.ClassDefiner$1 +jdk.internal.reflect.GeneratedConstructorAccessor1 +java.lang.Class$1 +jdk.internal.reflect.BootstrapConstructorAccessorImpl +org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils$$Lambda$147/0x0000007001081178 +org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils$$Lambda$148/0x00000070010813b8 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$149/0x00000070010815e0 +java.lang.invoke.LambdaForm$DMH/0x0000007001084800 +org.junit.jupiter.engine.config.EnumConfigurationParameterConverter$$Lambda$150/0x0000007001081a20 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$151/0x0000007001081c68 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$152/0x0000007001081eb0 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$153/0x00000070010820d8 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$154/0x0000007001082320 +org.junit.platform.engine.SelectorResolutionResult +org.junit.platform.engine.SelectorResolutionResult$Status +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$155/0x0000007001082bb8 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$156/0x0000007001082e08 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$157/0x0000007001083040 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$158/0x0000007001083290 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$159/0x00000070010834e8 +java.util.stream.DistinctOps +java.util.stream.DistinctOps$1 +org.junit.platform.commons.util.CollectionUtils +org.junit.platform.commons.util.CollectionUtils$$Lambda$160/0x0000007001083948 +java.util.stream.DistinctOps$1$2 +org.junit.jupiter.api.BeforeEach +jdk.proxy2.$Proxy9 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$161/0x0000007001086000 +org.junit.platform.commons.support.ReflectionSupport +org.junit.platform.engine.discovery.NestedClassSelector +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$162/0x0000007001086688 +java.util.stream.Streams$ConcatSpliterator +java.util.stream.Streams$ConcatSpliterator$OfRef +java.util.stream.AbstractPipeline$$Lambda$163/0x0000007001070f38 +java.util.stream.StreamSpliterators$AbstractWrappingSpliterator +java.util.stream.StreamSpliterators$WrappingSpliterator +java.util.stream.Streams$2 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$164/0x00000070010868d0 +java.lang.invoke.LambdaForm$DMH/0x0000007001084c00 +java.util.stream.StreamSpliterators +java.util.stream.StreamSpliterators$WrappingSpliterator$$Lambda$165/0x0000007001071ac8 +org.junit.platform.engine.discovery.MethodSelector +org.junit.platform.engine.discovery.MethodSelector$$Lambda$166/0x0000007001086d40 +org.junit.platform.commons.util.ClassUtils +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$167/0x0000007001087188 +org.junit.platform.engine.discovery.IterationSelector +org.junit.platform.engine.discovery.DirectorySelector +org.junit.platform.engine.discovery.FileSelector +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$168/0x0000007001087a38 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$169/0x0000007001087c60 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$1 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$2 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$3 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$170/0x0000007001085b38 +java.lang.invoke.LambdaForm$DMH/0x0000007001088000 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$171/0x0000007001085d80 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$172/0x000000700108c000 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$173/0x000000700108c240 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$174/0x000000700108c488 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$175/0x000000700108c6b0 +org.junit.platform.commons.util.ClassUtils$$Lambda$176/0x000000700108c8f8 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall$VoidMethodInterceptorCall +org.junit.jupiter.api.extension.Extension +org.junit.jupiter.api.extension.InvocationInterceptor +java.lang.invoke.LambdaForm$DMH/0x0000007001088400 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$177/0x000000700108da08 +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall$$Lambda$178/0x000000700108de28 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$179/0x000000700108e050 +org.junit.jupiter.api.DisplayNameGenerator$$Lambda$180/0x000000700108e298 +org.junit.platform.engine.support.descriptor.MethodSource +org.junit.platform.commons.util.AnnotationUtils$$Lambda$181/0x000000700108e700 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$182/0x000000700108e940 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$183/0x000000700108eb90 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$184/0x000000700108edd0 +java.util.HashMap$KeySpliterator +org.junit.jupiter.engine.descriptor.Filterable +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$185/0x000000700108f1f8 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$186/0x000000700108f430 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$187/0x000000700108f678 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$188/0x000000700108f8b0 +org.junit.jupiter.api.ClassDescriptor +org.junit.jupiter.engine.discovery.AbstractAnnotatedDescriptorWrapper +org.junit.jupiter.engine.discovery.DefaultClassDescriptor +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$189/0x000000700108a258 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$MessageGenerator +java.lang.invoke.LambdaForm$DMH/0x0000007001088800 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$190/0x000000700108a698 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$191/0x000000700108a8c0 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$DescriptorWrapperOrderer +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$$Lambda$192/0x000000700108acf8 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$$Lambda$193/0x000000700108af50 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$$Lambda$194/0x000000700108b198 +java.lang.invoke.LambdaForm$DMH/0x0000007001088c00 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$$Lambda$195/0x000000700108b3b8 +org.junit.jupiter.api.TestClassOrder +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$196/0x000000700108b7f0 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$197/0x000000700108ba30 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$198/0x000000700108bc70 +org.junit.platform.engine.TestDescriptor$$Lambda$199/0x0000007001089000 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$200/0x0000007001089238 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$201/0x0000007001089470 +org.junit.jupiter.api.TestMethodOrder +org.junit.platform.commons.support.AnnotationSupport +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$202/0x0000007001089ab8 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$203/0x0000007001089cf8 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$204/0x0000007001090000 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$205/0x0000007001090240 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$206/0x0000007001090468 +java.lang.invoke.LambdaForm$DMH/0x0000007001094000 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$207/0x00000070010906b0 +org.junit.platform.engine.TestDescriptor$Type +org.junit.platform.engine.TestDescriptor$$Lambda$208/0x0000007001090d28 +org.junit.platform.launcher.core.EngineDiscoveryResultValidator$$Lambda$209/0x0000007001090f78 +org.junit.platform.launcher.core.EngineDiscoveryResultValidator$$Lambda$210/0x00000070010911a0 +org.junit.platform.launcher.EngineDiscoveryResult +org.junit.platform.launcher.EngineDiscoveryResult$Status +org.junit.platform.commons.util.ExceptionUtils +java.io.StringWriter +org.junit.platform.launcher.listeners.discovery.AbortOnFailureLauncherDiscoveryListener$$Lambda$211/0x0000007001091c30 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$212/0x0000007001091e60 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$213/0x00000070010920b0 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$214/0x0000007001092308 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$215/0x0000007001092548 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$216/0x0000007001092768 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$217/0x00000070010929b8 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$218/0x0000007001092be0 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$219/0x0000007001092e18 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$220/0x0000007001093048 +org.junit.platform.launcher.core.LauncherDiscoveryResult +org.junit.platform.launcher.TestPlan$$Lambda$221/0x00000070010934a0 +org.junit.platform.launcher.TestPlan$$Lambda$222/0x00000070010936f0 +org.junit.platform.launcher.TestPlan$$Lambda$223/0x0000007001093918 +org.junit.platform.launcher.TestIdentifier +org.junit.platform.launcher.TestIdentifier$SerializedForm +java.io.ObjectStreamClass +java.io.ObjectStreamClass$Caches +java.io.ClassCache +java.io.ObjectStreamClass$Caches$1 +java.io.ClassCache$1 +java.io.ObjectStreamClass$Caches$2 +java.lang.ClassValue$ClassValueMap +java.io.Externalizable +java.io.ObjectStreamClass$2 +jdk.internal.reflect.UnsafeFieldAccessorFactory +jdk.internal.reflect.UnsafeQualifiedStaticFieldAccessorImpl +jdk.internal.reflect.UnsafeQualifiedStaticLongFieldAccessorImpl +java.util.ComparableTimSort +jdk.internal.reflect.SerializationConstructorAccessorImpl +jdk.internal.reflect.GeneratedSerializationConstructorAccessor1 +java.io.ObjectOutput +java.io.ObjectStreamConstants +java.io.ObjectOutputStream +java.io.ObjectInput +java.io.ObjectInputStream +java.lang.Class$$Lambda$224/0x0000007001075608 +java.util.stream.Collectors$$Lambda$32/0x800000047 +java.util.stream.Collectors$$Lambda$24/0x80000003f +java.util.stream.Collectors$$Lambda$27/0x800000042 +java.util.stream.Collectors$$Lambda$29/0x800000044 +java.lang.CloneNotSupportedException +java.io.ClassCache$CacheRef +java.io.ObjectStreamClass$FieldReflectorKey +java.io.ObjectStreamClass$FieldReflector +org.junit.platform.launcher.TestIdentifier$$Lambda$229/0x0000007001096000 +org.junit.platform.launcher.TestPlan$$Lambda$230/0x0000007001096240 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$231/0x0000007001096480 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$232/0x00000070010966b8 +software.amazon.lambda.powertools.metrics.ConfigurationPrecedenceTest +com.amazonaws.services.lambda.runtime.Context +com.amazonaws.services.lambda.runtime.RequestHandler +org.junitpioneer.jupiter.SetEnvironmentVariable$SetEnvironmentVariables +org.junitpioneer.jupiter.SetEnvironmentVariable +org.junitpioneer.jupiter.WritesEnvironmentVariable +org.junit.jupiter.api.extension.ExtendWith +sun.reflect.annotation.AnnotationParser$$Lambda$233/0x0000007001076168 +jdk.proxy2.$Proxy10 +jdk.proxy2.$Proxy11 +software.amazon.lambda.powertools.metrics.ConfigurationPrecedenceTest$HandlerWithDefaultMetricsAnnotation +software.amazon.lambda.powertools.metrics.ConfigurationPrecedenceTest$HandlerWithMetricsAnnotation +org.junit.jupiter.api.parallel.ResourceLock +jdk.proxy2.$Proxy12 +sun.reflect.annotation.AnnotationParser$$Lambda$234/0x0000007001076390 +org.junit.jupiter.api.extension.BeforeEachCallback +org.junit.jupiter.api.extension.AfterEachCallback +org.junit.jupiter.api.extension.BeforeAllCallback +org.junit.jupiter.api.extension.AfterAllCallback +org.junitpioneer.jupiter.AbstractEntryBasedExtension +org.junitpioneer.jupiter.EnvironmentVariableExtension +jdk.proxy2.$Proxy13 +org.junit.jupiter.api.parallel.ResourceAccessMode +jdk.proxy2.$Proxy14 +jdk.internal.reflect.GeneratedConstructorAccessor2 +org.junit.jupiter.api.parallel.ResourceLocks +jdk.internal.reflect.GeneratedConstructorAccessor3 +org.junit.jupiter.api.extension.Extensions +sun.reflect.annotation.AnnotationInvocationHandler$1 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$235/0x0000007001098aa0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$236/0x0000007001098cc8 +software.amazon.lambda.powertools.metrics.MetricsFactoryTest +jdk.internal.reflect.GeneratedConstructorAccessor4 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest +java.time.temporal.TemporalAccessor +java.time.temporal.Temporal +java.time.temporal.TemporalAdjuster +java.time.Instant +software.amazon.lambda.powertools.metrics.internal.EmfMetricsLoggerTest +software.amazon.lambda.powertools.metrics.model.MetricUnit +software.amazon.cloudwatchlogs.emf.model.Unit +org.junit.jupiter.params.ParameterizedTest +jdk.proxy2.$Proxy15 +org.junit.jupiter.params.provider.MethodSource +org.junit.jupiter.params.provider.ArgumentsSource +jdk.proxy2.$Proxy16 +jdk.proxy2.$Proxy17 +org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider +org.junit.jupiter.params.ParameterizedTestExtension +jdk.internal.reflect.GeneratedConstructorAccessor5 +jdk.internal.reflect.GeneratedConstructorAccessor6 +org.junit.jupiter.params.provider.ArgumentsProvider +org.junit.jupiter.params.support.AnnotationConsumer +org.junit.jupiter.params.provider.AnnotationBasedArgumentsProvider +org.junit.jupiter.params.provider.MethodArgumentsProvider +jdk.proxy2.$Proxy18 +org.junit.jupiter.params.provider.ArgumentsSources +org.junit.platform.commons.util.ClassUtils$$Lambda$237/0x000000700109ba00 +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor +java.util.function.BiPredicate +org.junit.jupiter.engine.descriptor.DynamicDescendantFilter +org.junit.jupiter.engine.descriptor.DynamicDescendantFilter$WithoutIndexFiltering +org.junit.jupiter.engine.descriptor.DynamicDescendantFilter$Mode +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest +jdk.internal.reflect.GeneratedConstructorAccessor7 +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest$HandlerWithAnnotationOnWrongMethod +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest$HandlerWithColdStartMetricsAnnotation +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest$HandlerWithCustomFunctionName +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest$HandlerWithDefaultMetricsAnnotation +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest$HandlerWithMetricsAnnotation +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest$HandlerWithServiceNameAndColdStart +software.amazon.lambda.powertools.metrics.provider.EmfMetricsProviderTest +software.amazon.lambda.powertools.metrics.model.DimensionSetTest +software.amazon.lambda.powertools.metrics.model.DimensionSet +software.amazon.lambda.powertools.metrics.Metrics +software.amazon.lambda.powertools.metrics.testutils.TestMetrics +software.amazon.lambda.powertools.metrics.model.MetricResolution +org.junit.platform.commons.util.ReflectionUtils$$Lambda$238/0x00000070010a0248 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$239/0x00000070010a0488 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$240/0x00000070010a06c8 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$241/0x00000070010a0920 +java.lang.reflect.Executable$$Lambda$242/0x00000070010767f0 +software.amazon.lambda.powertools.metrics.testutils.TestContext +com.amazonaws.services.lambda.runtime.LambdaLogger +com.amazonaws.services.lambda.runtime.CognitoIdentity +com.amazonaws.services.lambda.runtime.ClientContext +software.amazon.lambda.powertools.metrics.testutils.TestMetricsProvider +org.apache.maven.surefire.api.util.TestsToRun +org.apache.maven.surefire.api.util.DefaultRunOrderCalculator +java.util.random.RandomGenerator +java.util.Random +org.apache.maven.surefire.api.util.CloseableIterator +org.apache.maven.surefire.api.util.TestsToRun$ClassesIterator +java.util.NoSuchElementException +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$243/0x00000070010a1ff8 +org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder$$Lambda$244/0x00000070010a2230 +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$245/0x00000070010a2468 +org.junit.platform.launcher.core.CompositeTestExecutionListener$EagerTestExecutionListener +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$246/0x00000070010a28a0 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$247/0x00000070010a2af8 +org.junit.platform.engine.reporting.ReportEntry +java.lang.invoke.LambdaForm$DMH/0x00000070010a4000 +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$248/0x00000070010a2f50 +org.junit.platform.launcher.core.StreamInterceptingTestExecutionListener +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$249/0x00000070010a3450 +org.junit.platform.engine.EngineExecutionListener$1 +org.junit.platform.launcher.core.IterationOrder +org.junit.platform.launcher.core.IterationOrder$1 +org.junit.platform.launcher.core.IterationOrder$2 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$250/0x00000070010a61f8 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$251/0x00000070010a6430 +java.lang.invoke.LambdaForm$DMH/0x00000070010a4400 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$252/0x00000070010a6658 +org.junit.platform.launcher.core.CompositeEngineExecutionListener +org.junit.platform.launcher.core.ListenerRegistry$$Lambda$253/0x00000070010a6b00 +org.junit.platform.launcher.core.ExecutionListenerAdapter +org.junit.platform.launcher.core.DelegatingEngineExecutionListener +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener +org.junit.platform.launcher.core.EngineDiscoveryErrorDescriptor +org.junit.platform.launcher.core.OutcomeDelayingEngineExecutionListener +org.junit.platform.engine.ExecutionRequest +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$254/0x00000070010a7c90 +org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService +org.junit.jupiter.engine.execution.JupiterEngineExecutionContext +org.junit.jupiter.engine.execution.JupiterEngineExecutionContext$State +org.junit.platform.engine.support.hierarchical.ThrowableCollector$Factory +org.junit.platform.engine.support.hierarchical.ThrowableCollector +org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory +org.junit.jupiter.engine.support.OpenTest4JAndJUnit4AwareThrowableCollector +org.junit.jupiter.engine.JupiterTestEngine$$Lambda$255/0x00000070010a4800 +org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor +org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService$TestTask +org.junit.platform.engine.support.hierarchical.NodeTreeWalker +org.junit.platform.engine.support.hierarchical.LockManager +org.junit.platform.engine.support.hierarchical.ResourceLock +java.util.concurrent.locks.ReadWriteLock +org.junit.platform.engine.support.hierarchical.ExclusiveResource +org.junit.platform.engine.support.hierarchical.LockManager$$Lambda$256/0x00000070010a8858 +org.junit.platform.engine.support.hierarchical.LockManager$$Lambda$257/0x00000070010a8a98 +java.lang.invoke.LambdaForm$DMH/0x00000070010ac000 +java.lang.invoke.LambdaForm$MH/0x00000070010ac400 +java.util.Comparator$$Lambda$258/0x0000007001076e90 +java.util.Comparators$NaturalOrderComparator +java.lang.invoke.LambdaForm$DMH/0x00000070010ac800 +java.util.Comparator$$Lambda$259/0x00000070010777f0 +java.lang.invoke.LambdaForm$DMH/0x00000070010acc00 +java.util.Comparator$$Lambda$260/0x0000007001077a90 +org.junit.platform.engine.support.hierarchical.ExclusiveResource$LockMode +org.junit.platform.engine.support.hierarchical.LockManager$$Lambda$261/0x00000070010a8f20 +org.junit.platform.engine.support.hierarchical.SingleLock +org.junit.platform.engine.support.hierarchical.LockManager$$Lambda$262/0x00000070010a95c8 +java.util.concurrent.locks.ReentrantReadWriteLock +java.util.concurrent.locks.ReentrantReadWriteLock$Sync +java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync +java.util.concurrent.locks.ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter +java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock +java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock +org.junit.platform.engine.support.hierarchical.NodeUtils +org.junit.platform.engine.support.hierarchical.NodeUtils$1 +org.junit.platform.engine.support.hierarchical.NodeExecutionAdvisor +org.junit.platform.engine.support.hierarchical.NodeTreeWalker$$Lambda$263/0x00000070010a9f08 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$264/0x00000070010aa140 +org.junit.platform.engine.support.hierarchical.NodeTreeWalker$$Lambda$265/0x00000070010aa380 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$1 +org.junit.platform.engine.support.hierarchical.NodeTreeWalker$$Lambda$266/0x00000070010aa9b8 +org.junit.platform.engine.support.hierarchical.Node$ExecutionMode +org.junit.platform.engine.support.hierarchical.NodeTreeWalker$$Lambda$267/0x00000070010ab048 +org.junit.platform.engine.support.hierarchical.NodeTreeWalker$$Lambda$268/0x00000070010ab280 +org.junit.platform.commons.util.CollectionUtils$$Lambda$269/0x00000070010ab4b8 +org.junit.platform.engine.support.hierarchical.NodeTestTaskContext +org.junit.platform.engine.support.hierarchical.NodeTestTask +org.junit.platform.engine.support.hierarchical.Node$DynamicTestExecutor +java.lang.invoke.LambdaForm$DMH/0x00000070010ad000 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$270/0x00000070010abd70 +org.opentest4j.IncompleteExecutionException +org.opentest4j.TestAbortedException +org.junit.jupiter.engine.support.OpenTest4JAndJUnit4AwareThrowableCollector$$Lambda$271/0x00000070010ae4c0 +org.junit.platform.commons.util.UnrecoverableExceptions +org.junit.jupiter.engine.support.OpenTest4JAndJUnit4AwareThrowableCollector$$Lambda$272/0x00000070010ae920 +org.junit.platform.engine.support.hierarchical.ThrowableCollector$Executable +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$273/0x00000070010aed40 +org.junit.jupiter.engine.extension.MutableExtensionRegistry +org.junit.jupiter.api.extension.ExecutionCondition +org.junit.jupiter.engine.extension.DisabledCondition +org.junit.jupiter.engine.extension.TimeoutExtension +org.junit.jupiter.api.Timeout +org.junit.jupiter.api.extension.ExtensionContext$Namespace +org.junit.jupiter.engine.extension.RepeatedTestExtension +org.junit.jupiter.api.extension.TestTemplateInvocationContext +org.junit.jupiter.api.extension.ParameterResolver +org.junit.jupiter.engine.extension.TestInfoParameterResolver +org.junit.jupiter.api.TestInfo +org.junit.jupiter.engine.extension.TestReporterParameterResolver +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$274/0x00000070010b0440 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$275/0x00000070010b0678 +org.junit.jupiter.engine.extension.TempDirectory +org.junit.jupiter.engine.extension.TempDirectory$Scope +org.junit.jupiter.api.extension.AnnotatedElementContext +org.junit.jupiter.api.extension.ParameterResolutionException +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$276/0x00000070010b11c8 +org.junit.jupiter.engine.execution.DefaultExecutableInvoker +org.junit.jupiter.engine.descriptor.AbstractExtensionContext +org.junit.jupiter.engine.descriptor.JupiterEngineExtensionContext +org.junit.jupiter.api.extension.ExtensionContext$Store +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$CloseAction +java.lang.invoke.LambdaForm$DMH/0x00000070010b4000 +org.junit.jupiter.engine.descriptor.AbstractExtensionContext$$Lambda$277/0x00000070010b2210 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore +org.junit.platform.engine.support.store.NamespacedHierarchicalStoreException +org.junit.jupiter.engine.descriptor.AbstractExtensionContext$$Lambda$278/0x00000070010b28b8 +org.junit.jupiter.engine.descriptor.AbstractExtensionContext$$Lambda$279/0x00000070010b2af8 +org.junit.jupiter.engine.descriptor.AbstractExtensionContext$$Lambda$280/0x00000070010b2d18 +org.junit.jupiter.engine.execution.JupiterEngineExecutionContext$Builder +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$281/0x00000070010b3190 +org.junit.platform.engine.support.hierarchical.Node$SkipResult +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$282/0x00000070010b35d8 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$283/0x00000070010b3810 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$284/0x00000070010b3a38 +org.junit.platform.launcher.TestPlan$$Lambda$285/0x00000070010b3c70 +org.junit.platform.launcher.TestPlan$$Lambda$286/0x00000070010b6000 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$287/0x00000070010b6228 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$288/0x00000070010b6460 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$289/0x00000070010b6688 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$290/0x00000070010b68c0 +org.junit.platform.engine.UniqueIdFormat$$Lambda$291/0x00000070010b6ae8 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$292/0x00000070010b6d30 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$293/0x00000070010b6f88 +org.junit.platform.engine.support.hierarchical.Node$Invocation +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$294/0x00000070010b73b0 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$295/0x00000070010b75d8 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$296/0x00000070010b7800 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$297/0x00000070010b7a48 +org.junit.platform.engine.support.hierarchical.NodeTestTask$DefaultDynamicTestExecutor +java.util.concurrent.CancellationException +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$298/0x00000070010b5000 +org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService$$Lambda$299/0x00000070010b5238 +org.junit.jupiter.engine.descriptor.ExtensionUtils +java.util.function.ToIntFunction +java.lang.invoke.LambdaForm$DMH/0x00000070010b4400 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$300/0x00000070010b5670 +java.util.Comparator$$Lambda$301/0x0000007001078e48 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$302/0x00000070010b5890 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$303/0x00000070010b5ad0 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$304/0x00000070010b5d10 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$305/0x00000070010b4800 +com.fasterxml.jackson.core.Versioned +com.fasterxml.jackson.core.TreeCodec +com.fasterxml.jackson.core.ObjectCodec +com.fasterxml.jackson.databind.ObjectMapper +org.junit.platform.commons.util.ReflectionUtils$$Lambda$306/0x00000070010b8d00 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$307/0x00000070010b8f98 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$308/0x00000070010b91b8 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$309/0x00000070010b9408 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$310/0x00000070010b9660 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$311/0x00000070010b98b8 +java.util.stream.SortedOps$RefSortingSink +java.util.stream.SortedOps$RefSortingSink$$Lambda$312/0x00000070010793a8 +org.junit.jupiter.api.extension.TestInstanceFactory +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$313/0x00000070010b9cf0 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$314/0x00000070010b9f48 +org.junit.jupiter.engine.extension.ExtensionRegistry$$Lambda$315/0x00000070010ba190 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$316/0x00000070010ba3b0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$317/0x00000070010ba600 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$318/0x00000070010ba828 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$319/0x00000070010baa70 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$320/0x00000070010bacb0 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils +org.junit.jupiter.api.BeforeAll +org.junit.platform.commons.util.AnnotationUtils$$Lambda$321/0x00000070010bb2f0 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$322/0x00000070010bb548 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$323/0x00000070010bb780 +org.junit.jupiter.api.AfterAll +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$324/0x00000070010bbbb8 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$325/0x00000070010bbdf0 +org.junit.jupiter.engine.execution.BeforeEachMethodAdapter +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$326/0x00000070010bc228 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$327/0x00000070010bc470 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$328/0x00000070010bc6a8 +org.junit.jupiter.engine.execution.AfterEachMethodAdapter +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$329/0x00000070010bcad0 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$330/0x00000070010bcd18 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$331/0x00000070010bcf40 +org.junit.jupiter.engine.descriptor.ClassExtensionContext +org.junit.jupiter.engine.execution.TestInstancesProvider +org.junit.jupiter.api.extension.TestInstances +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$332/0x00000070010bd948 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$333/0x00000070010bdb80 +org.junit.jupiter.engine.execution.ConditionEvaluator$$Lambda$334/0x00000070010bddc8 +org.junit.jupiter.engine.execution.ConditionEvaluator$$Lambda$335/0x00000070010be010 +java.util.stream.AbstractSpinedBuffer +java.util.stream.SpinedBuffer +java.util.stream.StreamSpliterators$WrappingSpliterator$$Lambda$336/0x0000007001079cd0 +java.util.function.BooleanSupplier +java.util.stream.StreamSpliterators$WrappingSpliterator$$Lambda$337/0x000000700107a180 +org.junit.jupiter.api.Disabled +org.junit.jupiter.engine.extension.DisabledCondition$$Lambda$338/0x00000070010be460 +org.junit.jupiter.engine.execution.ConditionEvaluator$$Lambda$339/0x00000070010be6a8 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$340/0x00000070010be8d0 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$341/0x00000070010beb28 +org.junit.platform.launcher.TestIdentifier$$Lambda$342/0x00000070010bed80 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$343/0x00000070010befc0 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$344/0x00000070010bf208 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$345/0x00000070010bf450 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$346/0x00000070010bf670 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$347/0x00000070010bf8c0 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$348/0x00000070010bfb00 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$349/0x00000070010bfd58 +org.apache.maven.surefire.api.report.SimpleReportEntry +org.apache.maven.surefire.api.util.internal.ClassMethod +org.apache.maven.surefire.report.ClassMethodIndexer$$Lambda$350/0x00000070010c0510 +org.apache.maven.surefire.api.util.internal.ImmutableMap +org.apache.maven.surefire.booter.spi.EventChannelEncoder$StackTrace +java.lang.StrictMath +java.nio.StringCharBuffer +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$351/0x00000070010c0f00 +org.junit.jupiter.engine.extension.TimeoutDuration +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$352/0x00000070010c1350 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$353/0x00000070010c1590 +org.junit.jupiter.api.Timeout$ThreadMode +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$354/0x00000070010c1a10 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$355/0x00000070010c1c50 +org.junit.jupiter.engine.execution.NamespaceAwareStore +org.junit.jupiter.api.extension.ExtensionContextException +org.junit.jupiter.api.extension.ExtensionContext$Store$CloseableResource +java.lang.invoke.LambdaForm$DMH/0x00000070010c4000 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$356/0x00000070010c2598 +org.junit.jupiter.engine.execution.NamespaceAwareStore$$Lambda$357/0x00000070010c27c0 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$CompositeKey +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$358/0x00000070010c2bf8 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$StoredValue +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$359/0x00000070010c3030 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$360/0x00000070010c3280 +org.junit.jupiter.engine.execution.NamespaceAwareStore$$Lambda$361/0x00000070010c34c8 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$362/0x00000070010c36f0 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$MemoizingSupplier +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$363/0x00000070010c3b68 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$364/0x00000070010c3d90 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$MemoizingSupplier$Failure +org.junit.jupiter.api.io.TempDir +org.junit.platform.commons.util.AnnotationUtils$$Lambda$365/0x00000070010c6608 +java.util.function.Predicate$$Lambda$366/0x000000700107aa40 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$367/0x00000070010c6860 +org.junit.jupiter.engine.descriptor.ClassExtensionContext$$Lambda$368/0x00000070010c6a98 +org.junit.jupiter.engine.descriptor.MethodExtensionContext +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$369/0x00000070010c70a8 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$370/0x00000070010c72d0 +java.lang.invoke.LambdaForm$DMH/0x00000070010c4400 +java.lang.invoke.LambdaForm$MH/0x00000070010c4800 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$371/0x00000070010c74f8 +org.junit.jupiter.engine.descriptor.DefaultTestInstanceFactoryContext +org.junit.jupiter.api.extension.TestInstancePreConstructCallback +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$372/0x00000070010c7b78 +java.lang.invoke.LambdaForm$DMH/0x00000070010c4c00 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$373/0x00000070010c7db0 +org.junit.jupiter.engine.execution.ParameterResolutionUtils +org.junit.jupiter.api.extension.ParameterContext +org.junit.jupiter.engine.execution.ConstructorInvocation +org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptorCall +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$$Lambda$374/0x00000070010c58a0 +org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation +java.util.ArrayList$ListItr +org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation +com.fasterxml.jackson.core.JacksonException +com.fasterxml.jackson.core.JsonProcessingException +com.fasterxml.jackson.databind.DatabindException +com.fasterxml.jackson.databind.JsonMappingException +com.fasterxml.jackson.core.TokenStreamFactory +com.fasterxml.jackson.core.JsonFactory +com.fasterxml.jackson.databind.MappingJsonFactory +com.fasterxml.jackson.databind.jsontype.SubtypeResolver +com.fasterxml.jackson.databind.jsontype.impl.StdSubtypeResolver +com.fasterxml.jackson.databind.DatabindContext +com.fasterxml.jackson.databind.SerializerProvider +com.fasterxml.jackson.databind.ser.DefaultSerializerProvider +com.fasterxml.jackson.databind.ser.DefaultSerializerProvider$Impl +com.fasterxml.jackson.databind.deser.DeserializerFactory +com.fasterxml.jackson.databind.deser.BasicDeserializerFactory +com.fasterxml.jackson.databind.deser.BeanDeserializerFactory +com.fasterxml.jackson.databind.DeserializationContext +com.fasterxml.jackson.databind.deser.DefaultDeserializationContext +com.fasterxml.jackson.databind.deser.DefaultDeserializationContext$Impl +com.fasterxml.jackson.databind.ser.SerializerFactory +com.fasterxml.jackson.databind.ser.BasicSerializerFactory +com.fasterxml.jackson.databind.ser.BeanSerializerFactory +com.fasterxml.jackson.databind.AnnotationIntrospector +com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector +com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy$Provider +com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy$Provider +com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator +com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator$Base +com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator +java.text.Format +java.text.DateFormat +com.fasterxml.jackson.databind.util.StdDateFormat +com.fasterxml.jackson.core.TreeNode +com.fasterxml.jackson.databind.JsonSerializable +com.fasterxml.jackson.databind.JsonSerializable$Base +com.fasterxml.jackson.databind.JsonNode +com.fasterxml.jackson.databind.node.BaseJsonNode +com.fasterxml.jackson.databind.node.ValueNode +com.fasterxml.jackson.databind.node.NullNode +com.fasterxml.jackson.core.JsonGenerator +com.fasterxml.jackson.databind.util.TokenBuffer +com.fasterxml.jackson.databind.introspect.ClassIntrospector +com.fasterxml.jackson.databind.introspect.BasicClassIntrospector +com.fasterxml.jackson.databind.Module$SetupContext +com.fasterxml.jackson.databind.introspect.VisibilityChecker +com.fasterxml.jackson.core.JsonParser +com.fasterxml.jackson.core.base.ParserMinimalBase +com.fasterxml.jackson.databind.node.TreeTraversingParser +com.fasterxml.jackson.core.util.BufferRecycler$Gettable +com.fasterxml.jackson.core.io.SegmentedStringWriter +com.fasterxml.jackson.core.util.ByteArrayBuilder +com.fasterxml.jackson.core.type.ResolvedType +com.fasterxml.jackson.databind.JavaType +com.fasterxml.jackson.databind.type.TypeBase +com.fasterxml.jackson.databind.type.ArrayType +com.fasterxml.jackson.databind.type.CollectionLikeType +com.fasterxml.jackson.databind.type.CollectionType +com.fasterxml.jackson.databind.type.MapLikeType +com.fasterxml.jackson.databind.type.MapType +com.fasterxml.jackson.databind.exc.MismatchedInputException +com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder +com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair +com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector +com.fasterxml.jackson.databind.introspect.Annotated +com.fasterxml.jackson.databind.introspect.AnnotatedMember +com.fasterxml.jackson.databind.introspect.TypeResolutionContext +com.fasterxml.jackson.databind.introspect.AnnotatedClass +com.fasterxml.jackson.databind.introspect.AnnotatedWithParams +com.fasterxml.jackson.databind.introspect.AnnotatedMethod +com.fasterxml.jackson.databind.introspect.VirtualAnnotatedMember +com.fasterxml.jackson.databind.util.Named +com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition +com.fasterxml.jackson.databind.util.SimpleBeanPropertyDefinition +com.fasterxml.jackson.databind.BeanProperty +com.fasterxml.jackson.databind.introspect.ConcreteBeanPropertyBase +com.fasterxml.jackson.databind.ser.PropertyWriter +com.fasterxml.jackson.databind.ser.BeanPropertyWriter +com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter +com.fasterxml.jackson.databind.ser.impl.AttributePropertyWriter +com.fasterxml.jackson.databind.annotation.JsonSerialize +com.fasterxml.jackson.annotation.JsonView +com.fasterxml.jackson.annotation.JsonFormat +com.fasterxml.jackson.annotation.JsonTypeInfo +com.fasterxml.jackson.annotation.JsonRawValue +com.fasterxml.jackson.annotation.JsonUnwrapped +com.fasterxml.jackson.annotation.JsonBackReference +com.fasterxml.jackson.annotation.JsonManagedReference +com.fasterxml.jackson.databind.annotation.JsonDeserialize +com.fasterxml.jackson.annotation.JsonMerge +com.fasterxml.jackson.databind.ext.Java7Support +java.lang.IllegalAccessError +com.fasterxml.jackson.databind.ext.Java7SupportImpl +com.fasterxml.jackson.databind.util.ClassUtil +com.fasterxml.jackson.databind.util.ClassUtil$Ctor +java.beans.Transient +java.beans.ConstructorProperties +com.fasterxml.jackson.databind.util.LookupCache +com.fasterxml.jackson.databind.util.LRUMap +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$Builder +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap +java.io.ObjectStreamException +java.io.InvalidObjectException +com.fasterxml.jackson.databind.util.internal.Linked +com.fasterxml.jackson.databind.util.internal.LinkedDeque +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$1 +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$2 +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$3 +java.util.concurrent.atomic.AtomicLongArray +java.lang.invoke.VarHandleLongs$Array +java.util.concurrent.atomic.AtomicReferenceArray +java.lang.invoke.VarHandleReferences$Array +com.fasterxml.jackson.databind.cfg.BaseSettings +java.util.TimeZone +sun.util.calendar.ZoneInfo +sun.util.calendar.ZoneInfoFile +sun.util.calendar.ZoneInfoFile$1 +java.io.DataInputStream +sun.util.calendar.ZoneInfoFile$ZoneOffsetTransitionRule +com.fasterxml.jackson.databind.type.TypeFactory +com.fasterxml.jackson.databind.type.SimpleType +com.fasterxml.jackson.databind.type.IdentityEqualityType +com.fasterxml.jackson.databind.type.ResolvedRecursiveType +com.fasterxml.jackson.databind.type.ReferenceType +com.fasterxml.jackson.databind.type.IterationType +com.fasterxml.jackson.databind.type.PlaceholderForType +com.fasterxml.jackson.databind.type.TypeParser +com.fasterxml.jackson.databind.type.TypeBindings +java.text.SimpleDateFormat +java.util.Calendar +java.util.GregorianCalendar +java.text.ParseException +java.text.AttributedCharacterIterator$Attribute +java.text.Format$Field +java.text.DateFormat$Field +sun.util.calendar.ZoneInfoFile$Checksum +java.util.spi.LocaleServiceProvider +sun.util.spi.CalendarProvider +sun.util.locale.provider.LocaleProviderAdapter +sun.util.locale.provider.LocaleProviderAdapter$Type +sun.util.locale.provider.LocaleProviderAdapter$1 +sun.util.locale.provider.ResourceBundleBasedAdapter +sun.util.locale.provider.JRELocaleProviderAdapter +sun.util.cldr.CLDRLocaleProviderAdapter +sun.util.locale.provider.LocaleDataMetaInfo +sun.util.cldr.CLDRBaseLocaleDataMetaInfo +sun.util.locale.LanguageTag +sun.util.locale.ParseStatus +sun.util.locale.StringTokenIterator +sun.util.locale.InternalLocaleBuilder +sun.util.locale.InternalLocaleBuilder$CaseInsensitiveChar +sun.util.locale.BaseLocale$Key +sun.util.locale.LocaleObjectCache +sun.util.locale.BaseLocale$Cache +sun.util.locale.LocaleObjectCache$CacheEntry +java.util.Locale$Cache +sun.util.cldr.CLDRLocaleProviderAdapter$$Lambda$58/0x800000063 +jdk.internal.module.ModulePatcher$PatchedModuleReader +sun.net.www.protocol.jrt.Handler +sun.util.resources.cldr.provider.CLDRLocaleDataMetaInfo +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$60/0x800000065 +sun.util.locale.provider.AvailableLanguageTags +sun.util.locale.provider.CalendarProviderImpl +java.util.Calendar$Builder +sun.util.calendar.CalendarSystem +sun.util.calendar.CalendarSystem$GregorianHolder +sun.util.calendar.AbstractCalendar +sun.util.calendar.BaseCalendar +sun.util.calendar.Gregorian +sun.util.locale.provider.CalendarDataUtility +java.util.Locale$Builder +java.util.spi.CalendarDataProvider +sun.util.locale.provider.LocaleServiceProviderPool +java.text.spi.BreakIteratorProvider +java.text.spi.CollatorProvider +java.text.spi.DateFormatProvider +java.text.spi.DateFormatSymbolsProvider +java.text.spi.DecimalFormatSymbolsProvider +java.text.spi.NumberFormatProvider +java.util.spi.CurrencyNameProvider +java.util.spi.LocaleNameProvider +java.util.spi.TimeZoneNameProvider +sun.util.locale.provider.LocaleServiceProviderPool$LocalizedObjectGetter +sun.util.locale.provider.CalendarDataUtility$CalendarWeekParameterGetter +java.util.ResourceBundle$Control +java.util.ResourceBundle +java.util.ResourceBundle$Control$CandidateListCache +java.util.ResourceBundle$SingleFormatControl +java.util.ResourceBundle$NoFallbackControl +sun.util.cldr.CLDRLocaleProviderAdapter$$Lambda$59/0x800000064 +sun.util.locale.provider.CalendarDataProviderImpl +sun.util.cldr.CLDRCalendarDataProviderImpl +sun.util.locale.provider.LocaleResources +sun.util.resources.LocaleData +sun.util.resources.LocaleData$1 +sun.util.resources.Bundles$Strategy +sun.util.resources.LocaleData$LocaleDataStrategy +sun.util.resources.Bundles +sun.util.resources.Bundles$1 +jdk.internal.access.JavaUtilResourceBundleAccess +java.util.ResourceBundle$1 +java.util.ResourceBundle$2 +sun.util.resources.Bundles$CacheKey +java.util.ListResourceBundle +sun.util.resources.cldr.CalendarData +java.util.ResourceBundle$ResourceBundleProviderHelper +java.util.ResourceBundle$ResourceBundleProviderHelper$$Lambda$12/0x800000010 +sun.util.resources.Bundles$CacheKeyReference +sun.util.resources.Bundles$BundleReference +sun.util.locale.provider.LocaleResources$ResourceReference +sun.util.calendar.CalendarDate +sun.util.calendar.BaseCalendar$Date +sun.util.calendar.Gregorian$Date +sun.util.calendar.CalendarUtils +java.text.DateFormatSymbols +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$62/0x800000067 +sun.util.locale.provider.DateFormatSymbolsProviderImpl +sun.text.resources.cldr.FormatData +sun.text.resources.cldr.FormatData_en +java.text.NumberFormat +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$64/0x800000069 +sun.util.locale.provider.NumberFormatProviderImpl +java.text.DecimalFormatSymbols +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$63/0x800000068 +sun.util.locale.provider.DecimalFormatSymbolsProviderImpl +java.lang.StringLatin1$CharsSpliterator +java.util.stream.IntStream +java.util.stream.IntPipeline +java.util.stream.IntPipeline$Head +java.util.function.IntPredicate +java.text.DecimalFormatSymbols$$Lambda$8/0x80000000c +java.util.stream.IntPipeline$StatelessOp +java.util.stream.IntPipeline$10 +java.util.function.IntConsumer +java.util.stream.Sink$OfInt +java.util.stream.FindOps$FindSink$OfInt +java.util.OptionalInt +java.util.stream.FindOps$FindSink$OfInt$$Lambda$37/0x80000004c +java.util.stream.FindOps$FindSink$OfInt$$Lambda$35/0x80000004a +java.util.stream.FindOps$FindSink$OfInt$$Lambda$36/0x80000004b +java.util.stream.FindOps$FindSink$OfInt$$Lambda$34/0x800000049 +java.util.stream.Sink$ChainedInt +java.util.stream.IntPipeline$10$1 +java.lang.StringUTF16$CharsSpliterator +java.lang.CharacterData00 +java.text.DecimalFormat +java.text.FieldPosition +java.text.DigitList +java.math.RoundingMode +java.util.Date +com.fasterxml.jackson.core.Base64Variants +com.fasterxml.jackson.core.Base64Variant +com.fasterxml.jackson.core.Base64Variant$PaddingReadBehaviour +com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy +com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy +com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy$RecordNaming +com.fasterxml.jackson.databind.cfg.CacheProvider +com.fasterxml.jackson.databind.cfg.DefaultCacheProvider +com.fasterxml.jackson.core.io.DataOutputAsStream +com.fasterxml.jackson.core.SerializableString +com.fasterxml.jackson.core.TSFBuilder +com.fasterxml.jackson.core.JsonFactoryBuilder +java.io.CharArrayReader +com.fasterxml.jackson.core.base.GeneratorBase +com.fasterxml.jackson.core.json.JsonGeneratorImpl +com.fasterxml.jackson.core.json.WriterBasedJsonGenerator +com.fasterxml.jackson.core.base.ParserBase +com.fasterxml.jackson.core.json.JsonParserBase +com.fasterxml.jackson.core.json.ReaderBasedJsonParser +com.fasterxml.jackson.core.json.UTF8DataInputJsonParser +com.fasterxml.jackson.core.json.UTF8JsonGenerator +com.fasterxml.jackson.core.io.UTF8Writer +com.fasterxml.jackson.core.async.NonBlockingInputFeeder +com.fasterxml.jackson.core.async.ByteArrayFeeder +com.fasterxml.jackson.core.json.async.NonBlockingJsonParserBase +com.fasterxml.jackson.core.json.async.NonBlockingUtf8JsonParserBase +com.fasterxml.jackson.core.json.async.NonBlockingJsonParser +com.fasterxml.jackson.core.async.ByteBufferFeeder +com.fasterxml.jackson.core.json.async.NonBlockingByteBufferJsonParser +com.fasterxml.jackson.core.util.JacksonFeature +com.fasterxml.jackson.core.JsonFactory$Feature +com.fasterxml.jackson.core.JsonParser$Feature +com.fasterxml.jackson.core.JsonGenerator$Feature +com.fasterxml.jackson.core.io.SerializedString +com.fasterxml.jackson.core.io.JsonStringEncoder +com.fasterxml.jackson.core.io.CharTypes +com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer +com.fasterxml.jackson.core.exc.StreamConstraintsException +com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer$TableInfo +com.fasterxml.jackson.core.util.JsonRecyclerPools +com.fasterxml.jackson.core.util.RecyclerPool +com.fasterxml.jackson.core.util.RecyclerPool$ThreadLocalPoolBase +com.fasterxml.jackson.core.util.JsonRecyclerPools$ThreadLocalPool +com.fasterxml.jackson.core.util.RecyclerPool$WithPool +com.fasterxml.jackson.core.StreamReadConstraints +com.fasterxml.jackson.core.StreamWriteConstraints +com.fasterxml.jackson.core.ErrorReportConfiguration +com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer +com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer$TableInfo +com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer$Bucket +com.fasterxml.jackson.databind.util.RootNameLookup +com.fasterxml.jackson.databind.introspect.ClassIntrospector$MixInResolver +com.fasterxml.jackson.databind.introspect.SimpleMixInResolver +com.fasterxml.jackson.databind.cfg.MapperConfig +com.fasterxml.jackson.databind.cfg.MapperConfigBase +com.fasterxml.jackson.databind.SerializationConfig +com.fasterxml.jackson.databind.BeanDescription +com.fasterxml.jackson.databind.introspect.BasicBeanDescription +com.fasterxml.jackson.databind.DeserializationConfig +com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver +com.fasterxml.jackson.databind.introspect.AnnotationCollector +com.fasterxml.jackson.databind.util.Annotations +com.fasterxml.jackson.databind.introspect.AnnotationCollector$EmptyCollector +com.fasterxml.jackson.databind.introspect.AnnotationCollector$NoAnnotations +com.fasterxml.jackson.databind.introspect.AnnotatedClass$Creators +com.fasterxml.jackson.databind.introspect.AnnotatedConstructor +com.fasterxml.jackson.databind.introspect.AnnotatedParameter +com.fasterxml.jackson.databind.cfg.ConfigOverrides +com.fasterxml.jackson.annotation.JacksonAnnotationValue +com.fasterxml.jackson.annotation.JsonInclude$Value +com.fasterxml.jackson.annotation.JsonInclude$Include +com.fasterxml.jackson.annotation.JsonSetter$Value +com.fasterxml.jackson.annotation.Nulls +com.fasterxml.jackson.databind.introspect.VisibilityChecker$Std +com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility +com.fasterxml.jackson.databind.cfg.CoercionConfigs +com.fasterxml.jackson.databind.type.LogicalType +com.fasterxml.jackson.databind.cfg.CoercionAction +com.fasterxml.jackson.databind.cfg.CoercionConfig +com.fasterxml.jackson.databind.cfg.MutableCoercionConfig +com.fasterxml.jackson.databind.cfg.CoercionInputShape +com.fasterxml.jackson.databind.jsontype.DefaultBaseTypeLimitingValidator +com.fasterxml.jackson.core.PrettyPrinter +com.fasterxml.jackson.annotation.JsonFormat$Value +com.fasterxml.jackson.annotation.JsonFormat$Shape +com.fasterxml.jackson.annotation.JsonFormat$Features +com.fasterxml.jackson.databind.cfg.ConfigOverride +com.fasterxml.jackson.databind.cfg.ConfigOverride$Empty +com.fasterxml.jackson.databind.cfg.ConfigFeature +com.fasterxml.jackson.databind.MapperFeature +com.fasterxml.jackson.core.util.Instantiatable +com.fasterxml.jackson.core.util.DefaultPrettyPrinter +com.fasterxml.jackson.core.util.DefaultPrettyPrinter$Indenter +com.fasterxml.jackson.core.util.Separators +com.fasterxml.jackson.core.util.Separators$Spacing +com.fasterxml.jackson.core.util.DefaultPrettyPrinter$NopIndenter +com.fasterxml.jackson.core.util.DefaultPrettyPrinter$FixedSpaceIndenter +com.fasterxml.jackson.core.util.DefaultIndenter +com.fasterxml.jackson.databind.SerializationFeature +com.fasterxml.jackson.databind.cfg.DatatypeFeatures +com.fasterxml.jackson.databind.cfg.DatatypeFeatures$DefaultHolder +com.fasterxml.jackson.databind.cfg.DatatypeFeature +com.fasterxml.jackson.databind.cfg.EnumFeature +com.fasterxml.jackson.databind.cfg.JsonNodeFeature +com.fasterxml.jackson.databind.cfg.ContextAttributes +com.fasterxml.jackson.databind.cfg.ContextAttributes$Impl +com.fasterxml.jackson.databind.DeserializationFeature +com.fasterxml.jackson.databind.node.JsonNodeCreator +com.fasterxml.jackson.databind.node.JsonNodeFactory +com.fasterxml.jackson.databind.node.NumericNode +com.fasterxml.jackson.databind.node.FloatNode +com.fasterxml.jackson.databind.node.DoubleNode +com.fasterxml.jackson.databind.node.BigIntegerNode +com.fasterxml.jackson.databind.node.ShortNode +com.fasterxml.jackson.databind.node.IntNode +com.fasterxml.jackson.databind.node.DecimalNode +com.fasterxml.jackson.databind.node.LongNode +com.fasterxml.jackson.databind.node.TextNode +com.fasterxml.jackson.databind.node.BinaryNode +com.fasterxml.jackson.databind.node.POJONode +com.fasterxml.jackson.databind.node.MissingNode +com.fasterxml.jackson.databind.node.BooleanNode +com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable +com.fasterxml.jackson.databind.JsonSerializer +com.fasterxml.jackson.databind.jsonschema.SchemaAware +com.fasterxml.jackson.databind.ser.std.StdSerializer +com.fasterxml.jackson.databind.ser.std.NullSerializer +com.fasterxml.jackson.databind.ser.impl.FailingSerializer +com.fasterxml.jackson.databind.ser.std.ToEmptyObjectSerializer +com.fasterxml.jackson.databind.ser.impl.UnknownSerializer +com.fasterxml.jackson.databind.exc.InvalidDefinitionException +com.fasterxml.jackson.databind.exc.InvalidTypeIdException +com.fasterxml.jackson.databind.ser.ContextualSerializer +com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer +com.fasterxml.jackson.databind.node.ContainerNode +com.fasterxml.jackson.databind.node.ObjectNode +com.fasterxml.jackson.databind.ser.ResolvableSerializer +com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer +com.fasterxml.jackson.databind.ser.SerializerCache +com.fasterxml.jackson.databind.deser.NullValueProvider +com.fasterxml.jackson.databind.JsonDeserializer +com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer +com.fasterxml.jackson.databind.exc.PropertyBindingException +com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException +com.fasterxml.jackson.databind.exc.InvalidFormatException +com.fasterxml.jackson.databind.exc.ValueInstantiationException +com.fasterxml.jackson.databind.deser.UnresolvedForwardReference +com.fasterxml.jackson.databind.deser.ContextualDeserializer +com.fasterxml.jackson.databind.deser.AbstractDeserializer +com.fasterxml.jackson.databind.deser.ValueInstantiator$Gettable +com.fasterxml.jackson.databind.deser.std.StdDeserializer +com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer +com.fasterxml.jackson.databind.deser.std.EnumDeserializer +com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer +com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase +com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer +com.fasterxml.jackson.databind.deser.ResolvableDeserializer +com.fasterxml.jackson.databind.deser.std.EnumMapDeserializer +com.fasterxml.jackson.databind.deser.std.MapDeserializer +com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer +com.fasterxml.jackson.databind.deser.std.StringDeserializer +com.fasterxml.jackson.databind.deser.std.MapEntryDeserializer +com.fasterxml.jackson.databind.deser.std.TokenBufferDeserializer +com.fasterxml.jackson.databind.deser.SettableBeanProperty +com.fasterxml.jackson.databind.deser.CreatorProperty +com.fasterxml.jackson.databind.deser.std.ReferenceTypeDeserializer +com.fasterxml.jackson.databind.deser.std.AtomicReferenceDeserializer +com.fasterxml.jackson.databind.deser.std.EnumSetDeserializer +com.fasterxml.jackson.databind.deser.std.CollectionDeserializer +com.fasterxml.jackson.databind.deser.std.ArrayBlockingQueueDeserializer +com.fasterxml.jackson.databind.deser.std.StringCollectionDeserializer +com.fasterxml.jackson.databind.deser.impl.ErrorThrowingDeserializer +com.fasterxml.jackson.annotation.ObjectIdGenerator +com.fasterxml.jackson.annotation.ObjectIdGenerators$Base +com.fasterxml.jackson.annotation.ObjectIdGenerators$PropertyGenerator +com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator +com.fasterxml.jackson.databind.deser.BeanDeserializerBase +com.fasterxml.jackson.databind.deser.BeanDeserializer +com.fasterxml.jackson.databind.deser.std.ThrowableDeserializer +com.fasterxml.jackson.databind.deser.impl.MethodProperty +com.fasterxml.jackson.databind.deser.impl.FieldProperty +com.fasterxml.jackson.databind.deser.impl.UnsupportedTypeDeserializer +com.fasterxml.jackson.databind.deser.impl.SetterlessProperty +com.fasterxml.jackson.databind.deser.Deserializers +com.fasterxml.jackson.databind.PropertyName +com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig +com.fasterxml.jackson.databind.deser.BeanDeserializerModifier +com.fasterxml.jackson.databind.AbstractTypeResolver +com.fasterxml.jackson.databind.deser.ValueInstantiators +com.fasterxml.jackson.databind.deser.KeyDeserializers +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializers +com.fasterxml.jackson.databind.KeyDeserializer +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringCtorKeyDeserializer +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringFactoryKeyDeserializer +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$DelegatingKD +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$EnumKD +com.fasterxml.jackson.databind.deser.DeserializerCache +com.fasterxml.jackson.databind.deser.std.StdDelegatingDeserializer +com.fasterxml.jackson.databind.ser.ContainerSerializer +com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase +com.fasterxml.jackson.databind.ser.std.CollectionSerializer +com.fasterxml.jackson.databind.ser.std.StaticListSerializerBase +com.fasterxml.jackson.databind.ser.impl.IndexedStringListSerializer +com.fasterxml.jackson.databind.ser.impl.StringCollectionSerializer +com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer +com.fasterxml.jackson.databind.ser.std.EnumSetSerializer +com.fasterxml.jackson.databind.ser.std.MapSerializer +com.fasterxml.jackson.databind.ser.impl.MapEntrySerializer +com.fasterxml.jackson.databind.ser.std.ArraySerializerBase +com.fasterxml.jackson.databind.ser.impl.StringArraySerializer +com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer +com.fasterxml.jackson.databind.ser.impl.IteratorSerializer +com.fasterxml.jackson.databind.ser.std.IterableSerializer +com.fasterxml.jackson.databind.ser.std.StdScalarSerializer +com.fasterxml.jackson.databind.ser.std.EnumSerializer +com.fasterxml.jackson.databind.ser.std.JsonValueSerializer +com.fasterxml.jackson.databind.ser.std.ToStringSerializerBase +com.fasterxml.jackson.databind.ser.std.ToStringSerializer +com.fasterxml.jackson.databind.ser.std.SerializableSerializer +com.fasterxml.jackson.databind.ser.std.DateTimeSerializerBase +com.fasterxml.jackson.databind.ser.std.CalendarSerializer +com.fasterxml.jackson.databind.ser.std.DateSerializer +com.fasterxml.jackson.databind.ser.std.ByteBufferSerializer +com.fasterxml.jackson.databind.ser.std.InetAddressSerializer +com.fasterxml.jackson.databind.ser.std.InetSocketAddressSerializer +com.fasterxml.jackson.databind.ser.std.TimeZoneSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializer +com.fasterxml.jackson.databind.ser.std.ReferenceTypeSerializer +com.fasterxml.jackson.databind.ser.impl.PropertyBasedObjectIdGenerator +com.fasterxml.jackson.databind.introspect.AnnotatedField +com.fasterxml.jackson.databind.ser.std.BeanSerializerBase +com.fasterxml.jackson.databind.ser.BeanSerializer +com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer +com.fasterxml.jackson.databind.ser.std.StringSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers +com.fasterxml.jackson.databind.ser.std.NumberSerializers$Base +com.fasterxml.jackson.databind.ser.std.NumberSerializers$IntegerSerializer +com.fasterxml.jackson.core.JsonParser$NumberType +com.fasterxml.jackson.databind.ser.std.NumberSerializers$LongSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$IntLikeSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$ShortSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$DoubleSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$FloatSerializer +com.fasterxml.jackson.databind.ser.std.BooleanSerializer +com.fasterxml.jackson.databind.ser.std.BooleanSerializer$AsNumber +com.fasterxml.jackson.databind.ser.std.NumberSerializer$BigDecimalAsStringSerializer +com.fasterxml.jackson.databind.ser.std.StdJdkSerializers +java.util.Currency +java.util.UUID +com.fasterxml.jackson.databind.ser.std.UUIDSerializer +com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicBooleanSerializer +com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicIntegerSerializer +com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicLongSerializer +com.fasterxml.jackson.databind.ser.std.FileSerializer +com.fasterxml.jackson.databind.ser.std.ClassSerializer +com.fasterxml.jackson.databind.ser.std.TokenBufferSerializer +com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig +com.fasterxml.jackson.databind.ser.Serializers +com.fasterxml.jackson.databind.ser.BeanSerializerModifier +org.junit.jupiter.engine.execution.DefaultTestInstances +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$387/0x000000700111c2d0 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$388/0x000000700111c518 +org.junit.jupiter.api.extension.TestInstancePostProcessor +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$389/0x000000700111c940 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$390/0x000000700111cb78 +org.junit.jupiter.api.Order +java.lang.invoke.LambdaForm$DMH/0x0000007001120000 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$391/0x000000700111cfc8 +org.junit.jupiter.api.extension.RegisterExtension +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$392/0x000000700111d408 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$393/0x000000700111d650 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$394/0x000000700111d8a0 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$395/0x000000700111dae8 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$396/0x000000700111dd30 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$397/0x000000700111df70 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$398/0x000000700111e1c0 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$399/0x000000700111e400 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$400/0x000000700111e658 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$401/0x000000700111e8a8 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$402/0x000000700111eae8 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$CallbackInvoker +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$403/0x000000700111ef38 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$404/0x000000700111f158 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$405/0x000000700111f380 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$406/0x000000700111f5b8 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$407/0x000000700111f808 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$408/0x000000700111fa30 +java.util.AbstractList$Itr +java.util.AbstractList$ListItr +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$409/0x000000700111fc50 +org.junit.jupiter.engine.execution.MethodInvocation +org.junit.jupiter.engine.extension.TimeoutExtension$TimeoutProvider +org.junit.jupiter.engine.extension.TimeoutConfiguration +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$410/0x00000070011246e8 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$411/0x0000007001124940 +org.junit.jupiter.engine.extension.TimeoutDurationParser +java.time.DateTimeException +java.time.format.DateTimeParseException +java.util.regex.Pattern$$Lambda$412/0x000000700107f270 +java.util.regex.Pattern$$Lambda$413/0x000000700107f4d0 +java.util.regex.Pattern$$Lambda$414/0x000000700107f730 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$415/0x0000007001124d98 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$416/0x0000007001124fc0 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$417/0x0000007001125208 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$418/0x0000007001125450 +org.junit.jupiter.api.extension.BeforeTestExecutionCallback +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$419/0x0000007001125878 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$420/0x0000007001125a98 +org.junit.jupiter.engine.descriptor.MethodExtensionContext$$Lambda$421/0x0000007001125cc0 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$422/0x0000007001125f00 +org.junit.jupiter.engine.execution.NamespaceAwareStore$$Lambda$423/0x0000007001126158 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$424/0x0000007001126380 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$425/0x00000070011265a8 +software.amazon.lambda.powertools.metrics.MetricsBuilder +org.crac.Resource +software.amazon.lambda.powertools.metrics.MetricsFactory +software.amazon.lambda.powertools.metrics.provider.EmfMetricsProvider +software.amazon.lambda.powertools.metrics.internal.EmfMetricsLogger +org.slf4j.LoggerFactory +org.slf4j.spi.SLF4JServiceProvider +java.util.ServiceConfigurationError +org.slf4j.event.LoggingEvent +org.slf4j.helpers.SubstituteServiceProvider +org.slf4j.IMarkerFactory +org.slf4j.spi.MDCAdapter +org.slf4j.ILoggerFactory +org.slf4j.helpers.SubstituteLoggerFactory +org.slf4j.Logger +java.util.concurrent.LinkedBlockingQueue +java.util.concurrent.LinkedBlockingQueue$Node +org.slf4j.helpers.BasicMarkerFactory +org.slf4j.Marker +org.slf4j.helpers.BasicMDCAdapter +java.lang.InheritableThreadLocal +org.slf4j.helpers.BasicMDCAdapter$1 +org.slf4j.helpers.ThreadLocalMapOfStacks +org.slf4j.helpers.NOP_FallbackServiceProvider +org.slf4j.helpers.NOPLoggerFactory +org.slf4j.helpers.NOPMDCAdapter +org.slf4j.helpers.Util +org.slf4j.simple.SimpleServiceProvider +org.slf4j.simple.SimpleLoggerFactory +org.slf4j.helpers.AbstractLogger +org.slf4j.helpers.LegacyAbstractLogger +org.slf4j.simple.SimpleLogger +org.slf4j.spi.LoggingEventBuilder +org.slf4j.simple.SimpleLoggerConfiguration +org.slf4j.simple.SimpleLoggerConfiguration$$Lambda$426/0x0000007001128230 +sun.net.ProgressMonitor +sun.net.ProgressMeteringPolicy +sun.net.DefaultProgressMeteringPolicy +org.slf4j.simple.OutputChoice +org.slf4j.simple.OutputChoice$OutputChoiceType +software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider +software.amazon.cloudwatchlogs.emf.environment.Environment +software.amazon.cloudwatchlogs.emf.config.EnvironmentConfigurationProvider +software.amazon.cloudwatchlogs.emf.config.Configuration +software.amazon.cloudwatchlogs.emf.config.SystemWrapper +java.lang.ProcessEnvironment +java.lang.ProcessEnvironment$ExternalData +java.lang.ProcessEnvironment$Variable +java.lang.ProcessEnvironment$Value +java.lang.ProcessEnvironment$StringEnvironment +software.amazon.cloudwatchlogs.emf.util.StringUtils +software.amazon.cloudwatchlogs.emf.environment.Environments +software.amazon.cloudwatchlogs.emf.environment.LambdaEnvironment +software.amazon.cloudwatchlogs.emf.sinks.ISink +software.amazon.cloudwatchlogs.emf.environment.AgentBasedEnvironment +software.amazon.cloudwatchlogs.emf.environment.DefaultEnvironment +software.amazon.cloudwatchlogs.emf.sinks.retry.RetryStrategy +software.amazon.cloudwatchlogs.emf.environment.EC2Environment +software.amazon.cloudwatchlogs.emf.exception.EMFClientException +software.amazon.cloudwatchlogs.emf.environment.ResourceFetcher +software.amazon.cloudwatchlogs.emf.environment.ECSEnvironment +java.net.UnknownHostException +software.amazon.cloudwatchlogs.emf.model.MetricsContext +software.amazon.cloudwatchlogs.emf.model.RootNode +com.fasterxml.jackson.databind.ser.FilterProvider +com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider +com.fasterxml.jackson.databind.ser.BeanPropertyFilter +com.fasterxml.jackson.databind.ser.PropertyFilter +com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter +software.amazon.cloudwatchlogs.emf.model.EmptyMetricsFilter +com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter$FilterExceptFilter +com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter$SerializeExceptFilter +software.amazon.cloudwatchlogs.emf.model.Metadata +java.time.InstantSource +java.time.Clock +software.amazon.cloudwatchlogs.emf.model.MetricDirective +java.util.Collections$SynchronizedList +java.util.Collections$SynchronizedRandomAccessList +software.amazon.cloudwatchlogs.emf.model.DimensionSet +software.amazon.cloudwatchlogs.emf.exception.DimensionSetExceededException +software.amazon.cloudwatchlogs.emf.logger.MetricsLogger +software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException +software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider$1 +java.util.concurrent.CompletionStage +java.util.concurrent.CompletableFuture +java.util.concurrent.CompletableFuture$AltResult +java.util.concurrent.ForkJoinPool +java.lang.invoke.VarHandleLongs$FieldInstanceReadOnly +java.lang.invoke.VarHandleLongs$FieldInstanceReadWrite +java.lang.invoke.VarHandleInts$FieldStaticReadOnly +java.lang.invoke.VarHandleInts$FieldStaticReadWrite +java.util.concurrent.ForkJoinPool$ForkJoinWorkerThreadFactory +java.util.concurrent.ForkJoinPool$DefaultForkJoinWorkerThreadFactory +java.util.concurrent.ForkJoinPool$1 +java.util.concurrent.ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory +java.util.concurrent.ForkJoinPool$WorkQueue +java.util.concurrent.CompletableFuture$AsynchronousCompletionTask +java.util.concurrent.ForkJoinTask +java.util.concurrent.CompletableFuture$Completion +software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor +software.amazon.lambda.powertools.common.internal.SystemWrapper +software.amazon.lambda.powertools.metrics.internal.Validator +software.amazon.cloudwatchlogs.emf.util.Validator +software.amazon.cloudwatchlogs.emf.exception.InvalidNamespaceException +software.amazon.cloudwatchlogs.emf.exception.InvalidTimestampException +software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException +org.assertj.core.api.InstanceOfAssertFactories +org.assertj.core.api.Assertions +org.assertj.core.api.NumberAssert +org.assertj.core.api.ComparableAssert +org.assertj.core.api.Descriptable +org.assertj.core.api.ExtensionPoints +org.assertj.core.api.Assert +org.assertj.core.api.AbstractAssert +org.assertj.core.api.AbstractObjectAssert +org.assertj.core.api.AbstractComparableAssert +org.assertj.core.api.AbstractBigIntegerAssert +org.assertj.core.api.BigIntegerAssert +org.assertj.core.data.TemporalOffset +org.assertj.core.data.TemporalUnitOffset +org.assertj.core.data.TemporalUnitWithinOffset +org.assertj.core.data.TemporalUnitLessThanOffset +org.assertj.core.configuration.ConfigurationProvider +org.assertj.core.api.AssertionsForClassTypes +org.assertj.core.api.EnumerableAssert +org.assertj.core.api.AbstractCharSequenceAssert +org.assertj.core.api.CharSequenceAssert +org.assertj.core.api.AbstractStringAssert +org.assertj.core.api.StringAssert +org.assertj.core.api.AbstractDateAssert +org.assertj.core.api.DateAssert +org.assertj.core.api.AbstractTemporalAssert +org.assertj.core.api.AbstractZonedDateTimeAssert +org.assertj.core.api.ZonedDateTimeAssert +org.assertj.core.api.AbstractShortAssert +org.assertj.core.api.ShortAssert +org.assertj.core.api.ArraySortedAssert +org.assertj.core.api.AbstractEnumerableAssert +org.assertj.core.api.AbstractArrayAssert +org.assertj.core.api.AbstractShortArrayAssert +org.assertj.core.api.ShortArrayAssert +org.assertj.core.api.AbstractYearMonthAssert +org.assertj.core.api.YearMonthAssert +org.assertj.core.api.AbstractInstantAssert +org.assertj.core.api.InstantAssert +org.assertj.core.api.AbstractDurationAssert +org.assertj.core.api.DurationAssert +org.assertj.core.api.AbstractPeriodAssert +org.assertj.core.api.PeriodAssert +org.assertj.core.api.AbstractThrowableAssert +org.assertj.core.api.ThrowableAssert +org.assertj.core.api.AbstractLocalDateTimeAssert +org.assertj.core.api.LocalDateTimeAssert +org.assertj.core.api.AbstractOffsetDateTimeAssert +org.assertj.core.api.OffsetDateTimeAssert +org.assertj.core.api.AbstractOffsetTimeAssert +org.assertj.core.api.OffsetTimeAssert +org.assertj.core.api.AbstractLocalTimeAssert +org.assertj.core.api.LocalTimeAssert +org.assertj.core.api.AbstractLocalDateAssert +org.assertj.core.api.LocalDateAssert +org.assertj.core.api.AbstractCharacterAssert +org.assertj.core.api.CharacterAssert +org.assertj.core.api.AbstractBigDecimalAssert +org.assertj.core.api.BigDecimalAssert +org.assertj.core.api.AbstractCharArrayAssert +org.assertj.core.api.CharArrayAssert +org.assertj.core.api.AbstractByteAssert +org.assertj.core.api.ByteAssert +org.assertj.core.api.AbstractBooleanAssert +org.assertj.core.api.BooleanAssert +org.assertj.core.api.AbstractBooleanArrayAssert +org.assertj.core.api.BooleanArrayAssert +org.assertj.core.api.AbstractUriAssert +org.assertj.core.api.UriAssert +org.assertj.core.api.AbstractUrlAssert +org.assertj.core.api.UrlAssert +org.assertj.core.api.AbstractByteArrayAssert +org.assertj.core.api.ByteArrayAssert +org.assertj.core.api.AbstractIntArrayAssert +org.assertj.core.api.IntArrayAssert +org.assertj.core.api.AbstractIntegerAssert +org.assertj.core.api.IntegerAssert +org.assertj.core.api.AbstractFloatArrayAssert +org.assertj.core.api.FloatArrayAssert +org.assertj.core.api.AbstractLongArrayAssert +org.assertj.core.api.LongArrayAssert +org.assertj.core.api.AbstractLongAssert +org.assertj.core.api.LongAssert +org.assertj.core.api.AbstractDoubleArrayAssert +org.assertj.core.api.DoubleArrayAssert +org.assertj.core.api.FloatingPointNumberAssert +org.assertj.core.api.AbstractDoubleAssert +org.assertj.core.api.DoubleAssert +org.assertj.core.api.AbstractFloatAssert +org.assertj.core.api.FloatAssert +org.assertj.core.api.AbstractInputStreamAssert +org.assertj.core.api.InputStreamAssert +org.assertj.core.api.AbstractFileAssert +org.assertj.core.api.FileAssert +org.assertj.core.api.ObjectAssert +org.assertj.core.description.Description +org.assertj.core.description.LazyTextDescription +org.assertj.core.description.TextDescription +org.assertj.core.api.AssertionInfo +org.assertj.core.internal.ComparisonStrategy +org.assertj.core.api.ObjectEnumerableAssert +org.assertj.core.api.IndexedObjectEnumerableAssert +org.assertj.core.api.AbstractIterableAssert +org.assertj.core.api.AbstractCollectionAssert +org.assertj.core.api.AbstractListAssert +org.assertj.core.api.FactoryBasedNavigableListAssert +org.assertj.core.api.ListAssert +org.assertj.core.internal.Objects +org.assertj.core.util.introspection.IntrospectionError +org.assertj.core.error.ErrorMessageFactory +org.assertj.core.internal.AbstractComparisonStrategy +org.assertj.core.internal.StandardComparisonStrategy +org.assertj.core.util.introspection.PropertySupport +org.assertj.core.internal.Failures +org.assertj.core.error.AssertionErrorCreator +org.assertj.core.util.Arrays +org.assertj.core.error.ConstructorInvoker +org.assertj.core.util.introspection.FieldSupport +org.assertj.core.error.GroupTypeDescription +org.assertj.core.internal.Conditions +org.assertj.core.api.WritableAssertionInfo +org.assertj.core.presentation.Representation +org.assertj.core.configuration.Configuration +org.assertj.core.configuration.PreferredAssumptionException +org.assertj.core.configuration.PreferredAssumptionException$1 +org.assertj.core.configuration.Services +org.assertj.core.util.Lists +org.assertj.core.util.Streams +org.assertj.core.util.Lists$$Lambda$427/0x00000070011aa8d0 +org.assertj.core.presentation.CompositeRepresentation +org.assertj.core.presentation.CompositeRepresentation$$Lambda$428/0x00000070011aad48 +java.util.Collections$ReverseComparator +java.util.Collections$ReverseComparator2 +org.assertj.core.presentation.StandardRepresentation +java.time.chrono.ChronoLocalDateTime +java.time.LocalDateTime +java.time.chrono.ChronoZonedDateTime +java.time.ZonedDateTime +java.time.OffsetDateTime +java.nio.file.DirectoryStream +org.assertj.core.api.ThrowableAssert$ThrowingCallable +software.amazon.lambda.powertools.metrics.MetricsBuilderTest$$Lambda$429/0x00000070011ab530 +org.assertj.core.internal.Throwables +org.assertj.core.internal.CommonValidations +org.junit.jupiter.api.extension.AfterTestExecutionCallback +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$430/0x00000070011abe30 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$431/0x00000070011ac050 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$432/0x00000070011ac288 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$433/0x00000070011ac4b0 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$434/0x00000070011ac6d8 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$435/0x00000070011ac8f8 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$436/0x00000070011acb50 +jdk.internal.reflect.UnsafeStaticObjectFieldAccessorImpl +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$437/0x00000070011acd78 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$438/0x00000070011acf98 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$439/0x00000070011ad1c0 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$440/0x00000070011ad3e8 +org.junit.jupiter.engine.descriptor.MethodExtensionContext$$Lambda$441/0x00000070011ad610 +org.junit.jupiter.api.extension.TestInstancePreDestroyCallback +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$442/0x00000070011ada50 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$443/0x00000070011adc70 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$444/0x00000070011ade98 +java.util.concurrent.ConcurrentHashMap$EntrySpliterator +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$EvaluatedValue +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$445/0x00000070011ae2f8 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$446/0x00000070011ae538 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$EvaluatedValue$$Lambda$447/0x00000070011ae788 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$448/0x00000070011ae9c8 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$449/0x00000070011aec00 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$450/0x00000070011aee28 +org.junit.platform.engine.TestExecutionResult +org.junit.platform.engine.TestExecutionResult$Status +org.junit.jupiter.api.extension.TestWatcher +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$451/0x00000070011af8c0 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$452/0x00000070011afaf8 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$453/0x00000070011afd30 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$454/0x00000070011b0000 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$455/0x00000070011b0250 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$456/0x00000070011b0490 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$457/0x00000070011b06d0 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$458/0x00000070011b0920 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$459/0x00000070011b0b58 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$460/0x00000070011b0d80 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$461/0x00000070011b0fb8 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$462/0x00000070011b11e0 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$463/0x00000070011b1418 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$464/0x00000070011b1640 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$1 +java.lang.invoke.LambdaForm$DMH/0x00000070011b4000 +software.amazon.lambda.powertools.metrics.model.DimensionSet$$Lambda$465/0x00000070011b1aa0 +org.apache.commons.lang3.StringUtils +org.apache.commons.lang3.CharUtils +java.util.function.IntFunction +org.apache.commons.lang3.CharUtils$$Lambda$466/0x00000070011b20e8 +org.apache.commons.lang3.ArrayUtils +java.util.concurrent.ThreadLocalRandom +software.amazon.lambda.powertools.metrics.internal.EmfMetricsLogger$$Lambda$467/0x00000070011b2510 +software.amazon.cloudwatchlogs.emf.logger.MetricsLogger$$Lambda$468/0x00000070011b2940 +software.amazon.cloudwatchlogs.emf.model.StorageResolution +software.amazon.cloudwatchlogs.emf.model.MetricDefinition +java.lang.invoke.LambdaForm$DMH/0x00000070011b4400 +software.amazon.cloudwatchlogs.emf.model.MetricDirective$$Lambda$469/0x00000070011b37d0 +java.lang.invoke.LambdaForm$DMH/0x00000070011b4800 +java.lang.invoke.LambdaForm$DMH/0x00000070011b4c00 +java.lang.invoke.LambdaForm$MH/0x00000070011b5000 +software.amazon.cloudwatchlogs.emf.sinks.ConsoleSink +software.amazon.cloudwatchlogs.emf.environment.LambdaEnvironment$$Lambda$470/0x00000070011b3c48 +java.util.concurrent.ConcurrentHashMap$ValueSpliterator +software.amazon.cloudwatchlogs.emf.model.MetricsContext$$Lambda$471/0x00000070011b6000 +com.fasterxml.jackson.core.util.BufferRecyclers +com.fasterxml.jackson.core.util.BufferRecycler +com.fasterxml.jackson.core.util.TextBuffer +com.fasterxml.jackson.core.io.ContentReference +com.fasterxml.jackson.core.io.IOContext +com.fasterxml.jackson.core.util.ReadConstrainedTextBuffer +com.fasterxml.jackson.core.exc.StreamWriteException +com.fasterxml.jackson.core.JsonGenerationException +com.fasterxml.jackson.core.JsonStreamContext +com.fasterxml.jackson.core.json.JsonWriteContext +com.fasterxml.jackson.core.StreamWriteCapability +com.fasterxml.jackson.core.util.JacksonFeatureSet +com.fasterxml.jackson.core.FormatFeature +com.fasterxml.jackson.core.json.JsonWriteFeature +com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap +com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap$Bucket +com.fasterxml.jackson.databind.util.TypeKey +com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap$$Lambda$472/0x00000070011b8a38 +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$EntrySet +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$EntryIterator +com.fasterxml.jackson.databind.type.ClassStack +sun.reflect.generics.repository.AbstractRepository +sun.reflect.generics.repository.GenericDeclRepository +sun.reflect.generics.repository.ClassRepository +java.lang.reflect.TypeVariable +sun.reflect.generics.tree.FormalTypeParameter +sun.reflect.generics.tree.Signature +sun.reflect.generics.tree.ClassSignature +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$WeightedValue +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$Node +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$AddTask +com.fasterxml.jackson.databind.introspect.AnnotationCollector$OneCollector +com.fasterxml.jackson.annotation.JsonFilter +com.fasterxml.jackson.annotation.JacksonAnnotation +jdk.proxy2.$Proxy19 +com.fasterxml.jackson.databind.introspect.AnnotationCollector$NCollector +com.fasterxml.jackson.annotation.JacksonAnnotationsInside +jdk.proxy2.$Proxy20 +com.fasterxml.jackson.databind.introspect.AnnotationCollector$OneAnnotation +com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector +com.fasterxml.jackson.annotation.JsonAutoDetect +com.fasterxml.jackson.annotation.JsonIdentityInfo +com.fasterxml.jackson.databind.util.ArrayIterator +com.fasterxml.jackson.databind.introspect.CollectorBase +com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector +com.fasterxml.jackson.databind.introspect.AnnotationMap +com.fasterxml.jackson.databind.introspect.TypeResolutionContext$Basic +com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector$FieldBuilder +com.fasterxml.jackson.annotation.JsonProperty +com.fasterxml.jackson.annotation.JsonProperty$Access +jdk.proxy2.$Proxy21 +com.fasterxml.jackson.annotation.JsonKey +com.fasterxml.jackson.annotation.JsonValue +com.fasterxml.jackson.annotation.JsonAnyGetter +com.fasterxml.jackson.annotation.JsonAnySetter +com.fasterxml.jackson.core.util.InternCache +com.fasterxml.jackson.annotation.JsonGetter +com.fasterxml.jackson.annotation.JsonIgnore +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$WithMember +com.fasterxml.jackson.databind.AnnotationIntrospector$ReferenceProperty +com.fasterxml.jackson.databind.AnnotationIntrospector$ReferenceProperty$Type +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$Linked +com.fasterxml.jackson.annotation.JsonAutoDetect$1 +com.fasterxml.jackson.annotation.PropertyAccessor +com.fasterxml.jackson.databind.introspect.AnnotatedMethodCollector +com.fasterxml.jackson.databind.introspect.MemberKey +com.fasterxml.jackson.databind.introspect.AnnotatedMethodCollector$MethodBuilder +jdk.proxy2.$Proxy22 +com.fasterxml.jackson.databind.introspect.AnnotatedMethodMap +sun.reflect.generics.scope.MethodScope +sun.reflect.generics.repository.ConstructorRepository +sun.reflect.generics.repository.MethodRepository +sun.reflect.generics.tree.MethodTypeSignature +java.lang.reflect.ParameterizedType +sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl +sun.reflect.generics.reflectiveObjects.LazyReflectiveObjectGenerator +sun.reflect.generics.reflectiveObjects.TypeVariableImpl +com.fasterxml.jackson.databind.type.TypeBindings$TypeParamStash +sun.reflect.generics.tree.TypeVariableSignature +com.fasterxml.jackson.databind.type.TypeBindings$AsKey +com.fasterxml.jackson.annotation.JsonSetter +com.fasterxml.jackson.databind.introspect.AnnotatedCreatorCollector +sun.reflect.generics.scope.ConstructorScope +sun.reflect.generics.tree.VoidDescriptor +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$5 +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$6 +java.util.LinkedList$ListItr +com.fasterxml.jackson.annotation.JacksonInject +com.fasterxml.jackson.databind.annotation.JsonNaming +com.fasterxml.jackson.annotation.JsonPropertyOrder +com.fasterxml.jackson.annotation.JsonPropertyDescription +com.fasterxml.jackson.databind.PropertyMetadata +com.fasterxml.jackson.databind.ext.OptionalHandlerFactory +org.w3c.dom.Node +org.w3c.dom.Document +com.fasterxml.jackson.databind.ext.Java7Handlers +com.fasterxml.jackson.databind.ext.Java7HandlersImpl +com.fasterxml.jackson.databind.ext.NioPathSerializer +com.fasterxml.jackson.databind.ext.NioPathDeserializer +java.net.InetAddress +com.fasterxml.jackson.databind.util.BeanUtil +com.fasterxml.jackson.databind.ObjectReader +com.fasterxml.jackson.databind.ObjectWriter +com.fasterxml.jackson.databind.ser.BeanSerializerBuilder +com.fasterxml.jackson.annotation.JsonIgnoreType +com.fasterxml.jackson.databind.ser.PropertyBuilder +com.fasterxml.jackson.annotation.JsonInclude +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$3 +com.fasterxml.jackson.annotation.JsonTypeId +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$2 +com.fasterxml.jackson.databind.BeanProperty$Std +com.fasterxml.jackson.databind.annotation.JsonTypeResolver +com.fasterxml.jackson.databind.ser.PropertyBuilder$1 +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$1 +com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Empty +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Single +com.fasterxml.jackson.databind.annotation.JsonAppend +com.fasterxml.jackson.annotation.JsonIgnoreProperties +com.fasterxml.jackson.annotation.JsonIgnoreProperties$Value +com.fasterxml.jackson.annotation.JsonIncludeProperties +com.fasterxml.jackson.annotation.JsonIncludeProperties$Value +com.fasterxml.jackson.databind.ser.std.MapProperty +com.fasterxml.jackson.databind.util.IgnorePropertiesUtil +com.fasterxml.jackson.databind.ser.AnyGetterWriter +com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer +com.fasterxml.jackson.databind.ser.impl.BeanAsArraySerializer +com.fasterxml.jackson.databind.ser.std.StdKeySerializers +com.fasterxml.jackson.databind.ser.std.StdKeySerializer +com.fasterxml.jackson.databind.ser.std.StdKeySerializers$StringKeySerializer +com.fasterxml.jackson.databind.ser.std.StdKeySerializers$Dynamic +com.fasterxml.jackson.databind.ser.std.StdKeySerializers$Default +com.fasterxml.jackson.databind.ser.std.StdKeySerializers$EnumKeySerializer +com.fasterxml.jackson.annotation.JsonFormat$Feature +software.amazon.cloudwatchlogs.emf.model.Metadata$$Lambda$473/0x00000070011c9930 +com.fasterxml.jackson.annotation.OptBoolean +jdk.proxy2.$Proxy23 +com.fasterxml.jackson.databind.annotation.JsonSerialize$Inclusion +com.fasterxml.jackson.databind.annotation.JsonSerialize$Typing +com.fasterxml.jackson.databind.util.Converter +com.fasterxml.jackson.databind.util.Converter$None +com.fasterxml.jackson.databind.JsonSerializer$None +software.amazon.cloudwatchlogs.emf.serializers.InstantSerializer +jdk.proxy2.$Proxy24 +com.fasterxml.jackson.databind.JsonDeserializer$None +com.fasterxml.jackson.databind.KeyDeserializer$None +software.amazon.cloudwatchlogs.emf.serializers.InstantDeserializer +jdk.proxy2.$Proxy25 +jdk.internal.ValueBased +com.sun.proxy.jdk.proxy1.$Proxy26 +java.lang.FunctionalInterface +jdk.proxy1.$Proxy27 +com.fasterxml.jackson.databind.introspect.AnnotationCollector$TwoAnnotations +com.fasterxml.jackson.databind.annotation.NoClass +com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector$1 +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$SerializerAndMapResult +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Double +com.fasterxml.jackson.core.io.NumberOutput +jdk.proxy2.$Proxy28 +sun.reflect.generics.tree.BooleanSignature +software.amazon.cloudwatchlogs.emf.serializers.UnitSerializer +software.amazon.cloudwatchlogs.emf.serializers.UnitDeserializer +software.amazon.cloudwatchlogs.emf.serializers.StorageResolutionFilter +jdk.proxy2.$Proxy29 +software.amazon.cloudwatchlogs.emf.serializers.StorageResolutionSerializer +software.amazon.cloudwatchlogs.emf.model.MetricDirective$$Lambda$474/0x00000070011ceac8 +com.fasterxml.jackson.databind.BeanProperty$Bogus +jdk.internal.vm.annotation.IntrinsicCandidate +com.sun.proxy.jdk.proxy1.$Proxy30 +java.lang.Deprecated +jdk.proxy1.$Proxy31 +com.fasterxml.jackson.databind.introspect.MethodGenericTypeResolver +com.fasterxml.jackson.databind.ser.std.NumberSerializers$1 +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Multi +software.amazon.cloudwatchlogs.emf.model.MetricDirective$$Lambda$475/0x00000070011cf658 +software.amazon.cloudwatchlogs.emf.model.MetricDirective$$Lambda$476/0x00000070011cf890 +com.fasterxml.jackson.core.exc.StreamReadException +com.fasterxml.jackson.core.exc.InputCoercionException +com.fasterxml.jackson.core.JsonParseException +com.fasterxml.jackson.core.io.JsonEOFException +com.fasterxml.jackson.core.json.JsonReadContext +com.fasterxml.jackson.core.StreamReadCapability +com.fasterxml.jackson.core.JsonToken +com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer +com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer +com.fasterxml.jackson.databind.node.ArrayNode +com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer$ObjectDeserializer +com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer$ArrayDeserializer +com.fasterxml.jackson.databind.util.LinkedNode +com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer$ContainerStack +com.fasterxml.jackson.core.io.NumberInput +com.fasterxml.jackson.core.JsonParser$NumberTypeFP +com.fasterxml.jackson.core.StreamReadFeature +org.assertj.core.internal.Strings +org.assertj.core.internal.InputStreamsException +org.assertj.core.internal.Comparables +java.util.AbstractMap$SimpleEntry +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$WriteThroughEntry +java.lang.invoke.LambdaForm$DMH/0x00000070011d8000 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$477/0x00000070011d54d0 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$478/0x00000070011d5708 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$479/0x00000070011d5940 +org.apache.maven.surefire.api.util.internal.ObjectUtils +org.apache.maven.surefire.api.util.internal.ImmutableMap$Node +jdk.internal.reflect.GeneratedMethodAccessor1 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$480/0x00000070011d5fe8 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$481/0x00000070011d6228 +java.util.stream.Nodes +java.util.stream.Node +java.util.stream.Nodes$EmptyNode +java.util.stream.Nodes$EmptyNode$OfRef +java.util.stream.Node$OfPrimitive +java.util.stream.Node$OfInt +java.util.stream.Nodes$EmptyNode$OfInt +java.util.stream.Node$OfLong +java.util.stream.Nodes$EmptyNode$OfLong +java.util.stream.Node$OfDouble +java.util.stream.Nodes$EmptyNode$OfDouble +java.util.stream.Node$Builder +java.util.stream.Nodes$ArrayNode +java.util.stream.Nodes$FixedNodeBuilder +org.junitpioneer.internal.PioneerUtils +org.junitpioneer.internal.PioneerUtils$$Lambda$482/0x00000070011d6650 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$483/0x00000070011d6890 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$484/0x00000070011d6ac8 +org.junitpioneer.jupiter.ClearEnvironmentVariable +org.junitpioneer.jupiter.ClearEnvironmentVariable$ClearEnvironmentVariables +org.junitpioneer.jupiter.EnvironmentVariableExtension$$Lambda$485/0x00000070011d7100 +org.junitpioneer.internal.PioneerUtils$$Lambda$486/0x00000070011d7340 +org.junitpioneer.internal.PioneerUtils$$Lambda$487/0x00000070011d7560 +org.junitpioneer.internal.PioneerUtils$$Lambda$488/0x00000070011d7790 +org.junitpioneer.jupiter.EnvironmentVariableExtension$$Lambda$489/0x00000070011d79d8 +org.junitpioneer.jupiter.EnvironmentVariableExtension$$Lambda$490/0x00000070011d7c18 +java.util.stream.Collectors$$Lambda$491/0x000000700114cf38 +java.util.stream.Collectors$$Lambda$492/0x000000700114d158 +java.util.stream.Collectors$$Lambda$493/0x000000700114d390 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$494/0x00000070011dc000 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$495/0x00000070011dc258 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$496/0x00000070011dc498 +java.time.chrono.ChronoLocalDate +java.time.LocalDate +java.time.temporal.TemporalField +java.time.temporal.ChronoField +java.time.temporal.ValueRange +java.time.LocalTime +java.time.Clock$SystemClock +java.time.ZoneId +java.time.ZoneOffset +java.time.ZoneRegion +java.time.zone.ZoneRulesProvider +java.time.zone.ZoneRulesProvider$1 +java.time.zone.TzdbZoneRulesProvider +java.time.zone.Ser +java.time.zone.ZoneRules +java.time.zone.ZoneOffsetTransitionRule +java.time.Month +java.time.DayOfWeek +java.time.zone.ZoneOffsetTransitionRule$TimeDefinition +java.time.zone.ZoneOffsetTransition +java.time.temporal.TemporalAdjusters +java.lang.invoke.LambdaForm$DMH/0x00000070011d8800 +java.time.temporal.TemporalAdjusters$$Lambda$497/0x00000070011507c8 +java.time.LocalDate$1 +java.time.chrono.Chronology +java.time.chrono.AbstractChronology +java.time.chrono.IsoChronology +java.time.zone.ZoneOffsetTransitionRule$1 +org.junit.platform.engine.reporting.ReportEntry$$Lambda$498/0x00000070011dc6e0 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$499/0x00000070011dc918 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$500/0x00000070011dcb50 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$501/0x00000070011dcd78 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$502/0x00000070011dcfb0 +org.apache.maven.surefire.api.report.TestOutputReportEntry +org.junitpioneer.jupiter.AbstractEntryBasedExtension$EntriesBackup +org.junitpioneer.jupiter.AbstractEntryBasedExtension$EntriesBackup$$Lambda$503/0x00000070011dd628 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$504/0x00000070011dd860 +java.lang.invoke.LambdaForm$DMH/0x00000070011d8c00 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$505/0x00000070011dda98 +org.junitpioneer.jupiter.EnvironmentVariableUtils +org.junitpioneer.jupiter.EnvironmentVariableUtils$$Lambda$506/0x00000070011dded8 +jdk.internal.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl +jdk.internal.reflect.UnsafeQualifiedFieldAccessorImpl +jdk.internal.reflect.UnsafeQualifiedObjectFieldAccessorImpl +org.aspectj.lang.JoinPoint +org.aspectj.lang.ProceedingJoinPoint +org.aspectj.lang.Signature +org.aspectj.runtime.internal.AroundClosure +software.amazon.lambda.powertools.metrics.ConfigurationPrecedenceTest$HandlerWithMetricsAnnotation$AjcClosure1 +org.aspectj.runtime.reflect.Factory +org.aspectj.lang.JoinPoint$StaticPart +org.aspectj.lang.JoinPoint$EnclosingStaticPart +org.aspectj.lang.reflect.MemberSignature +org.aspectj.lang.reflect.CodeSignature +org.aspectj.lang.reflect.MethodSignature +org.aspectj.lang.reflect.SourceLocation +org.aspectj.lang.reflect.ConstructorSignature +org.aspectj.lang.reflect.FieldSignature +org.aspectj.lang.reflect.AdviceSignature +org.aspectj.lang.reflect.InitializerSignature +org.aspectj.lang.reflect.CatchClauseSignature +org.aspectj.lang.reflect.LockSignature +org.aspectj.lang.reflect.UnlockSignature +org.aspectj.runtime.reflect.SignatureImpl +org.aspectj.runtime.reflect.MemberSignatureImpl +org.aspectj.runtime.reflect.CodeSignatureImpl +org.aspectj.runtime.reflect.MethodSignatureImpl +org.aspectj.runtime.reflect.SignatureImpl$Cache +org.aspectj.runtime.reflect.JoinPointImpl$StaticPartImpl +org.aspectj.runtime.reflect.SourceLocationImpl +org.aspectj.runtime.reflect.JoinPointImpl +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspect +org.aspectj.lang.NoAspectBoundException +software.amazon.lambda.powertools.metrics.FlushMetrics +jdk.proxy2.$Proxy32 +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspect$$Lambda$507/0x00000070011d98f0 +software.amazon.cloudwatchlogs.emf.logger.MetricsLogger$$Lambda$508/0x00000070011d9b28 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$$Lambda$509/0x00000070011d9d50 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$EntriesBackup$$Lambda$510/0x00000070011e0000 +org.junitpioneer.jupiter.EnvironmentVariableUtils$$Lambda$511/0x00000070011e0238 +org.junitpioneer.jupiter.AbstractEntryBasedExtension$EntriesBackup$$Lambda$512/0x00000070011e0470 +software.amazon.lambda.powertools.metrics.ConfigurationPrecedenceTest$HandlerWithDefaultMetricsAnnotation$AjcClosure1 +software.amazon.lambda.powertools.metrics.MetricsFactoryTest$$Lambda$513/0x00000070011e08f8 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$514/0x00000070011e0b18 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$515/0x00000070011e0d40 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$516/0x00000070011e0f68 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$517/0x00000070011e1188 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$518/0x00000070011e13b0 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$519/0x00000070011e15d8 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$520/0x00000070011e1800 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$521/0x00000070011e1a20 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$522/0x00000070011e1c40 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$523/0x00000070011e1e60 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$524/0x00000070011e2080 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$525/0x00000070011e22a0 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$526/0x00000070011e24c0 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$527/0x00000070011e26e0 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$528/0x00000070011e2900 +jdk.internal.reflect.GeneratedConstructorAccessor8 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$529/0x00000070011e2b28 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$530/0x00000070011e2d48 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$531/0x00000070011e2f68 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$532/0x00000070011e3188 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$533/0x00000070011e33a8 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$534/0x00000070011e35d0 +software.amazon.lambda.powertools.metrics.internal.ValidatorTest$$Lambda$535/0x00000070011e37f0 +software.amazon.lambda.powertools.metrics.internal.EmfMetricsLogger$$Lambda$536/0x00000070011e3a18 +software.amazon.cloudwatchlogs.emf.logger.MetricsLogger$$Lambda$537/0x00000070011e3c50 +software.amazon.lambda.powertools.metrics.internal.EmfMetricsLogger$$Lambda$538/0x00000070011e6000 +org.assertj.core.internal.Numbers +org.assertj.core.internal.RealNumbers +org.assertj.core.internal.Doubles +org.assertj.core.internal.ComparatorBasedComparisonStrategy +software.amazon.lambda.powertools.metrics.internal.EmfMetricsLoggerTest$$Lambda$539/0x00000070011e7018 +software.amazon.cloudwatchlogs.emf.Constants +org.assertj.core.internal.WholeNumbers +org.assertj.core.internal.Longs +jdk.internal.reflect.GeneratedMethodAccessor2 +jdk.internal.reflect.GeneratedMethodAccessor3 +jdk.internal.reflect.GeneratedMethodAccessor4 +jdk.internal.reflect.GeneratedMethodAccessor5 +jdk.internal.reflect.GeneratedMethodAccessor6 +jdk.internal.reflect.GeneratedMethodAccessor7 +jdk.internal.reflect.GeneratedMethodAccessor8 +jdk.internal.reflect.GeneratedMethodAccessor9 +jdk.internal.reflect.GeneratedMethodAccessor10 +jdk.internal.reflect.GeneratedMethodAccessor11 +jdk.internal.reflect.GeneratedMethodAccessor12 +jdk.internal.reflect.GeneratedMethodAccessor13 +org.assertj.core.internal.Integers +software.amazon.lambda.powertools.metrics.internal.EmfMetricsLoggerTest$$Lambda$540/0x00000070011ec3a8 +software.amazon.cloudwatchlogs.emf.logger.MetricsLogger$$Lambda$541/0x00000070011ec5d0 +java.lang.invoke.LambdaForm$DMH/0x00000070011e9400 +software.amazon.lambda.powertools.metrics.internal.EmfMetricsLogger$$Lambda$542/0x00000070011ec7f8 +software.amazon.cloudwatchlogs.emf.logger.MetricsLogger$$Lambda$543/0x00000070011eca30 +org.slf4j.event.Level +java.text.DontCareFieldPosition +java.text.Format$FieldDelegate +java.text.DontCareFieldPosition$1 +java.text.NumberFormat$Field +org.slf4j.helpers.MessageFormatter +org.slf4j.helpers.FormattingTuple +org.slf4j.simple.OutputChoice$1 +java.nio.file.attribute.FileAttribute +sun.nio.fs.UnixFileModeAttribute +sun.nio.fs.UnixChannelFactory +sun.nio.fs.UnixChannelFactory$Flags +java.nio.channels.ByteChannel +java.nio.channels.SeekableByteChannel +java.nio.channels.GatheringByteChannel +java.nio.channels.ScatteringByteChannel +java.nio.channels.InterruptibleChannel +java.nio.channels.spi.AbstractInterruptibleChannel +java.nio.channels.FileChannel +sun.nio.ch.FileChannelImpl +sun.nio.ch.IOUtil +sun.nio.ch.NativeThreadSet +sun.nio.ch.NativeDispatcher +sun.nio.ch.FileDispatcher +sun.nio.ch.FileDispatcherImpl +sun.nio.ch.FileChannelImpl$Closer +java.nio.channels.Channels +sun.nio.ch.ChannelInputStream +sun.nio.ch.NativeThread +sun.nio.ch.IOStatus +java.nio.channels.SelectableChannel +sun.nio.ch.Util +sun.nio.ch.Util$1 +sun.nio.ch.Util$BufferCache +java.nio.DirectByteBuffer$Deallocator +java.lang.invoke.LambdaForm$DMH/0x00000070011e9800 +org.assertj.core.internal.Strings$$Lambda$544/0x00000070011ed6d8 +org.assertj.core.internal.Strings$$Lambda$545/0x00000070011ed930 +software.amazon.lambda.powertools.metrics.internal.EmfMetricsLogger$$Lambda$546/0x00000070011edb50 +org.assertj.core.api.AssertionsForInterfaceTypes +org.assertj.core.api.GenericComparableAssert +org.assertj.core.api.AbstractUniversalComparableAssert +org.assertj.core.api.UniversalComparableAssert +org.assertj.core.api.AbstractMapAssert +org.assertj.core.api.MapAssert +org.assertj.core.api.AbstractMapSizeAssert +org.assertj.core.api.MapSizeAssert +org.assertj.core.internal.Maps +java.lang.InstantiationException +org.assertj.core.data.MapEntry +org.assertj.core.internal.ErrorMessages +org.junit.jupiter.engine.descriptor.TestTemplateExtensionContext +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor$$Lambda$547/0x00000070011f1640 +org.junit.jupiter.api.RepeatedTest +org.junit.jupiter.params.ParameterizedTestMethodContext +org.junit.jupiter.params.ParameterizedTestMethodContext$Resolver +org.junit.jupiter.params.aggregator.ArgumentsAccessor +org.junit.jupiter.params.aggregator.AggregateWith +org.junit.jupiter.params.ParameterizedTestMethodContext$ResolverType +org.junit.jupiter.params.ParameterizedTestMethodContext$ResolverType$1 +org.junit.jupiter.params.ParameterizedTestMethodContext$ResolverType$2 +org.junit.jupiter.params.ParameterizedTestExtension$$Lambda$548/0x00000070011f2dc0 +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor$$Lambda$549/0x00000070011f2fe8 +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor$$Lambda$550/0x00000070011f3210 +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor$$Lambda$551/0x00000070011f3458 +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor$$Lambda$552/0x00000070011f36a0 +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor$$Lambda$553/0x00000070011f38f0 +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor$$Lambda$554/0x00000070011f3b30 +org.junit.jupiter.params.ParameterizedTestExtension$$Lambda$555/0x00000070011f3d68 +org.junit.platform.engine.ConfigurationParameters$$Lambda$556/0x00000070011f3fa8 +org.junit.jupiter.params.ParameterizedTestExtension$$Lambda$557/0x00000070011f41f0 +org.junit.jupiter.params.ParameterizedTestNameFormatter +org.junit.jupiter.params.ParameterizedTestExtension$$Lambda$558/0x00000070011f4630 +org.junit.jupiter.params.ParameterizedTestExtension$$Lambda$559/0x00000070011f4870 +org.junit.jupiter.params.ParameterizedTestExtension$$Lambda$560/0x00000070011f4ab8 +org.junit.jupiter.params.ParameterizedTestExtension$$Lambda$561/0x00000070011f4d00 +org.junit.jupiter.params.provider.Arguments +org.junit.jupiter.params.ParameterizedTestExtension$$Lambda$562/0x00000070011f5148 +org.junit.jupiter.params.ParameterizedTestExtension$$Lambda$563/0x00000070011f5388 +org.junit.jupiter.params.ParameterizedTestExtension$$Lambda$564/0x00000070011f55d0 +org.junit.jupiter.params.ParameterizedTestExtension$$Lambda$565/0x00000070011f5818 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$566/0x00000070011f5a40 +org.junit.jupiter.params.support.AnnotationConsumerInitializer +org.junit.jupiter.params.support.AnnotationConsumerInitializer$AnnotationConsumingMethodSignature +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$567/0x00000070011f62b0 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$568/0x00000070011f64f0 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$569/0x00000070011f6740 +java.util.stream.ReduceOps$1 +java.util.stream.ReduceOps$1ReducingSink +java.lang.invoke.LambdaForm$DMH/0x00000070011e9c00 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$570/0x00000070011f6988 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$571/0x00000070011f6be0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$572/0x00000070011f6e30 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$573/0x00000070011f7088 +sun.reflect.generics.tree.BottomSignature +sun.reflect.generics.tree.Wildcard +java.lang.reflect.WildcardType +sun.reflect.generics.reflectiveObjects.WildcardTypeImpl +org.junit.platform.commons.util.ReflectionUtils$$Lambda$574/0x00000070011f72e0 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$575/0x00000070011f7530 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$576/0x00000070011f7788 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$577/0x00000070011f79c8 +org.junit.jupiter.engine.descriptor.TestTemplateExtensionContext$$Lambda$578/0x00000070011f7bf0 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$579/0x00000070011f8000 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$580/0x00000070011f8248 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$581/0x00000070011f8490 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$582/0x00000070011f86d8 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$583/0x00000070011f8918 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$584/0x00000070011f8b58 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$585/0x00000070011f8d80 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$586/0x00000070011f8fc8 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$587/0x00000070011f9220 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$588/0x00000070011f9448 +org.junit.jupiter.params.provider.Arguments$$Lambda$589/0x00000070011f9868 +org.junit.jupiter.params.ParameterizedTestInvocationContext +org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor +org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor$$Lambda$590/0x00000070011fa158 +org.junit.jupiter.params.ParameterizedTestNameFormatter$$Lambda$591/0x00000070011fa378 +java.util.stream.ReferencePipeline$$Lambda$592/0x0000007001156f20 +org.junit.jupiter.api.Named +java.util.stream.Streams$RangeIntSpliterator +org.junit.jupiter.params.ParameterizedTestNameFormatter$$Lambda$593/0x00000070011fa7b8 +java.util.stream.IntPipeline$1 +java.util.stream.IntPipeline$1$1 +org.junit.jupiter.params.ParameterizedTestNameFormatter$$Lambda$594/0x00000070011fa9e0 +java.text.MessageFormat +java.text.MessageFormat$Field +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$595/0x00000070011fac20 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$596/0x00000070011fae58 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$597/0x00000070011fb080 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$598/0x00000070011fb2b8 +org.junit.platform.engine.support.hierarchical.NodeTestTask$DefaultDynamicTestExecutor$$Lambda$599/0x00000070011fb4e0 +org.junit.platform.engine.support.hierarchical.NodeTestTask$DynamicTaskState +org.junit.platform.engine.support.hierarchical.NodeTestTask$DynamicTaskState$$Lambda$600/0x00000070011fb908 +org.junit.jupiter.params.ParameterizedTestParameterResolver +org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor$$Lambda$601/0x00000070011fbd88 +org.junit.jupiter.engine.execution.DefaultParameterContext +org.junit.jupiter.engine.execution.ParameterResolutionUtils$$Lambda$602/0x00000070011fc290 +org.junit.jupiter.api.TestReporter +org.junit.jupiter.params.ParameterizedTestParameterResolver$$Lambda$603/0x00000070011fc6e8 +org.junit.jupiter.params.converter.ConvertWith +org.junit.jupiter.params.ParameterizedTestMethodContext$ResolverType$1$$Lambda$604/0x00000070011fcb28 +org.junit.jupiter.params.converter.ArgumentConverter +org.junit.jupiter.params.ParameterizedTestMethodContext$ResolverType$1$$Lambda$605/0x00000070011fcf68 +org.junit.jupiter.params.ParameterizedTestMethodContext$ResolverType$1$$Lambda$606/0x00000070011fd1a8 +org.junit.jupiter.params.ParameterizedTestMethodContext$Converter +org.junit.jupiter.params.ParameterizedTestMethodContext$ResolverType$1$$Lambda$607/0x00000070011fd620 +org.junit.jupiter.params.converter.DefaultArgumentConverter +org.junit.jupiter.params.converter.ArgumentConversionException +org.junit.jupiter.params.converter.StringToObjectConverter +org.junit.jupiter.params.converter.StringToBooleanConverter +org.junit.jupiter.params.converter.StringToCharacterConverter +org.junit.jupiter.params.converter.StringToNumberConverter +org.junit.jupiter.params.converter.StringToNumberConverter$$Lambda$608/0x00000070011fe7b0 +org.junit.jupiter.params.converter.StringToNumberConverter$$Lambda$609/0x00000070011fe9f0 +org.junit.jupiter.params.converter.StringToNumberConverter$$Lambda$610/0x00000070011fec30 +org.junit.jupiter.params.converter.StringToNumberConverter$$Lambda$611/0x00000070011fee70 +org.junit.jupiter.params.converter.StringToNumberConverter$$Lambda$612/0x00000070011ff0b0 +org.junit.jupiter.params.converter.StringToNumberConverter$$Lambda$613/0x00000070011ff2f0 +org.junit.jupiter.params.converter.StringToNumberConverter$$Lambda$614/0x00000070011ff530 +org.junit.jupiter.params.converter.StringToNumberConverter$$Lambda$615/0x00000070011ff770 +org.junit.jupiter.params.converter.StringToClassConverter +org.junit.jupiter.params.converter.StringToEnumConverter +org.junit.jupiter.params.converter.StringToJavaTimeConverter +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$616/0x0000007001200248 +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$617/0x0000007001200488 +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$618/0x00000070012006c8 +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$619/0x0000007001200908 +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$620/0x0000007001200b48 +java.time.MonthDay +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$621/0x0000007001200d88 +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$622/0x0000007001200fc8 +java.time.OffsetTime +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$623/0x0000007001201208 +java.time.chrono.ChronoPeriod +java.time.Period +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$624/0x0000007001201448 +java.time.Year +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$625/0x0000007001201688 +java.time.YearMonth +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$626/0x00000070012018c8 +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$627/0x0000007001201b08 +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$628/0x0000007001201d48 +org.junit.jupiter.params.converter.StringToJavaTimeConverter$$Lambda$629/0x0000007001201f88 +org.junit.jupiter.params.converter.StringToCommonJavaTypesConverter +org.junit.jupiter.params.converter.StringToCommonJavaTypesConverter$$Lambda$630/0x0000007001202410 +org.junit.jupiter.params.converter.StringToCommonJavaTypesConverter$$Lambda$631/0x0000007001202650 +org.junit.jupiter.params.converter.StringToCommonJavaTypesConverter$$Lambda$632/0x0000007001202890 +org.junit.jupiter.params.converter.StringToCommonJavaTypesConverter$$Lambda$633/0x0000007001202ad0 +org.junit.jupiter.params.converter.StringToCommonJavaTypesConverter$$Lambda$634/0x0000007001202d10 +org.junit.jupiter.params.converter.StringToCommonJavaTypesConverter$$Lambda$635/0x0000007001202f50 +org.junit.jupiter.params.converter.StringToCommonJavaTypesConverter$$Lambda$636/0x0000007001203190 +org.junit.jupiter.params.converter.StringToCommonJavaTypesConverter$$Lambda$637/0x00000070012033d0 +org.junit.jupiter.params.converter.FallbackStringToObjectConverter +org.junit.jupiter.params.converter.FallbackStringToObjectConverter$$Lambda$638/0x0000007001203858 +org.junit.jupiter.engine.execution.ParameterResolutionUtils$$Lambda$639/0x0000007001203a98 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$640/0x0000007001203cc0 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$641/0x0000007001203f18 +org.junit.jupiter.params.ParameterizedTestParameterResolver$$Lambda$642/0x0000007001204140 +org.junit.jupiter.params.ParameterizedTestParameterResolver$$Lambda$643/0x0000007001204398 +org.junit.jupiter.params.ParameterizedTestParameterResolver$CloseableArgument +org.junit.jupiter.params.ParameterizedTestParameterResolver$$Lambda$644/0x0000007001204810 +org.junit.jupiter.params.ParameterizedTestParameterResolver$$Lambda$645/0x0000007001204a50 +org.junit.jupiter.engine.descriptor.TestTemplateExtensionContext$$Lambda$646/0x0000007001204c88 +org.junit.platform.engine.support.hierarchical.NodeTestTask$DefaultDynamicTestExecutor$$Lambda$647/0x0000007001204ec8 +jdk.internal.reflect.GeneratedConstructorAccessor9 +jdk.internal.reflect.GeneratedMethodAccessor14 +jdk.internal.reflect.GeneratedMethodAccessor15 +jdk.internal.reflect.GeneratedMethodAccessor16 +jdk.internal.reflect.GeneratedMethodAccessor17 +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor$$Lambda$648/0x0000007001205100 +software.amazon.lambda.powertools.metrics.internal.EmfMetricsLoggerTest$$Lambda$649/0x0000007001205328 +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest$HandlerWithAnnotationOnWrongMethod$AjcClosure1 +com.amazonaws.services.lambda.runtime.RequestStreamHandler +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest$HandlerWithMetricsAnnotation$AjcClosure1 +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest$HandlerWithDefaultMetricsAnnotation$AjcClosure1 +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest$HandlerWithColdStartMetricsAnnotation$AjcClosure1 +org.assertj.core.api.AbstractObjectArrayAssert +org.assertj.core.api.ObjectArrayAssert +org.assertj.core.internal.ObjectArrays +org.assertj.core.internal.Arrays +org.assertj.core.internal.Iterables +org.assertj.core.internal.Predicates +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest$HandlerWithCustomFunctionName$AjcClosure1 +software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspectTest$HandlerWithServiceNameAndColdStart$AjcClosure1 +org.assertj.core.api.CollectionAssert +org.assertj.core.api.AbstractIterableSizeAssert +org.assertj.core.api.IterableSizeAssert +org.assertj.core.util.Lists$$Lambda$650/0x000000700120fda0 +org.assertj.core.internal.StandardComparisonStrategy$$Lambda$651/0x000000700120a000 +org.assertj.core.util.Preconditions +org.assertj.core.internal.IterableDiff +org.assertj.core.internal.IterableDiff$$Lambda$652/0x000000700120a678 +org.assertj.core.util.IterableUtil +software.amazon.lambda.powertools.metrics.model.DimensionSetTest$$Lambda$653/0x000000700120aad8 +org.junit.platform.launcher.core.OutcomeDelayingEngineExecutionListener$Outcome +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$654/0x000000700120b140 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$655/0x000000700120b378 +java.lang.invoke.LambdaForm$DMH/0x0000007001209400 +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$656/0x000000700120b5a0 +org.junit.platform.launcher.core.DefaultLauncherSession$ClosedLauncher +org.apache.maven.surefire.api.suite.RunResult +org.apache.maven.surefire.booter.ForkedBooter$6 +org.apache.maven.surefire.booter.ForkedBooter$7 +java.util.concurrent.locks.AbstractQueuedSynchronizer$SharedNode +java.util.concurrent.locks.AbstractQueuedSynchronizer$ExclusiveNode +org.apache.maven.surefire.booter.ForkedBooter$1 +org.apache.maven.surefire.booter.spi.AbstractMasterProcessChannelProcessorFactory$2 +java.util.IdentityHashMap$IdentityHashMapIterator +java.util.IdentityHashMap$KeyIterator diff --git a/powertools-metrics/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-metrics/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..38e53da3e --- /dev/null +++ b/powertools-metrics/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.metrics.internal.MetricsUserAgentInterceptor diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/ConfigurationPrecedenceTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/ConfigurationPrecedenceTest.java new file mode 100644 index 000000000..492ecfc1e --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/ConfigurationPrecedenceTest.java @@ -0,0 +1,207 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; + +/** + * Tests to verify the hierarchy of precedence for configuration: + * 1. @FlushMetrics annotation + * 2. MetricsBuilder + * 3. Environment variables + */ +class ConfigurationPrecedenceTest { + + private static final PrintStream STANDARD_OUT = System.out; + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @BeforeEach + void setUp() throws Exception { + System.setOut(new PrintStream(outputStreamCaptor)); + + // Reset LambdaHandlerProcessor's serviceName + Method resetServiceName = LambdaHandlerProcessor.class.getDeclaredMethod("resetServiceName"); + resetServiceName.setAccessible(true); + resetServiceName.invoke(null); + + // Reset isColdStart + java.lang.reflect.Field coldStartField = LambdaHandlerProcessor.class.getDeclaredField("isColdStart"); + coldStartField.setAccessible(true); + coldStartField.set(null, null); + } + + @AfterEach + void tearDown() throws Exception { + System.setOut(STANDARD_OUT); + + // Reset the singleton state between tests + java.lang.reflect.Field field = MetricsFactory.class.getDeclaredField("metricsProxy"); + field.setAccessible(true); + field.set(null, null); + + field = MetricsFactory.class.getDeclaredField("provider"); + field.setAccessible(true); + field.set(null, new software.amazon.lambda.powertools.metrics.provider.EmfMetricsProvider()); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_METRICS_NAMESPACE", value = "EnvNamespace") + @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "EnvService") + void annotationShouldOverrideBuilderAndEnvironment() throws Exception { + // Given + // Configure with builder first + MetricsBuilder.builder() + .withNamespace("BuilderNamespace") + .withService("BuilderService") + .build(); + + RequestHandler<Map<String, Object>, String> handler = new HandlerWithMetricsAnnotation(); + Context context = new TestLambdaContext(); + Map<String, Object> input = new HashMap<>(); + + // When + handler.handleRequest(input, context); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + // Annotation values should take precedence + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("AnnotationNamespace"); + assertThat(rootNode.has("Service")).isTrue(); + assertThat(rootNode.get("Service").asText()).isEqualTo("AnnotationService"); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_METRICS_NAMESPACE", value = "EnvNamespace") + @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "EnvService") + void builderShouldOverrideEnvironment() throws Exception { + // Given + // Configure with builder + MetricsBuilder.builder() + .withNamespace("BuilderNamespace") + .withService("BuilderService") + .build(); + + RequestHandler<Map<String, Object>, String> handler = new HandlerWithDefaultMetricsAnnotation(); + Context context = new TestLambdaContext(); + Map<String, Object> input = new HashMap<>(); + + // When + handler.handleRequest(input, context); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + // Builder values should take precedence over environment variables + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("BuilderNamespace"); + assertThat(rootNode.has("Service")).isTrue(); + assertThat(rootNode.get("Service").asText()).isEqualTo("BuilderService"); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_METRICS_NAMESPACE", value = "EnvNamespace") + @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "EnvService") + void environmentVariablesShouldBeUsedWhenNoOverrides() throws Exception { + // Given + RequestHandler<Map<String, Object>, String> handler = new HandlerWithDefaultMetricsAnnotation(); + Context context = new TestLambdaContext(); + Map<String, Object> input = new HashMap<>(); + + // When + handler.handleRequest(input, context); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + // Environment variable values should be used + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("EnvNamespace"); + assertThat(rootNode.has("Service")).isTrue(); + assertThat(rootNode.get("Service").asText()).isEqualTo("EnvService"); + } + + @Test + void shouldUseDefaultsWhenNoConfiguration() throws Exception { + // Given + MetricsBuilder.builder() + .withNamespace("TestNamespace") + .build(); + + RequestHandler<Map<String, Object>, String> handler = new HandlerWithDefaultMetricsAnnotation(); + Context context = new TestLambdaContext(); + Map<String, Object> input = new HashMap<>(); + + // When + handler.handleRequest(input, context); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + // Default values should be used + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("TestNamespace"); + // Service dimension should not be present when service is undefined + assertThat(rootNode.has("Service")).isFalse(); + } + + private static final class HandlerWithMetricsAnnotation implements RequestHandler<Map<String, Object>, String> { + @Override + @FlushMetrics(namespace = "AnnotationNamespace", service = "AnnotationService") + public String handleRequest(Map<String, Object> input, Context context) { + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + return "OK"; + } + } + + private static final class HandlerWithDefaultMetricsAnnotation + implements RequestHandler<Map<String, Object>, String> { + @Override + @FlushMetrics + public String handleRequest(Map<String, Object> input, Context context) { + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + return "OK"; + } + } + +} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsBuilderTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsBuilderTest.java new file mode 100644 index 000000000..12ac46e43 --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsBuilderTest.java @@ -0,0 +1,192 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.metrics.internal.RequestScopedMetricsProxy; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import software.amazon.lambda.powertools.metrics.provider.MetricsProvider; +import software.amazon.lambda.powertools.metrics.testutils.TestMetricsProvider; + +class MetricsBuilderTest { + + private static final PrintStream STANDARD_OUT = System.out; + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @BeforeEach + void setUp() { + System.setOut(new PrintStream(outputStreamCaptor)); + } + + @AfterEach + void tearDown() throws Exception { + System.setOut(STANDARD_OUT); + + // Reset the singleton state between tests + java.lang.reflect.Field field = MetricsFactory.class.getDeclaredField("metricsProxy"); + field.setAccessible(true); + field.set(null, null); + + field = MetricsFactory.class.getDeclaredField("provider"); + field.setAccessible(true); + field.set(null, new software.amazon.lambda.powertools.metrics.provider.EmfMetricsProvider()); + } + + @Test + void shouldBuildWithCustomNamespace() throws Exception { + // When + Metrics metrics = MetricsBuilder.builder() + .withNamespace("CustomNamespace") + .build(); + + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("CustomNamespace"); + } + + @Test + void shouldBuildWithCustomService() throws Exception { + // When + Metrics metrics = MetricsBuilder.builder() + .withService("CustomService") + .withNamespace("TestNamespace") + .build(); + + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("Service")).isTrue(); + assertThat(rootNode.get("Service").asText()).isEqualTo("CustomService"); + } + + @Test + void shouldBuildWithRaiseOnEmptyMetrics() { + // When + Metrics metrics = MetricsBuilder.builder() + .withRaiseOnEmptyMetrics(true) + .withNamespace("TestNamespace") + .build(); + + // Then + assertThat(metrics).isNotNull(); + assertThatThrownBy(metrics::flush) + .isInstanceOf(IllegalStateException.class) + .hasMessage("No metrics were emitted"); + } + + @Test + void shouldBuildWithDefaultDimension() throws Exception { + // When + Metrics metrics = MetricsBuilder.builder() + .withDefaultDimension("Environment", "Test") + .withNamespace("TestNamespace") + .build(); + + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("Environment")).isTrue(); + assertThat(rootNode.get("Environment").asText()).isEqualTo("Test"); + } + + @Test + void shouldBuildWithMultipleDefaultDimensions() throws Exception { + // When + Metrics metrics = MetricsBuilder.builder() + .withDefaultDimensions(DimensionSet.of("Environment", "Test", "Region", "us-west-2")) + .withNamespace("TestNamespace") + .build(); + + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("Environment")).isTrue(); + assertThat(rootNode.get("Environment").asText()).isEqualTo("Test"); + assertThat(rootNode.has("Region")).isTrue(); + assertThat(rootNode.get("Region").asText()).isEqualTo("us-west-2"); + } + + @Test + void shouldBuildWithCustomMetricsProvider() throws Exception { + // Given + MetricsProvider testProvider = new TestMetricsProvider(); + + // When + Metrics metrics = MetricsBuilder.builder() + .withMetricsProvider(testProvider) + .build(); + + // Then + assertThat(metrics) + .isInstanceOf(RequestScopedMetricsProxy.class); + + java.lang.reflect.Field providerField = metrics.getClass().getDeclaredField("provider"); + providerField.setAccessible(true); + MetricsProvider actualProvider = (MetricsProvider) providerField.get(metrics); + assertThat(actualProvider).isSameAs(testProvider); + } + + @Test + void shouldOverrideServiceWithDefaultDimensions() throws Exception { + // When + Metrics metrics = MetricsBuilder.builder() + .withService("OriginalService") + .withDefaultDimensions(DimensionSet.of("Service", "OverriddenService")) + .withNamespace("TestNamespace") + .build(); + + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("Service")).isTrue(); + assertThat(rootNode.get("Service").asText()).isEqualTo("OverriddenService"); + } +} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsFactoryTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsFactoryTest.java new file mode 100644 index 000000000..c9b183a1a --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsFactoryTest.java @@ -0,0 +1,224 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Method; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.common.internal.LambdaConstants; +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.metrics.internal.RequestScopedMetricsProxy; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import software.amazon.lambda.powertools.metrics.provider.MetricsProvider; +import software.amazon.lambda.powertools.metrics.testutils.TestMetricsProvider; + +class MetricsFactoryTest { + + private static final String TEST_NAMESPACE = "TestNamespace"; + private static final String TEST_SERVICE = "TestService"; + + private static final PrintStream STANDARD_OUT = System.out; + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @BeforeEach + void setUp() throws Exception { + System.setOut(new PrintStream(outputStreamCaptor)); + + // Reset LambdaHandlerProcessor's serviceName + Method resetServiceName = LambdaHandlerProcessor.class.getDeclaredMethod("resetServiceName"); + resetServiceName.setAccessible(true); + resetServiceName.invoke(null); + + // Reset isColdStart + java.lang.reflect.Field coldStartField = LambdaHandlerProcessor.class.getDeclaredField("isColdStart"); + coldStartField.setAccessible(true); + coldStartField.set(null, null); + } + + @AfterEach + void tearDown() throws Exception { + System.setOut(STANDARD_OUT); + System.clearProperty(LambdaConstants.XRAY_TRACE_HEADER); + + // Reset the singleton state between tests + java.lang.reflect.Field field = MetricsFactory.class.getDeclaredField("metricsProxy"); + field.setAccessible(true); + field.set(null, null); + + field = MetricsFactory.class.getDeclaredField("provider"); + field.setAccessible(true); + field.set(null, new software.amazon.lambda.powertools.metrics.provider.EmfMetricsProvider()); + } + + @Test + void shouldGetMetricsInstance() { + // When + Metrics metrics = MetricsFactory.getMetricsInstance(); + + // Then + assertThat(metrics).isNotNull(); + } + + @Test + void shouldReturnSameInstanceOnMultipleCalls() { + // When + Metrics firstInstance = MetricsFactory.getMetricsInstance(); + Metrics secondInstance = MetricsFactory.getMetricsInstance(); + + // Then + assertThat(firstInstance).isSameAs(secondInstance); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_METRICS_NAMESPACE", value = TEST_NAMESPACE) + void shouldUseNamespaceFromEnvironmentVariable() throws Exception { + // When + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo(TEST_NAMESPACE); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = TEST_SERVICE) + void shouldUseServiceNameFromEnvironmentVariable() throws Exception { + // When + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.setNamespace("TestNamespace"); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("Service")).isTrue(); + assertThat(rootNode.get("Service").asText()).isEqualTo(TEST_SERVICE); + } + + @Test + void shouldSetCustomMetricsProvider() throws Exception { + // Given + MetricsProvider testProvider = new TestMetricsProvider(); + + // When + MetricsFactory.setMetricsProvider(testProvider); + Metrics metrics = MetricsFactory.getMetricsInstance(); + + // Then + assertThat(metrics) + .isInstanceOf(RequestScopedMetricsProxy.class); + + java.lang.reflect.Field providerField = metrics.getClass().getDeclaredField("provider"); + providerField.setAccessible(true); + MetricsProvider actualProvider = (MetricsProvider) providerField.get(metrics); + assertThat(actualProvider).isSameAs(testProvider); + } + + @Test + void shouldThrowExceptionWhenSettingNullProvider() { + // When/Then + assertThatThrownBy(() -> MetricsFactory.setMetricsProvider(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Metrics provider cannot be null"); + } + + @Test + void shouldNotSetServiceDimensionWhenServiceUndefined() throws Exception { + // Given - no POWERTOOLS_SERVICE_NAME set, so it will use the default undefined value + + // When + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.setNamespace("TestNamespace"); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + // Service dimension should not be present + assertThat(rootNode.has("Service")).isFalse(); + } + + @Test + void shouldIsolateMetricsByTraceId() throws Exception { + // GIVEN + Metrics metrics = MetricsFactory.getMetricsInstance(); + + // WHEN - Simulate Lambda invocation 1 with trace ID 1 + System.setProperty(LambdaConstants.XRAY_TRACE_HEADER, "Root=1-trace-id-1"); + metrics.setNamespace("TestNamespace"); + metrics.addDimension("userId", "user123"); + metrics.addMetric("ProcessedOrder", 1, MetricUnit.COUNT); + metrics.flush(); + + // WHEN - Simulate Lambda invocation 2 with trace ID 2 + System.setProperty(LambdaConstants.XRAY_TRACE_HEADER, "Root=1-trace-id-2"); + metrics.setNamespace("TestNamespace"); + metrics.addDimension("userId", "user456"); + metrics.addMetric("ProcessedOrder", 1, MetricUnit.COUNT); + metrics.flush(); + + // THEN - Verify each invocation has isolated metrics + String emfOutput = outputStreamCaptor.toString().trim(); + String[] jsonLines = emfOutput.split("\n"); + assertThat(jsonLines).hasSize(2); + + JsonNode output1 = objectMapper.readTree(jsonLines[0]); + JsonNode output2 = objectMapper.readTree(jsonLines[1]); + + assertThat(output1.get("userId").asText()).isEqualTo("user123"); + assertThat(output2.get("userId").asText()).isEqualTo("user456"); + } + + @Test + void shouldUseDefaultKeyWhenNoTraceId() throws Exception { + // GIVEN - No trace ID set + System.clearProperty(LambdaConstants.XRAY_TRACE_HEADER); + + // WHEN + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.setNamespace("TestNamespace"); + metrics.addMetric("TestMetric", 1, MetricUnit.COUNT); + metrics.flush(); + + // THEN - Should work without X-Ray trace ID + String emfOutput = outputStreamCaptor.toString().trim(); + assertThat(emfOutput).isNotEmpty(); + + JsonNode rootNode = objectMapper.readTree(emfOutput); + assertThat(rootNode.get("TestMetric").asDouble()).isEqualTo(1.0); + } +} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsLoggerTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsLoggerTest.java deleted file mode 100644 index 449f12815..000000000 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsLoggerTest.java +++ /dev/null @@ -1,141 +0,0 @@ -package software.amazon.lambda.powertools.metrics; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.Map; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; -import software.amazon.cloudwatchlogs.emf.config.SystemWrapper; -import software.amazon.cloudwatchlogs.emf.model.DimensionSet; -import software.amazon.cloudwatchlogs.emf.model.Unit; - -import static java.util.Collections.*; -import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mockStatic; -import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv; - -class MetricsLoggerTest { - - private final ByteArrayOutputStream out = new ByteArrayOutputStream(); - private final PrintStream originalOut = System.out; - private final ObjectMapper mapper = new ObjectMapper(); - - @BeforeEach - void setUp() { - System.setOut(new PrintStream(out)); - } - - @AfterEach - void tearDown() { - System.setOut(originalOut); - } - - @BeforeAll - static void beforeAll() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - } - } - - @Test - void singleMetricsCaptureUtilityWithDefaultDimension() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class); - MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""); - - MetricsUtils.defaultDimensions(DimensionSet.of("Service", "Booking")); - - MetricsUtils.withSingleMetric("Metric1", 1, Unit.COUNT, "test", - metricsLogger -> {}); - - assertThat(out.toString()) - .satisfies(s -> { - Map<String, Object> logAsJson = readAsJson(s); - - assertThat(logAsJson) - .containsEntry("Metric1", 1.0) - .containsEntry("Service", "Booking") - .containsKey("_aws") - .containsEntry("xray_trace_id", "1-5759e988-bd862e3fe1be46a994272793"); - }); - } - } - - @Test - void singleMetricsCaptureUtility() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class); - MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""); - - MetricsUtils.withSingleMetric("Metric1", 1, Unit.COUNT, "test", - metricsLogger -> metricsLogger.setDimensions(DimensionSet.of("Dimension1", "Value1"))); - - assertThat(out.toString()) - .satisfies(s -> { - Map<String, Object> logAsJson = readAsJson(s); - - assertThat(logAsJson) - .containsEntry("Metric1", 1.0) - .containsEntry("Dimension1", "Value1") - .containsKey("_aws") - .containsEntry("xray_trace_id", "1-5759e988-bd862e3fe1be46a994272793"); - }); - } - } - - @Test - void singleMetricsCaptureUtilityWithDefaultNameSpace() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class); - MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - mocked.when(() -> SystemWrapper.getenv("POWERTOOLS_METRICS_NAMESPACE")).thenReturn("GlobalName"); - internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""); - - MetricsUtils.withSingleMetric("Metric1", 1, Unit.COUNT, - metricsLogger -> metricsLogger.setDimensions(DimensionSet.of("Dimension1", "Value1"))); - - assertThat(out.toString()) - .satisfies(s -> { - Map<String, Object> logAsJson = readAsJson(s); - - assertThat(logAsJson) - .containsEntry("Metric1", 1.0) - .containsEntry("Dimension1", "Value1") - .containsKey("_aws") - .containsEntry("xray_trace_id", "1-5759e988-bd862e3fe1be46a994272793"); - - Map<String, Object> aws = (Map<String, Object>) logAsJson.get("_aws"); - - assertThat(aws.get("CloudWatchMetrics")) - .asString() - .contains("Namespace=GlobalName"); - }); - } - } - - @Test - void shouldThrowExceptionWhenDefaultDimensionIsNull() { - assertThatNullPointerException() - .isThrownBy(() -> MetricsUtils.defaultDimensionSet(null)) - .withMessage("Null dimension set not allowed"); - } - - private Map<String, Object> readAsJson(String s) { - try { - return mapper.readValue(s, Map.class); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } - return emptyMap(); - } -} \ No newline at end of file diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/RequestHandlerTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/RequestHandlerTest.java new file mode 100644 index 000000000..fcd677c02 --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/RequestHandlerTest.java @@ -0,0 +1,126 @@ +package software.amazon.lambda.powertools.metrics; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.SetEnvironmentVariable; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; + +@ExtendWith(MockitoExtension.class) +class RequestHandlerTest { + + // For developer convenience, no exceptions should be thrown when using a plain Lambda Context mock + @Mock + Context lambdaContext; + + private static final PrintStream STDOUT = System.out; + private ByteArrayOutputStream outputStreamCaptor; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @BeforeEach + void setUp() throws Exception { + outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + + // Reset LambdaHandlerProcessor's serviceName + Method resetServiceName = LambdaHandlerProcessor.class.getDeclaredMethod("resetServiceName"); + resetServiceName.setAccessible(true); + resetServiceName.invoke(null); + + // Reset isColdStart + java.lang.reflect.Field coldStartField = LambdaHandlerProcessor.class.getDeclaredField("isColdStart"); + coldStartField.setAccessible(true); + coldStartField.set(null, null); + } + + @AfterEach + void tearDown() throws Exception { + System.setOut(STDOUT); + + // Reset the singleton state between tests + java.lang.reflect.Field field = MetricsFactory.class.getDeclaredField("metricsProxy"); + field.setAccessible(true); + field.set(null, null); + + field = MetricsFactory.class.getDeclaredField("provider"); + field.setAccessible(true); + field.set(null, new software.amazon.lambda.powertools.metrics.provider.EmfMetricsProvider()); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_METRICS_NAMESPACE", value = "TestNamespace") + @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "TestService") + void shouldCaptureMetricsFromAnnotatedHandler() throws Exception { + // Given + RequestHandler<Map<String, Object>, String> handler = new HandlerWithMetricsAnnotation(); + Map<String, Object> input = new HashMap<>(); + + // When + handler.handleRequest(input, lambdaContext); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + String[] jsonLines = emfOutput.split("\n"); + + // First JSON object should be the cold start metric + JsonNode coldStartNode = objectMapper.readTree(jsonLines[0]); + assertThat(coldStartNode.has("ColdStart")).isTrue(); + assertThat(coldStartNode.get("ColdStart").asDouble()).isEqualTo(1.0); + assertThat(coldStartNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("TestNamespace"); + assertThat(coldStartNode.has("Service")).isTrue(); + assertThat(coldStartNode.get("Service").asText()).isEqualTo("TestService"); + + // Second JSON object should be the regular metric + JsonNode regularNode = objectMapper.readTree(jsonLines[1]); + assertThat(regularNode.has("test-metric")).isTrue(); + assertThat(regularNode.get("test-metric").asDouble()).isEqualTo(100.0); + assertThat(regularNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("TestNamespace"); + assertThat(regularNode.has("Service")).isTrue(); + assertThat(regularNode.get("Service").asText()).isEqualTo("TestService"); + } + + @SetEnvironmentVariable(key = "POWERTOOLS_METRICS_DISABLED", value = "true") + @Test + void shouldNotCaptureMetricsWhenDisabled() { + // Given + RequestHandler<Map<String, Object>, String> handler = new HandlerWithMetricsAnnotation(); + Map<String, Object> input = new HashMap<>(); + + // When + handler.handleRequest(input, lambdaContext); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + assertThat(emfOutput).isEmpty(); + } + + static class HandlerWithMetricsAnnotation implements RequestHandler<Map<String, Object>, String> { + @Override + @FlushMetrics(captureColdStart = true) + public String handleRequest(Map<String, Object> input, Context context) { + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + return "OK"; + } + } +} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsColdStartEnabledHandler.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsColdStartEnabledHandler.java deleted file mode 100644 index a722bd689..000000000 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsColdStartEnabledHandler.java +++ /dev/null @@ -1,21 +0,0 @@ -package software.amazon.lambda.powertools.metrics.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.cloudwatchlogs.emf.model.Unit; -import software.amazon.lambda.powertools.metrics.Metrics; - -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; - -public class PowertoolsMetricsColdStartEnabledHandler implements RequestHandler<Object, Object> { - - @Override - @Metrics(namespace = "ExampleApplication", service = "booking", captureColdStart = true) - public Object handleRequest(Object input, Context context) { - MetricsLogger metricsLogger = metricsLogger(); - metricsLogger.putMetric("Metric1", 1, Unit.BYTES); - - return null; - } -} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledDefaultDimensionHandler.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledDefaultDimensionHandler.java deleted file mode 100644 index f66269546..000000000 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledDefaultDimensionHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -package software.amazon.lambda.powertools.metrics.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.cloudwatchlogs.emf.model.DimensionSet; -import software.amazon.cloudwatchlogs.emf.model.Unit; -import software.amazon.lambda.powertools.metrics.Metrics; - -import static software.amazon.lambda.powertools.metrics.MetricsUtils.defaultDimensions; -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; -import static software.amazon.lambda.powertools.metrics.MetricsUtils.withSingleMetric; - -public class PowertoolsMetricsEnabledDefaultDimensionHandler implements RequestHandler<Object, Object> { - - static { - defaultDimensions(DimensionSet.of("CustomDimension", "booking")); - } - - @Override - @Metrics(namespace = "ExampleApplication", service = "booking") - public Object handleRequest(Object input, Context context) { - MetricsLogger metricsLogger = metricsLogger(); - metricsLogger.putMetric("Metric1", 1, Unit.BYTES); - - withSingleMetric("Metric2", 1, Unit.COUNT, log -> {}); - - return null; - } -} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledDefaultNoDimensionHandler.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledDefaultNoDimensionHandler.java deleted file mode 100644 index 761362f43..000000000 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledDefaultNoDimensionHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package software.amazon.lambda.powertools.metrics.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.cloudwatchlogs.emf.model.Unit; -import software.amazon.lambda.powertools.metrics.Metrics; -import software.amazon.lambda.powertools.metrics.MetricsUtils; - -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; -import static software.amazon.lambda.powertools.metrics.MetricsUtils.withSingleMetric; - -public class PowertoolsMetricsEnabledDefaultNoDimensionHandler implements RequestHandler<Object, Object> { - - static { - MetricsUtils.defaultDimensions(); - } - - @Override - @Metrics(namespace = "ExampleApplication", service = "booking") - public Object handleRequest(Object input, Context context) { - MetricsLogger metricsLogger = metricsLogger(); - metricsLogger.putMetric("Metric1", 1, Unit.BYTES); - - withSingleMetric("Metric2", 1, Unit.COUNT, log -> {}); - - return null; - } -} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledHandler.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledHandler.java deleted file mode 100644 index 160109787..000000000 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledHandler.java +++ /dev/null @@ -1,27 +0,0 @@ -package software.amazon.lambda.powertools.metrics.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.cloudwatchlogs.emf.model.DimensionSet; -import software.amazon.cloudwatchlogs.emf.model.Unit; -import software.amazon.lambda.powertools.metrics.Metrics; - -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; -import static software.amazon.lambda.powertools.metrics.MetricsUtils.withSingleMetric; - -public class PowertoolsMetricsEnabledHandler implements RequestHandler<Object, Object> { - - @Override - @Metrics(namespace = "ExampleApplication", service = "booking") - public Object handleRequest(Object input, Context context) { - MetricsLogger metricsLogger = metricsLogger(); - metricsLogger.putMetric("Metric1", 1, Unit.BYTES); - - - withSingleMetric("Metric2", 1, Unit.COUNT, - log -> log.setDimensions(DimensionSet.of("Dimension1", "Value1"))); - - return null; - } -} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledStreamHandler.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledStreamHandler.java deleted file mode 100644 index 2eb877dc3..000000000 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledStreamHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -package software.amazon.lambda.powertools.metrics.handlers; - -import java.io.InputStream; -import java.io.OutputStream; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.cloudwatchlogs.emf.model.Unit; -import software.amazon.lambda.powertools.metrics.Metrics; - -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; - -public class PowertoolsMetricsEnabledStreamHandler implements RequestStreamHandler { - - @Override - @Metrics(namespace = "ExampleApplication", service = "booking") - public void handleRequest(InputStream input, OutputStream output, Context context) { - MetricsLogger metricsLogger = metricsLogger(); - metricsLogger.putMetric("Metric1", 1, Unit.BYTES); - } -} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsExceptionWhenNoMetricsHandler.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsExceptionWhenNoMetricsHandler.java deleted file mode 100644 index 8ada044ee..000000000 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsExceptionWhenNoMetricsHandler.java +++ /dev/null @@ -1,20 +0,0 @@ -package software.amazon.lambda.powertools.metrics.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.lambda.powertools.metrics.Metrics; - -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; - -public class PowertoolsMetricsExceptionWhenNoMetricsHandler implements RequestHandler<Object, Object> { - - @Override - @Metrics(namespace = "ExampleApplication", service = "booking", raiseOnEmptyMetrics = true) - public Object handleRequest(Object input, Context context) { - MetricsLogger metricsLogger = metricsLogger(); - metricsLogger.putMetadata("MetaData", "MetaDataValue"); - - return null; - } -} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsNoDimensionsHandler.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsNoDimensionsHandler.java deleted file mode 100644 index 1c4cc3f77..000000000 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsNoDimensionsHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -package software.amazon.lambda.powertools.metrics.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.cloudwatchlogs.emf.model.DimensionSet; -import software.amazon.lambda.powertools.metrics.Metrics; - -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; - -public class PowertoolsMetricsNoDimensionsHandler implements RequestHandler<Object, Object> { - - @Override - @Metrics(namespace = "ExampleApplication", service = "booking") - public Object handleRequest(Object input, Context context) { - MetricsLogger metricsLogger = metricsLogger(); - metricsLogger.putMetric("CoolMetric", 1); - metricsLogger.setDimensions(new DimensionSet()); - - return null; - } -} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsNoExceptionWhenNoMetricsHandler.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsNoExceptionWhenNoMetricsHandler.java deleted file mode 100644 index 3639542f8..000000000 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsNoExceptionWhenNoMetricsHandler.java +++ /dev/null @@ -1,20 +0,0 @@ -package software.amazon.lambda.powertools.metrics.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.lambda.powertools.metrics.Metrics; - -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; - -public class PowertoolsMetricsNoExceptionWhenNoMetricsHandler implements RequestHandler<Object, Object> { - - @Override - @Metrics(namespace = "ExampleApplication", service = "booking") - public Object handleRequest(Object input, Context context) { - MetricsLogger metricsLogger = metricsLogger(); - metricsLogger.putMetadata("MetaData", "MetaDataValue"); - - return null; - } -} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsTooManyDimensionsHandler.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsTooManyDimensionsHandler.java deleted file mode 100644 index ccec863f9..000000000 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsTooManyDimensionsHandler.java +++ /dev/null @@ -1,26 +0,0 @@ -package software.amazon.lambda.powertools.metrics.handlers; - -import java.util.stream.IntStream; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.cloudwatchlogs.emf.model.DimensionSet; -import software.amazon.lambda.powertools.metrics.Metrics; - -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; - -public class PowertoolsMetricsTooManyDimensionsHandler implements RequestHandler<Object, Object> { - - @Override - @Metrics - public Object handleRequest(Object input, Context context) { - MetricsLogger metricsLogger = metricsLogger(); - - metricsLogger.setDimensions(IntStream.range(1, 15) - .mapToObj(value -> DimensionSet.of("Dimension" + value, "DimensionValue" + value)) - .toArray(DimensionSet[]::new)); - - return null; - } -} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsWithExceptionInHandler.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsWithExceptionInHandler.java deleted file mode 100644 index db75d9f95..000000000 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsWithExceptionInHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -package software.amazon.lambda.powertools.metrics.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; -import software.amazon.lambda.powertools.metrics.Metrics; - -import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; - -public class PowertoolsMetricsWithExceptionInHandler implements RequestHandler<Object, Object> { - - @Override - @Metrics(namespace = "ExampleApplication", service = "booking") - public Object handleRequest(Object input, Context context) { - MetricsLogger metricsLogger = metricsLogger(); - metricsLogger.putMetric("CoolMetric", 1); - throw new IllegalStateException("Whoops, unexpected exception"); - } -} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/EmfMetricsLoggerTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/EmfMetricsLoggerTest.java new file mode 100644 index 000000000..5e597e835 --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/EmfMetricsLoggerTest.java @@ -0,0 +1,739 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.time.Instant; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junitpioneer.jupiter.SetEnvironmentVariable; + +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider; +import software.amazon.cloudwatchlogs.emf.model.MetricsContext; +import software.amazon.cloudwatchlogs.emf.model.Unit; +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricResolution; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; + +class EmfMetricsLoggerTest { + + private Metrics metrics; + private final ObjectMapper objectMapper = new ObjectMapper(); + private static final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + + @BeforeEach + void setUp() throws Exception { + // Reset LambdaHandlerProcessor's serviceName + Method resetServiceName = LambdaHandlerProcessor.class.getDeclaredMethod("resetServiceName"); + resetServiceName.setAccessible(true); + resetServiceName.invoke(null); + + // Reset isColdStart + java.lang.reflect.Field coldStartField = LambdaHandlerProcessor.class.getDeclaredField("isColdStart"); + coldStartField.setAccessible(true); + coldStartField.set(null, null); + + metrics = new EmfMetricsLogger(new EnvironmentProvider(), new MetricsContext()); + metrics.setNamespace("TestNamespace"); + System.setOut(new PrintStream(outputStreamCaptor)); + } + + @AfterEach + void tearDown() { + System.setOut(standardOut); + } + + @ParameterizedTest + @MethodSource("unitConversionTestCases") + void shouldConvertMetricUnits(MetricUnit inputUnit, Unit expectedUnit) throws Exception { + // Given + // We access using reflection here for simplicity (even though this is not best practice) + Method convertUnitMethod = EmfMetricsLogger.class.getDeclaredMethod("convertUnit", MetricUnit.class); + convertUnitMethod.setAccessible(true); + + // When + Unit actualUnit = (Unit) convertUnitMethod.invoke(metrics, inputUnit); + + // Then + assertThat(actualUnit).isEqualTo(expectedUnit); + } + + private static Stream<Arguments> unitConversionTestCases() { + return Stream.of( + Arguments.of(MetricUnit.SECONDS, Unit.SECONDS), + Arguments.of(MetricUnit.MICROSECONDS, Unit.MICROSECONDS), + Arguments.of(MetricUnit.MILLISECONDS, Unit.MILLISECONDS), + Arguments.of(MetricUnit.BYTES, Unit.BYTES), + Arguments.of(MetricUnit.KILOBYTES, Unit.KILOBYTES), + Arguments.of(MetricUnit.MEGABYTES, Unit.MEGABYTES), + Arguments.of(MetricUnit.GIGABYTES, Unit.GIGABYTES), + Arguments.of(MetricUnit.TERABYTES, Unit.TERABYTES), + Arguments.of(MetricUnit.BITS, Unit.BITS), + Arguments.of(MetricUnit.KILOBITS, Unit.KILOBITS), + Arguments.of(MetricUnit.MEGABITS, Unit.MEGABITS), + Arguments.of(MetricUnit.GIGABITS, Unit.GIGABITS), + Arguments.of(MetricUnit.TERABITS, Unit.TERABITS), + Arguments.of(MetricUnit.PERCENT, Unit.PERCENT), + Arguments.of(MetricUnit.COUNT, Unit.COUNT), + Arguments.of(MetricUnit.BYTES_SECOND, Unit.BYTES_SECOND), + Arguments.of(MetricUnit.KILOBYTES_SECOND, Unit.KILOBYTES_SECOND), + Arguments.of(MetricUnit.MEGABYTES_SECOND, Unit.MEGABYTES_SECOND), + Arguments.of(MetricUnit.GIGABYTES_SECOND, Unit.GIGABYTES_SECOND), + Arguments.of(MetricUnit.TERABYTES_SECOND, Unit.TERABYTES_SECOND), + Arguments.of(MetricUnit.BITS_SECOND, Unit.BITS_SECOND), + Arguments.of(MetricUnit.KILOBITS_SECOND, Unit.KILOBITS_SECOND), + Arguments.of(MetricUnit.MEGABITS_SECOND, Unit.MEGABITS_SECOND), + Arguments.of(MetricUnit.GIGABITS_SECOND, Unit.GIGABITS_SECOND), + Arguments.of(MetricUnit.TERABITS_SECOND, Unit.TERABITS_SECOND), + Arguments.of(MetricUnit.COUNT_SECOND, Unit.COUNT_SECOND), + Arguments.of(MetricUnit.NONE, Unit.NONE)); + } + + @Test + void shouldCreateMetricWithDefaultResolution() throws Exception { + // When + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("_aws")).isTrue(); + assertThat(rootNode.get("test-metric").asDouble()).isEqualTo(100); + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Metrics").get(0).get("Unit").asText()) + .isEqualTo("Count"); + } + + @Test + void shouldCreateMetricWithHighResolution() throws Exception { + // When + metrics.addMetric("test-metric", 100, MetricUnit.COUNT, MetricResolution.HIGH); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("_aws")).isTrue(); + assertThat(rootNode.get("test-metric").asDouble()).isEqualTo(100); + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Metrics").get(0).get("Unit").asText()) + .isEqualTo("Count"); + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Metrics").get(0).get("StorageResolution") + .asInt()).isEqualTo(1); + } + + @Test + void shouldAddDimension() throws Exception { + // When + metrics.clearDefaultDimensions(); // Clear default Service dimension first for easier assertions + metrics.addDimension("CustomDimension", "CustomValue"); + metrics.addMetric("test-metric", 100); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("CustomDimension")).isTrue(); + assertThat(rootNode.get("CustomDimension").asText()).isEqualTo("CustomValue"); + + // Check that the dimension is in the CloudWatchMetrics section + JsonNode dimensions = rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Dimensions").get(0); + boolean hasDimension = false; + for (JsonNode dimension : dimensions) { + if ("CustomDimension".equals(dimension.asText())) { + hasDimension = true; + break; + } + } + assertThat(hasDimension).isTrue(); + } + + @Test + void shouldSetCustomTimestamp() throws Exception { + // Given + Instant customTimestamp = Instant.now(); + + // When + metrics.setTimestamp(customTimestamp); + metrics.addMetric("test-metric", 100); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("_aws")).isTrue(); + assertThat(rootNode.get("_aws").has("Timestamp")).isTrue(); + assertThat(rootNode.get("_aws").get("Timestamp").asLong()).isEqualTo(customTimestamp.toEpochMilli()); + } + + @Test + void shouldAddDimensionSet() throws Exception { + // Given + DimensionSet dimensionSet = DimensionSet.of("Dim1", "Value1", "Dim2", "Value2"); + + // When + metrics.clearDefaultDimensions(); // Clear default Service dimension first for easier assertions + metrics.addDimension(dimensionSet); + metrics.addMetric("test-metric", 100); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("Dim1")).isTrue(); + assertThat(rootNode.get("Dim1").asText()).isEqualTo("Value1"); + assertThat(rootNode.has("Dim2")).isTrue(); + assertThat(rootNode.get("Dim2").asText()).isEqualTo("Value2"); + + // Check that the dimensions are in the CloudWatchMetrics section + JsonNode dimensions = rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Dimensions").get(0); + boolean hasDim1 = false; + boolean hasDim2 = false; + for (JsonNode dimension : dimensions) { + String dimName = dimension.asText(); + if ("Dim1".equals(dimName)) { + hasDim1 = true; + } else if ("Dim2".equals(dimName)) { + hasDim2 = true; + } + } + assertThat(hasDim1).isTrue(); + assertThat(hasDim2).isTrue(); + } + + @Test + void shouldThrowExceptionWhenDimensionSetIsNull() { + // When/Then + assertThatThrownBy(() -> metrics.addDimension(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("DimensionSet cannot be null"); + } + + @Test + void shouldAddMetadata() throws Exception { + // When + metrics.addMetadata("CustomMetadata", "MetadataValue"); + metrics.addMetric("test-metric", 100); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + // The metadata is added to the _aws section in the EMF output + assertThat(rootNode.has("CustomMetadata")).isTrue(); + assertThat(rootNode.get("CustomMetadata").asText()).isEqualTo("MetadataValue"); + } + + @Test + void shouldClearMetadataAfterFlush() throws Exception { + // Given - Add metadata and flush first time + metrics.addMetadata("RequestId", "req-123"); + metrics.addMetadata("UserAgent", "test-agent"); + metrics.addMetric("FirstMetric", 1.0); + metrics.flush(); + + // Capture first flush output and reset for second flush + String firstFlushOutput = outputStreamCaptor.toString().trim(); + outputStreamCaptor.reset(); + + // When - Add another metric and flush again using the SAME metrics instance + metrics.addMetric("SecondMetric", 2.0); + metrics.flush(); + + // Then - Verify first flush had metadata + JsonNode firstRootNode = objectMapper.readTree(firstFlushOutput); + assertThat(firstRootNode.has("RequestId")).isTrue(); + assertThat(firstRootNode.get("RequestId").asText()).isEqualTo("req-123"); + assertThat(firstRootNode.has("UserAgent")).isTrue(); + assertThat(firstRootNode.get("UserAgent").asText()).isEqualTo("test-agent"); + assertThat(firstRootNode.has("FirstMetric")).isTrue(); + + // Verify second flush does NOT have metadata from first flush + // The EMF library automatically clears metadata after flush + String secondFlushOutput = outputStreamCaptor.toString().trim(); + JsonNode secondRootNode = objectMapper.readTree(secondFlushOutput); + + // Metadata should be cleared after first flush by the EMF library + assertThat(secondRootNode.has("RequestId")).isFalse(); + assertThat(secondRootNode.has("UserAgent")).isFalse(); + assertThat(secondRootNode.has("SecondMetric")).isTrue(); + } + + @Test + void shouldInheritMetadataInFlushMetricsMethod() throws Exception { + // Given - Add metadata to the main metrics instance + metrics.addMetadata("PersistentMetadata", "should-inherit"); + metrics.addMetadata("GlobalContext", "main-instance"); + + // When - Use flushMetrics to create a separate metrics context + metrics.flushMetrics(separateMetrics -> { + separateMetrics.addMetric("SeparateMetric", 1.0); + // Don't add any metadata to the separate instance + }); + + // Then - The separate metrics context SHOULD inherit metadata from main instance + String flushMetricsOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(flushMetricsOutput); + + // The separate metrics should have inherited metadata (this is expected behavior) + assertThat(rootNode.has("PersistentMetadata")).isTrue(); + assertThat(rootNode.get("PersistentMetadata").asText()).isEqualTo("should-inherit"); + assertThat(rootNode.has("GlobalContext")).isTrue(); + assertThat(rootNode.get("GlobalContext").asText()).isEqualTo("main-instance"); + assertThat(rootNode.has("SeparateMetric")).isTrue(); + } + + @Test + void shouldSetDefaultDimensions() throws Exception { + // Given + DimensionSet dimensionSet = DimensionSet.of("Service", "TestService", "Environment", "Test"); + + // When + metrics.setDefaultDimensions(dimensionSet); + metrics.addMetric("test-metric", 100); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("Service")).isTrue(); + assertThat(rootNode.get("Service").asText()).isEqualTo("TestService"); + assertThat(rootNode.has("Environment")).isTrue(); + assertThat(rootNode.get("Environment").asText()).isEqualTo("Test"); + } + + @Test + void shouldGetDefaultDimensions() { + // Given + DimensionSet dimensionSet = DimensionSet.of("Service", "TestService", "Environment", "Test"); + + // When + metrics.setDefaultDimensions(dimensionSet); + DimensionSet dimensions = metrics.getDefaultDimensions(); + + // Then + assertThat(dimensions.getDimensions()).containsEntry("Service", "TestService"); + assertThat(dimensions.getDimensions()).containsEntry("Environment", "Test"); + } + + @Test + void shouldThrowExceptionWhenDefaultDimensionSetIsNull() { + // When/Then + assertThatThrownBy(() -> metrics.setDefaultDimensions(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("DimensionSet cannot be null"); + } + + @Test + void shouldSetNamespace() throws Exception { + // When + metrics.setNamespace("CustomNamespace"); + metrics.addMetric("test-metric", 100); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("CustomNamespace"); + } + + @Test + void shouldRaiseExceptionOnEmptyMetrics() { + // When + metrics.setRaiseOnEmptyMetrics(true); + + // Then + assertThatThrownBy(() -> metrics.flush()) + .isInstanceOf(IllegalStateException.class) + .hasMessage("No metrics were emitted"); + } + + @Test + void shouldLogWarningOnEmptyMetrics() throws Exception { + // Given + File logFile = new File("target/metrics-test.log"); + + // When + // Flushing without adding metrics + metrics.flush(); + + // Then + // Read the log file and check for the warning + String logContent = Files.readString(logFile.toPath(), StandardCharsets.UTF_8); + assertThat(logContent).contains("No metrics were emitted"); + // No EMF output should be generated + assertThat(outputStreamCaptor.toString().trim()).isEmpty(); + } + + @Test + void shouldClearDefaultDimensions() throws Exception { + // Given + metrics.setDefaultDimensions(DimensionSet.of("Service", "TestService", "Environment", "Test")); + + // When + metrics.clearDefaultDimensions(); + metrics.addMetric("test-metric", 100); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("Service")).isFalse(); + assertThat(rootNode.has("Environment")).isFalse(); + } + + @Test + void shouldCaptureColdStartMetric() throws Exception { + // Given + Context testContext = new TestLambdaContext(); + + // When + metrics.captureColdStartMetric(testContext); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("ColdStart")).isTrue(); + assertThat(rootNode.get("ColdStart").asDouble()).isEqualTo(1.0); + assertThat(rootNode.has("function_request_id")).isTrue(); + assertThat(rootNode.get("function_request_id").asText()).isEqualTo(testContext.getAwsRequestId()); + } + + @Test + void shouldCaptureColdStartMetricWithDimensions() throws Exception { + // Given + DimensionSet dimensions = DimensionSet.of("CustomDim", "CustomValue"); + + // When + metrics.captureColdStartMetric(dimensions); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("ColdStart")).isTrue(); + assertThat(rootNode.get("ColdStart").asDouble()).isEqualTo(1.0); + assertThat(rootNode.has("CustomDim")).isTrue(); + assertThat(rootNode.get("CustomDim").asText()).isEqualTo("CustomValue"); + } + + @Test + void shouldCaptureColdStartMetricWithoutDimensions() throws Exception { + // When + metrics.captureColdStartMetric(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("ColdStart")).isTrue(); + assertThat(rootNode.get("ColdStart").asDouble()).isEqualTo(1.0); + } + + @Test + void shouldReuseNamespaceForColdStartMetric() throws Exception { + // Given + String customNamespace = "CustomNamespace"; + metrics.setNamespace(customNamespace); + + Context testContext = new TestLambdaContext(); + + DimensionSet dimensions = DimensionSet.of("CustomDim", "CustomValue"); + + // When + metrics.captureColdStartMetric(testContext, dimensions); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("ColdStart")).isTrue(); + assertThat(rootNode.get("ColdStart").asDouble()).isEqualTo(1.0); + assertThat(rootNode.has("CustomDim")).isTrue(); + assertThat(rootNode.get("CustomDim").asText()).isEqualTo("CustomValue"); + assertThat(rootNode.has("function_request_id")).isTrue(); + assertThat(rootNode.get("function_request_id").asText()).isEqualTo(testContext.getAwsRequestId()); + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo(customNamespace); + } + + @Test + void shouldFlushMetrics() throws Exception { + // Given + metrics.setNamespace("MainNamespace"); + metrics.setDefaultDimensions(DimensionSet.of("CustomDim", "CustomValue")); + metrics.addDimension(DimensionSet.of("CustomDim2", "CustomValue2")); + metrics.addMetadata("CustomMetadata", "MetadataValue"); + + // When + metrics.flushMetrics(m -> { + m.addMetric("metric-one", 200, MetricUnit.COUNT); + m.addMetric("metric-two", 100, MetricUnit.COUNT); + }); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("metric-one")).isTrue(); + assertThat(rootNode.get("metric-one").asDouble()).isEqualTo(200.0); + assertThat(rootNode.has("metric-two")).isTrue(); + assertThat(rootNode.get("metric-two").asDouble()).isEqualTo(100); + assertThat(rootNode.has("CustomDim")).isTrue(); + assertThat(rootNode.get("CustomDim").asText()).isEqualTo("CustomValue"); + assertThat(rootNode.get("CustomDim2")).isNull(); + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("MainNamespace"); + assertThat(rootNode.has("CustomMetadata")).isTrue(); + assertThat(rootNode.get("CustomMetadata").asText()).isEqualTo("MetadataValue"); + } + + @Test + void shouldFlushSingleMetric() throws Exception { + // Given + DimensionSet dimensions = DimensionSet.of("CustomDim", "CustomValue"); + + // When + metrics.flushSingleMetric("single-metric", 200, MetricUnit.COUNT, "SingleNamespace", dimensions); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("single-metric")).isTrue(); + assertThat(rootNode.get("single-metric").asDouble()).isEqualTo(200.0); + assertThat(rootNode.has("CustomDim")).isTrue(); + assertThat(rootNode.get("CustomDim").asText()).isEqualTo("CustomValue"); + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("SingleNamespace"); + } + + @Test + void shouldFlushSingleMetricWithoutDimensions() throws Exception { + // When + metrics.flushSingleMetric("single-metric", 200, MetricUnit.COUNT, "SingleNamespace"); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("single-metric")).isTrue(); + assertThat(rootNode.get("single-metric").asDouble()).isEqualTo(200.0); + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("SingleNamespace"); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_METRICS_DISABLED", value = "true") + void shouldNotFlushMetricsWhenDisabled() { + // When + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + metrics.flush(); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + assertThat(emfOutput).isEmpty(); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_METRICS_DISABLED", value = "true") + void shouldNotCaptureColdStartMetricWhenDisabled() { + // Given + Context testContext = new TestLambdaContext(); + + // When + metrics.captureColdStartMetric(testContext); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + assertThat(emfOutput).isEmpty(); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_METRICS_DISABLED", value = "true") + void shouldNotFlushSingleMetricWhenDisabled() { + // Given + DimensionSet dimensions = DimensionSet.of("CustomDim", "CustomValue"); + + // When + metrics.flushSingleMetric("single-metric", 200, MetricUnit.COUNT, "SingleNamespace", dimensions); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + assertThat(emfOutput).isEmpty(); + } + + @Test + void shouldClearCustomDimensionsAfterFlush() throws Exception { + // Given - Set up default dimensions that should persist + DimensionSet defaultDimensions = DimensionSet.of("Service", "TestService", "Environment", "Test"); + metrics.setDefaultDimensions(defaultDimensions); + + // First invocation - add custom dimensions and flush + DimensionSet customDimensions = DimensionSet.of("EXAMPLE_KEY", "EXAMPLE_VALUE"); + metrics.addDimension(customDimensions); + metrics.addMetric("SERL", 1.0); + metrics.flush(); + + // Capture first flush output + String firstFlushOutput = outputStreamCaptor.toString().trim(); + outputStreamCaptor.reset(); // Clear for second flush + + // Second invocation - should NOT have custom dimensions from first invocation + metrics.addMetric("Expected", 1.0); + metrics.flush(); + + // Then - Verify first flush had both default and custom dimensions + JsonNode firstRootNode = objectMapper.readTree(firstFlushOutput); + assertThat(firstRootNode.has("Service")).isTrue(); + assertThat(firstRootNode.get("Service").asText()).isEqualTo("TestService"); + assertThat(firstRootNode.has("Environment")).isTrue(); + assertThat(firstRootNode.get("Environment").asText()).isEqualTo("Test"); + assertThat(firstRootNode.has("EXAMPLE_KEY")).isTrue(); + assertThat(firstRootNode.get("EXAMPLE_KEY").asText()).isEqualTo("EXAMPLE_VALUE"); + assertThat(firstRootNode.has("SERL")).isTrue(); + + // Verify second flush has ONLY default dimensions (custom dimensions should be cleared) + String secondFlushOutput = outputStreamCaptor.toString().trim(); + JsonNode secondRootNode = objectMapper.readTree(secondFlushOutput); + + // Default dimensions should still be present + assertThat(secondRootNode.has("Service")).isTrue(); + assertThat(secondRootNode.get("Service").asText()).isEqualTo("TestService"); + assertThat(secondRootNode.has("Environment")).isTrue(); + assertThat(secondRootNode.get("Environment").asText()).isEqualTo("Test"); + + // Custom dimensions should be cleared (this is the failing assertion that demonstrates the bug) + assertThat(secondRootNode.has("EXAMPLE_KEY")).isFalse(); + assertThat(secondRootNode.has("Expected")).isTrue(); + + // Verify dimensions in CloudWatchMetrics section + JsonNode secondDimensions = secondRootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Dimensions").get(0); + boolean hasExampleKey = false; + boolean hasService = false; + boolean hasEnvironment = false; + + for (JsonNode dimension : secondDimensions) { + String dimName = dimension.asText(); + if ("EXAMPLE_KEY".equals(dimName)) { + hasExampleKey = true; + } else if ("Service".equals(dimName)) { + hasService = true; + } else if ("Environment".equals(dimName)) { + hasEnvironment = true; + } + } + + // Default dimensions should be in CloudWatchMetrics + assertThat(hasService).isTrue(); + assertThat(hasEnvironment).isTrue(); + // Custom dimension should NOT be in CloudWatchMetrics (this should fail initially) + assertThat(hasExampleKey).isFalse(); + } + + @Test + void shouldHandleEmptyCustomDimensionsGracefully() throws Exception { + // Given - Only default dimensions, no custom dimensions + metrics.setDefaultDimensions(DimensionSet.of("Service", "TestService")); + + // When - Flush without adding custom dimensions + metrics.addMetric("TestMetric", 1.0); + metrics.flush(); + outputStreamCaptor.reset(); + + // Second flush + metrics.addMetric("TestMetric2", 2.0); + metrics.flush(); + + // Then - Should work normally with only default dimensions + String output = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(output); + + assertThat(rootNode.has("Service")).isTrue(); + assertThat(rootNode.get("Service").asText()).isEqualTo("TestService"); + assertThat(rootNode.has("TestMetric2")).isTrue(); + } + + @Test + void shouldClearCustomDimensionsWhenNoDefaultDimensionsSet() throws Exception { + // Given - No default dimensions set + metrics.clearDefaultDimensions(); + + // When - Add custom dimensions and flush + metrics.addDimension("CustomDim", "CustomValue"); + metrics.addMetric("Metric1", 1.0); + metrics.flush(); + outputStreamCaptor.reset(); + + // Second flush without custom dimensions + metrics.addMetric("Metric2", 2.0); + metrics.flush(); + + // Then - Custom dimensions should be cleared + String output = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(output); + + assertThat(rootNode.has("CustomDim")).isFalse(); + assertThat(rootNode.has("Metric2")).isTrue(); + + // Verify no custom dimensions in CloudWatchMetrics section + JsonNode dimensionsArray = rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Dimensions"); + boolean hasCustomDim = false; + if (dimensionsArray != null && dimensionsArray.size() > 0) { + JsonNode dimensions = dimensionsArray.get(0); + if (dimensions != null) { + for (JsonNode dimension : dimensions) { + if ("CustomDim".equals(dimension.asText())) { + hasCustomDim = true; + break; + } + } + } + } + assertThat(hasCustomDim).isFalse(); + } +} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspectTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspectTest.java index 6c18d5d7a..119e094a9 100644 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspectTest.java +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspectTest.java @@ -1,413 +1,328 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.metrics.internal; -import java.io.ByteArrayInputStream; +import static org.assertj.core.api.Assertions.assertThat; + import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.io.PrintStream; +import java.lang.reflect.Method; +import java.util.HashMap; import java.util.Map; -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import software.amazon.cloudwatchlogs.emf.config.SystemWrapper; -import software.amazon.cloudwatchlogs.emf.model.DimensionSet; -import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; -import software.amazon.lambda.powertools.metrics.MetricsUtils; -import software.amazon.lambda.powertools.metrics.ValidationException; -import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsColdStartEnabledHandler; -import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsEnabledDefaultDimensionHandler; -import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsEnabledDefaultNoDimensionHandler; -import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsEnabledHandler; -import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsEnabledStreamHandler; -import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsExceptionWhenNoMetricsHandler; -import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsNoDimensionsHandler; -import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsNoExceptionWhenNoMetricsHandler; -import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsTooManyDimensionsHandler; -import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsWithExceptionInHandler; - -import static java.util.Collections.emptyMap; -import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; -import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv; - -public class LambdaMetricsAspectTest { - @Mock - private Context context; - - private final ByteArrayOutputStream out = new ByteArrayOutputStream(); - private final PrintStream originalOut = System.out; - private final ObjectMapper mapper = new ObjectMapper(); - private RequestHandler<Object, Object> requestHandler; - - - @BeforeAll - static void beforeAll() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - } - } +import org.junitpioneer.jupiter.SetEnvironmentVariable; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.metrics.FlushMetrics; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsFactory; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; + +class LambdaMetricsAspectTest { + + private static final PrintStream STANDARD_OUT = System.out; + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + private final ObjectMapper objectMapper = new ObjectMapper(); @BeforeEach - void setUp() throws IllegalAccessException { - openMocks(this); - setupContext(); - writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true); - System.setOut(new PrintStream(out)); + void setUp() throws Exception { + System.setOut(new PrintStream(outputStreamCaptor)); + + // Reset LambdaHandlerProcessor's serviceName + Method resetServiceName = LambdaHandlerProcessor.class.getDeclaredMethod("resetServiceName"); + resetServiceName.setAccessible(true); + resetServiceName.invoke(null); + + // Reset isColdStart + java.lang.reflect.Field coldStartField = LambdaHandlerProcessor.class.getDeclaredField("isColdStart"); + coldStartField.setAccessible(true); + coldStartField.set(null, null); } @AfterEach - void tearDown() { - System.setOut(originalOut); + void tearDown() throws Exception { + System.setOut(STANDARD_OUT); + + // Reset the singleton state between tests + java.lang.reflect.Field field = MetricsFactory.class.getDeclaredField("metricsProxy"); + field.setAccessible(true); + field.set(null, null); } @Test - public void metricsWithoutColdStart() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class); - MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) { - - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""); - - MetricsUtils.defaultDimensions(null); - requestHandler = new PowertoolsMetricsEnabledHandler(); - requestHandler.handleRequest("input", context); - - assertThat(out.toString().split("\n")) - .hasSize(2) - .satisfies(s -> { - Map<String, Object> logAsJson = readAsJson(s[0]); - - assertThat(logAsJson) - .containsEntry("Metric2", 1.0) - .containsEntry("Dimension1", "Value1") - .containsKey("_aws") - .containsEntry("xray_trace_id", "1-5759e988-bd862e3fe1be46a994272793") - .containsEntry("function_request_id", "123ABC"); - - Map<String, Object> aws = (Map<String, Object>) logAsJson.get("_aws"); - - assertThat(aws.get("CloudWatchMetrics")) - .asString() - .contains("Namespace=ExampleApplication"); - - logAsJson = readAsJson(s[1]); - - assertThat(logAsJson) - .containsEntry("Metric1", 1.0) - .containsEntry("Service", "booking") - .containsEntry("function_request_id", "123ABC") - .containsKey("_aws"); - }); - } + void shouldCaptureMetricsFromAnnotatedHandler() throws Exception { + // Given + RequestHandler<Map<String, Object>, String> handler = new HandlerWithMetricsAnnotation(); + Context context = new TestLambdaContext(); + Map<String, Object> input = new HashMap<>(); + + // When + handler.handleRequest(input, context); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.has("test-metric")).isTrue(); + assertThat(rootNode.get("test-metric").asDouble()).isEqualTo(100.0); + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("CustomNamespace"); + assertThat(rootNode.has("Service")).isTrue(); + assertThat(rootNode.get("Service").asText()).isEqualTo("CustomService"); } @Test - public void metricsWithDefaultDimensionSpecified() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class); - MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) { - - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""); - - requestHandler = new PowertoolsMetricsEnabledDefaultDimensionHandler(); - - requestHandler.handleRequest("input", context); - - assertThat(out.toString().split("\n")) - .hasSize(2) - .satisfies(s -> { - Map<String, Object> logAsJson = readAsJson(s[0]); - - assertThat(logAsJson) - .containsEntry("Metric2", 1.0) - .containsEntry("CustomDimension", "booking") - .containsKey("_aws") - .containsEntry("xray_trace_id", "1-5759e988-bd862e3fe1be46a994272793") - .containsEntry("function_request_id", "123ABC"); - - Map<String, Object> aws = (Map<String, Object>) logAsJson.get("_aws"); - - assertThat(aws.get("CloudWatchMetrics")) - .asString() - .contains("Namespace=ExampleApplication"); - - logAsJson = readAsJson(s[1]); - - assertThat(logAsJson) - .containsEntry("Metric1", 1.0) - .containsEntry("CustomDimension", "booking") - .containsEntry("function_request_id", "123ABC") - .containsKey("_aws"); - }); - } + @SetEnvironmentVariable(key = "POWERTOOLS_METRICS_NAMESPACE", value = "EnvNamespace") + @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "EnvService") + void shouldOverrideEnvironmentVariablesWithAnnotation() throws Exception { + // Given + RequestHandler<Map<String, Object>, String> handler = new HandlerWithMetricsAnnotation(); + Context context = new TestLambdaContext(); + Map<String, Object> input = new HashMap<>(); + + // When + handler.handleRequest(input, context); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("CustomNamespace"); + assertThat(rootNode.has("Service")).isTrue(); + assertThat(rootNode.get("Service").asText()).isEqualTo("CustomService"); } @Test - public void metricsWithDefaultNoDimensionSpecified() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class); - MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) { - - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""); - - requestHandler = new PowertoolsMetricsEnabledDefaultNoDimensionHandler(); - - requestHandler.handleRequest("input", context); + @SetEnvironmentVariable(key = "POWERTOOLS_METRICS_NAMESPACE", value = "EnvNamespace") + @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "EnvService") + void shouldUseEnvironmentVariablesWhenNoAnnotationOverrides() throws Exception { + // Given + RequestHandler<Map<String, Object>, String> handler = new HandlerWithDefaultMetricsAnnotation(); + Context context = new TestLambdaContext(); + Map<String, Object> input = new HashMap<>(); + + // When + handler.handleRequest(input, context); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText()) + .isEqualTo("EnvNamespace"); + assertThat(rootNode.has("Service")).isTrue(); + assertThat(rootNode.get("Service").asText()).isEqualTo("EnvService"); + } - assertThat(out.toString().split("\n")) - .hasSize(2) - .satisfies(s -> { - Map<String, Object> logAsJson = readAsJson(s[0]); + @Test + void shouldCaptureColdStartMetricWhenConfigured() throws Exception { + // Given + RequestHandler<Map<String, Object>, String> handler = new HandlerWithColdStartMetricsAnnotation(); + Context context = new TestLambdaContext(); + Map<String, Object> input = new HashMap<>(); - assertThat(logAsJson) - .containsEntry("Metric2", 1.0) - .containsKey("_aws") - .containsEntry("xray_trace_id", "1-5759e988-bd862e3fe1be46a994272793") - .containsEntry("function_request_id", "123ABC"); + // When + handler.handleRequest(input, context); - Map<String, Object> aws = (Map<String, Object>) logAsJson.get("_aws"); + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + String[] emfOutputs = emfOutput.split("\\n"); - assertThat(aws.get("CloudWatchMetrics")) - .asString() - .contains("Namespace=ExampleApplication"); + // There should be two EMF outputs - one for cold start and one for the handler metrics + assertThat(emfOutputs).hasSize(2); - logAsJson = readAsJson(s[1]); + JsonNode coldStartNode = objectMapper.readTree(emfOutputs[0]); + assertThat(coldStartNode.has("ColdStart")).isTrue(); + assertThat(coldStartNode.get("ColdStart").asDouble()).isEqualTo(1.0); - assertThat(logAsJson) - .containsEntry("Metric1", 1.0) - .containsEntry("function_request_id", "123ABC") - .containsKey("_aws"); - }); - } + JsonNode metricsNode = objectMapper.readTree(emfOutputs[1]); + assertThat(metricsNode.has("test-metric")).isTrue(); } @Test - public void metricsWithColdStart() { + @SetEnvironmentVariable(key = "POWERTOOLS_METRICS_FUNCTION_NAME", value = "EnvFunctionName") + void shouldNotIncludeServiceDimensionInColdStartMetricWhenServiceUndefined() throws Exception { + // Given - no service name set, so it will use the default undefined value + RequestHandler<Map<String, Object>, String> handler = new HandlerWithColdStartMetricsAnnotation(); + Context context = new TestLambdaContext(); + Map<String, Object> input = new HashMap<>(); - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { + // When + handler.handleRequest(input, context); - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + String[] emfOutputs = emfOutput.split("\\n"); - MetricsUtils.defaultDimensions(null); - requestHandler = new PowertoolsMetricsColdStartEnabledHandler(); + // There should be two EMF outputs - one for cold start and one for the handler metrics + assertThat(emfOutputs).hasSize(2); - requestHandler.handleRequest("input", context); + JsonNode coldStartNode = objectMapper.readTree(emfOutputs[0]); + assertThat(coldStartNode.has("ColdStart")).isTrue(); + assertThat(coldStartNode.get("ColdStart").asDouble()).isEqualTo(1.0); - assertThat(out.toString().split("\n")) - .hasSize(2) - .satisfies(s -> { - Map<String, Object> logAsJson = readAsJson(s[0]); + // Service dimension should not be present in cold start metrics + assertThat(coldStartNode.has("Service")).isFalse(); - assertThat(logAsJson) - .doesNotContainKey("Metric1") - .containsEntry("ColdStart", 1.0) - .containsEntry("Service", "booking") - .containsEntry("function_request_id", "123ABC") - .containsKey("_aws"); - - logAsJson = readAsJson(s[1]); - - assertThat(logAsJson) - .doesNotContainKey("ColdStart") - .containsEntry("Metric1", 1.0) - .containsEntry("Service", "booking") - .containsEntry("function_request_id", "123ABC") - .containsKey("_aws"); - }); - } + // FunctionName dimension should be present + assertThat(coldStartNode.has("FunctionName")).isTrue(); + assertThat(coldStartNode.get("FunctionName").asText()).isEqualTo("EnvFunctionName"); } @Test - public void noColdStartMetricsWhenColdStartDone() { - - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - - MetricsUtils.defaultDimensions(null); - requestHandler = new PowertoolsMetricsColdStartEnabledHandler(); - - requestHandler.handleRequest("input", context); - requestHandler.handleRequest("input", context); - - assertThat(out.toString().split("\n")) - .hasSize(3) - .satisfies(s -> { - Map<String, Object> logAsJson = readAsJson(s[0]); - - assertThat(logAsJson) - .doesNotContainKey("Metric1") - .containsEntry("ColdStart", 1.0) - .containsEntry("Service", "booking") - .containsEntry("function_request_id", "123ABC") - .containsKey("_aws"); - - logAsJson = readAsJson(s[1]); - - assertThat(logAsJson) - .doesNotContainKey("ColdStart") - .containsEntry("Metric1", 1.0) - .containsEntry("Service", "booking") - .containsEntry("function_request_id", "123ABC") - .containsKey("_aws"); - - logAsJson = readAsJson(s[2]); - - assertThat(logAsJson) - .doesNotContainKey("ColdStart") - .containsEntry("Metric1", 1.0) - .containsEntry("Service", "booking") - .containsEntry("function_request_id", "123ABC") - .containsKey("_aws"); - }); + void shouldUseCustomFunctionNameWhenProvidedForColdStartMetric() throws Exception { + // Given + RequestHandler<Map<String, Object>, String> handler = new HandlerWithCustomFunctionName(); + Context context = new TestLambdaContext(); + Map<String, Object> input = new HashMap<>(); + + // When + handler.handleRequest(input, context); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + String[] emfOutputs = emfOutput.split("\\n"); + + // There should be two EMF outputs - one for cold start and one for the handler metrics + assertThat(emfOutputs).hasSize(2); + + JsonNode coldStartNode = objectMapper.readTree(emfOutputs[0]); + assertThat(coldStartNode.has("FunctionName")).isTrue(); + assertThat(coldStartNode.get("FunctionName").asText()).isEqualTo("CustomFunction"); + + // Check that FunctionName is in the dimensions + JsonNode dimensions = coldStartNode.get("_aws").get("CloudWatchMetrics").get(0).get("Dimensions").get(0); + boolean hasFunctionName = false; + for (JsonNode dimension : dimensions) { + if ("FunctionName".equals(dimension.asText())) { + hasFunctionName = true; + break; + } } + assertThat(hasFunctionName).isTrue(); } @Test - public void metricsWithStreamHandler() throws IOException { - - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); + void shouldUseServiceNameWhenProvidedForColdStartMetric() throws Exception { + // Given + RequestHandler<Map<String, Object>, String> handler = new HandlerWithServiceNameAndColdStart(); + Context context = new TestLambdaContext(); + Map<String, Object> input = new HashMap<>(); + + // When + handler.handleRequest(input, context); + + // Then + String emfOutput = outputStreamCaptor.toString().trim(); + JsonNode rootNode = objectMapper.readTree(emfOutput); + + // Should use the service name from annotation + assertThat(rootNode.has("Service")).isTrue(); + assertThat(rootNode.get("Service").asText()).isEqualTo("CustomService"); + } - MetricsUtils.defaultDimensions(null); - RequestStreamHandler streamHandler = new PowertoolsMetricsEnabledStreamHandler(); + @Test + void shouldHaveNoEffectOnNonHandlerMethod() { + // Given + RequestHandler<Map<String, Object>, String> handler = new HandlerWithAnnotationOnWrongMethod(); + Context context = new TestLambdaContext(); + Map<String, Object> input = new HashMap<>(); - streamHandler.handleRequest(new ByteArrayInputStream(new byte[]{}), new ByteArrayOutputStream(), context); + // When + handler.handleRequest(input, context); - assertThat(out.toString()) - .satisfies(s -> { - Map<String, Object> logAsJson = readAsJson(s); + // Then + String emfOutput = outputStreamCaptor.toString().trim(); - assertThat(logAsJson) - .containsEntry("Metric1", 1.0) - .containsEntry("Service", "booking") - .containsEntry("function_request_id", "123ABC") - .containsKey("_aws"); - }); - } + // Should be empty because we do not flush any metrics manually + assertThat(emfOutput).isEmpty(); } - @Test - public void exceptionWhenNoMetricsEmitted() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - - MetricsUtils.defaultDimensions(null); - requestHandler = new PowertoolsMetricsExceptionWhenNoMetricsHandler(); - - assertThatExceptionOfType(ValidationException.class) - .isThrownBy(() -> requestHandler.handleRequest("input", context)) - .withMessage("No metrics captured, at least one metrics must be emitted"); + static class HandlerWithMetricsAnnotation implements RequestHandler<Map<String, Object>, String> { + @Override + @FlushMetrics(namespace = "CustomNamespace", service = "CustomService") + public String handleRequest(Map<String, Object> input, Context context) { + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + return "OK"; } } - @Test - public void noExceptionWhenNoMetricsEmitted() { - - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - - MetricsUtils.defaultDimensions(null); - requestHandler = new PowertoolsMetricsNoExceptionWhenNoMetricsHandler(); - - requestHandler.handleRequest("input", context); - - assertThat(out.toString()) - .satisfies(s -> { - Map<String, Object> logAsJson = readAsJson(s); - - assertThat(logAsJson) - .containsEntry("Service", "booking") - .doesNotContainKey("_aws"); - }); + static class HandlerWithDefaultMetricsAnnotation implements RequestHandler<Map<String, Object>, String> { + @Override + @FlushMetrics + public String handleRequest(Map<String, Object> input, Context context) { + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + return "OK"; } } - @Test - public void allowWhenNoDimensionsSet() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - MetricsUtils.defaultDimensions(null); - - requestHandler = new PowertoolsMetricsNoDimensionsHandler(); - requestHandler.handleRequest("input", context); - - assertThat(out.toString()) - .satisfies(s -> { - Map<String, Object> logAsJson = readAsJson(s); - assertThat(logAsJson) - .containsEntry("CoolMetric", 1.0) - .containsEntry("function_request_id", "123ABC") - .containsKey("_aws"); - }); + static class HandlerWithColdStartMetricsAnnotation implements RequestHandler<Map<String, Object>, String> { + @Override + @FlushMetrics(captureColdStart = true, namespace = "TestNamespace") + public String handleRequest(Map<String, Object> input, Context context) { + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + return "OK"; } } - @Test - public void exceptionWhenTooManyDimensionsSet() { - - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - - MetricsUtils.defaultDimensions(null); - - requestHandler = new PowertoolsMetricsTooManyDimensionsHandler(); - - assertThatExceptionOfType(ValidationException.class) - .isThrownBy(() -> requestHandler.handleRequest("input", context)) - .withMessage("Number of Dimensions must be in range of 0-9. Actual size: 14."); + static class HandlerWithCustomFunctionName implements RequestHandler<Map<String, Object>, String> { + @Override + @FlushMetrics(captureColdStart = true, functionName = "CustomFunction", namespace = "TestNamespace") + public String handleRequest(Map<String, Object> input, Context context) { + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + return "OK"; } } - @Test - public void metricsPublishedEvenHandlerThrowsException() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); - - MetricsUtils.defaultDimensions(null); - requestHandler = new PowertoolsMetricsWithExceptionInHandler(); - - assertThatExceptionOfType(IllegalStateException.class) - .isThrownBy(() -> requestHandler.handleRequest("input", context)) - .withMessage("Whoops, unexpected exception"); - - assertThat(out.toString()) - .satisfies(s -> { - Map<String, Object> logAsJson = readAsJson(s); - assertThat(logAsJson) - .containsEntry("CoolMetric", 1.0) - .containsEntry("Service", "booking") - .containsEntry("function_request_id", "123ABC") - .containsKey("_aws"); - }); + static class HandlerWithServiceNameAndColdStart implements RequestHandler<Map<String, Object>, String> { + @Override + @FlushMetrics(service = "CustomService", captureColdStart = true, namespace = "TestNamespace") + public String handleRequest(Map<String, Object> input, Context context) { + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + return "OK"; } } - private void setupContext() { - when(context.getFunctionName()).thenReturn("testFunction"); - when(context.getInvokedFunctionArn()).thenReturn("testArn"); - when(context.getFunctionVersion()).thenReturn("1"); - when(context.getMemoryLimitInMB()).thenReturn(10); - when(context.getAwsRequestId()).thenReturn("123ABC"); - } + static class HandlerWithAnnotationOnWrongMethod implements RequestHandler<Map<String, Object>, String> { + @Override + public String handleRequest(Map<String, Object> input, Context context) { + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); + someOtherMethod(); + return "OK"; + } - private Map<String, Object> readAsJson(String s) { - try { - return mapper.readValue(s, Map.class); - } catch (JsonProcessingException e) { - e.printStackTrace(); + @FlushMetrics + public void someOtherMethod() { + Metrics metrics = MetricsFactory.getMetricsInstance(); + metrics.addMetric("test-metric", 100, MetricUnit.COUNT); } - return emptyMap(); } } diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/MetricsUserAgentInterceptorTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/MetricsUserAgentInterceptorTest.java new file mode 100644 index 000000000..673f9f7d4 --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/MetricsUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.metrics.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class MetricsUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/METRICS/"); + } +} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/RequestScopedMetricsProxyTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/RequestScopedMetricsProxyTest.java new file mode 100644 index 000000000..848222bae --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/RequestScopedMetricsProxyTest.java @@ -0,0 +1,277 @@ +package software.amazon.lambda.powertools.metrics.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +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.time.Instant; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mock.Strictness; +import org.mockito.junit.jupiter.MockitoExtension; + +import software.amazon.lambda.powertools.common.internal.LambdaConstants; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricResolution; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; +import software.amazon.lambda.powertools.metrics.provider.MetricsProvider; + +/** + * Tests for RequestScopedMetricsProxy focusing on lazy vs eager initialization behavior. + * + * CRITICAL: These tests ensure configuration methods (setNamespace, setDefaultDimensions, + * setRaiseOnEmptyMetrics) do NOT eagerly create instances, while metrics operations + * (addMetric, addDimension, flush) DO eagerly create instances. + */ +@ExtendWith(MockitoExtension.class) +class RequestScopedMetricsProxyTest { + + @Mock(strictness = Strictness.LENIENT) + private MetricsProvider mockProvider; + + @Mock(strictness = Strictness.LENIENT) + private Metrics mockMetrics; + + private RequestScopedMetricsProxy proxy; + + @BeforeEach + void setUp() { + when(mockProvider.getMetricsInstance()).thenReturn(mockMetrics); + proxy = new RequestScopedMetricsProxy(mockProvider); + } + + @AfterEach + void tearDown() { + System.clearProperty(LambdaConstants.XRAY_TRACE_HEADER); + } + + // ========== LAZY INITIALIZATION TESTS (Configuration Methods) ========== + + @Test + void setNamespace_shouldNotEagerlyCreateInstance() { + // WHEN + proxy.setNamespace("TestNamespace"); + + // THEN - Provider should NOT be called (lazy initialization) + verify(mockProvider, never()).getMetricsInstance(); + } + + @Test + void setDefaultDimensions_shouldNotEagerlyCreateInstance() { + // WHEN + proxy.setDefaultDimensions(DimensionSet.of("key", "value")); + + // THEN - Provider should NOT be called (lazy initialization) + verify(mockProvider, never()).getMetricsInstance(); + } + + @Test + void setDefaultDimensions_shouldThrowExceptionWhenNull() { + // When/Then + assertThatThrownBy(() -> proxy.setDefaultDimensions(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("DimensionSet cannot be null"); + } + + @Test + void setRaiseOnEmptyMetrics_shouldNotEagerlyCreateInstance() { + // WHEN + proxy.setRaiseOnEmptyMetrics(true); + + // THEN - Provider should NOT be called (lazy initialization) + verify(mockProvider, never()).getMetricsInstance(); + } + + // ========== EAGER INITIALIZATION TESTS (Metrics Operations) ========== + + @Test + void addMetric_shouldEagerlyCreateInstance() { + // WHEN + proxy.addMetric("test", 1, MetricUnit.COUNT, MetricResolution.HIGH); + + // THEN - Provider SHOULD be called (eager initialization) + verify(mockProvider, times(1)).getMetricsInstance(); + verify(mockMetrics).addMetric("test", 1, MetricUnit.COUNT, MetricResolution.HIGH); + } + + @Test + void addDimension_shouldEagerlyCreateInstance() { + // WHEN + proxy.addDimension(DimensionSet.of("key", "value")); + + // THEN - Provider SHOULD be called (eager initialization) + verify(mockProvider, times(1)).getMetricsInstance(); + verify(mockMetrics).addDimension(any(DimensionSet.class)); + } + + @Test + void addMetadata_shouldEagerlyCreateInstance() { + // WHEN + proxy.addMetadata("key", "value"); + + // THEN - Provider SHOULD be called (eager initialization) + verify(mockProvider, times(1)).getMetricsInstance(); + verify(mockMetrics).addMetadata("key", "value"); + } + + @Test + void flush_shouldAlwaysCreateInstance() { + // WHEN + proxy.flush(); + + // THEN - Provider SHOULD be called even if no metrics added + verify(mockProvider, times(1)).getMetricsInstance(); + verify(mockMetrics).flush(); + } + + // ========== CONFIGURATION APPLIED ON FIRST METRICS OPERATION ========== + + @Test + void firstMetricsOperation_shouldApplyStoredConfiguration() { + // GIVEN - Set configuration without creating instance + proxy.setNamespace("TestNamespace"); + proxy.setDefaultDimensions(DimensionSet.of("Service", "TestService")); + proxy.setRaiseOnEmptyMetrics(true); + verify(mockProvider, never()).getMetricsInstance(); + + // WHEN - First metrics operation + proxy.addMetric("test", 1, MetricUnit.COUNT); + + // THEN - Instance created and configuration applied + verify(mockProvider, times(1)).getMetricsInstance(); + verify(mockMetrics).setNamespace("TestNamespace"); + verify(mockMetrics).setDefaultDimensions(any(DimensionSet.class)); + verify(mockMetrics).setRaiseOnEmptyMetrics(true); + verify(mockMetrics).addMetric("test", 1, MetricUnit.COUNT, MetricResolution.STANDARD); + } + + // ========== THREAD ISOLATION TESTS ========== + + @Test + void shouldShareInstanceAcrossThreadsWithSameTraceId() throws Exception { + // GIVEN - Set trace ID + System.setProperty(LambdaConstants.XRAY_TRACE_HEADER, "Root=1-test-trace-id"); + + // WHEN - Parent thread adds metric + proxy.addMetric("metric1", 1, MetricUnit.COUNT); + verify(mockProvider, times(1)).getMetricsInstance(); + + // WHEN - Child thread adds metric (same trace ID) + Thread thread2 = new Thread(() -> { + proxy.addMetric("metric2", 2, MetricUnit.COUNT); + }); + thread2.start(); + thread2.join(); + + // THEN - Only one instance created (same trace ID = shared instance) + verify(mockProvider, times(1)).getMetricsInstance(); + } + + @Test + void flush_shouldRemoveThreadLocalInstance() { + // GIVEN - Create instance + proxy.addMetric("test", 1, MetricUnit.COUNT); + verify(mockProvider, times(1)).getMetricsInstance(); + + // WHEN - Flush + proxy.flush(); + + // WHEN - Add another metric (should create new instance) + proxy.addMetric("test2", 2, MetricUnit.COUNT); + + // THEN - Provider called twice (instance was removed after flush) + verify(mockProvider, times(2)).getMetricsInstance(); + } + + // ========== EDGE CASES ========== + + @Test + void multipleConfigurationCalls_shouldUpdateAtomicReferences() { + // WHEN + proxy.setNamespace("Namespace1"); + proxy.setNamespace("Namespace2"); + proxy.setNamespace("Namespace3"); + + // THEN - No instance created + verify(mockProvider, never()).getMetricsInstance(); + + // WHEN - First metrics operation + proxy.addMetric("test", 1, MetricUnit.COUNT); + + // THEN - Only last namespace applied + verify(mockMetrics).setNamespace("Namespace3"); + verify(mockMetrics, never()).setNamespace("Namespace1"); + verify(mockMetrics, never()).setNamespace("Namespace2"); + } + + @Test + void configurationAfterInstanceCreation_shouldApplyImmediately() { + // GIVEN - Instance already created + proxy.addMetric("test", 1, MetricUnit.COUNT); + + // WHEN - Set configuration + proxy.setNamespace("NewNamespace"); + + // THEN - Applied immediately to existing instance + verify(mockMetrics).setNamespace("NewNamespace"); + } + + @Test + void setTimestamp_shouldEagerlyCreateInstance() { + // When + proxy.setTimestamp(Instant.now()); + + // Then + verify(mockProvider, times(1)).getMetricsInstance(); + verify(mockMetrics).setTimestamp(any()); + } + + @Test + void getDefaultDimensions_shouldNotEagerlyCreateInstance() { + // WHEN + DimensionSet result = proxy.getDefaultDimensions(); + + // THEN - Provider should NOT be called (returns stored config or empty) + verify(mockProvider, never()).getMetricsInstance(); + assertThat(result).isNotNull(); + } + + @Test + void captureColdStartMetric_shouldEagerlyCreateInstance() { + // WHEN + proxy.captureColdStartMetric(DimensionSet.of("key", "value")); + + // THEN - Provider SHOULD be called (eager initialization) + verify(mockProvider, times(1)).getMetricsInstance(); + verify(mockMetrics).captureColdStartMetric(any(DimensionSet.class)); + } + + @Test + void flushMetrics_shouldEagerlyCreateInstance() { + // WHEN + proxy.flushMetrics(m -> m.addMetric("test", 1, MetricUnit.COUNT)); + + // THEN - Provider SHOULD be called (eager initialization) + verify(mockProvider, times(1)).getMetricsInstance(); + verify(mockMetrics).flushMetrics(any()); + } + + @Test + void clearDefaultDimensions_shouldEagerlyCreateInstance() { + // WHEN + proxy.clearDefaultDimensions(); + + // THEN - Provider SHOULD be called (eager initialization) + verify(mockProvider, times(1)).getMetricsInstance(); + verify(mockMetrics).clearDefaultDimensions(); + } +} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/ValidatorTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/ValidatorTest.java new file mode 100644 index 000000000..e5d780f9f --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/ValidatorTest.java @@ -0,0 +1,224 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics.internal; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Instant; + +import org.junit.jupiter.api.Test; + +class ValidatorTest { + + @Test + void shouldThrowExceptionWhenNamespaceIsNull() { + // When/Then + assertThatThrownBy(() -> Validator.validateNamespace(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Namespace must be specified before flushing metrics"); + } + + @Test + void shouldThrowExceptionWhenNamespaceIsEmpty() { + // When/Then + assertThatThrownBy(() -> Validator.validateNamespace("")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Namespace must be specified before flushing metrics"); + } + + @Test + void shouldThrowExceptionWhenNamespaceIsBlank() { + // When/Then + assertThatThrownBy(() -> Validator.validateNamespace(" ")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Namespace must be specified before flushing metrics"); + } + + @Test + void shouldThrowExceptionWhenNamespaceExceedsMaxLength() { + // Given + String tooLongNamespace = "a".repeat(256); + + // When/Then + assertThatThrownBy(() -> Validator.validateNamespace(tooLongNamespace)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Namespace exceeds maximum length of 255"); + } + + @Test + void shouldThrowExceptionWhenNamespaceContainsInvalidCharacters() { + // When/Then + assertThatThrownBy(() -> Validator.validateNamespace("Invalid Namespace")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Namespace contains invalid characters"); + } + + @Test + void shouldAcceptValidNamespace() { + // When/Then + assertThatCode(() -> Validator.validateNamespace("Valid.Namespace_123#/")) + .doesNotThrowAnyException(); + } + + @Test + void shouldThrowExceptionWhenDimensionKeyIsNull() { + // When/Then + assertThatThrownBy(() -> Validator.validateDimension(null, "Value")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Dimension key cannot be null or empty"); + } + + @Test + void shouldThrowExceptionWhenDimensionKeyIsEmpty() { + // When/Then + assertThatThrownBy(() -> Validator.validateDimension("", "Value")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Dimension key cannot be null or empty"); + } + + @Test + void shouldThrowExceptionWhenDimensionValueIsNull() { + // When/Then + assertThatThrownBy(() -> Validator.validateDimension("Key", null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Dimension value cannot be null or empty"); + } + + @Test + void shouldThrowExceptionWhenDimensionValueIsEmpty() { + // When/Then + assertThatThrownBy(() -> Validator.validateDimension("Key", "")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Dimension value cannot be null or empty"); + } + + @Test + void shouldThrowExceptionWhenDimensionKeyContainsWhitespace() { + // When/Then + assertThatThrownBy(() -> Validator.validateDimension("Key With Space", "Value")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Dimension key cannot contain whitespaces: Key With Space"); + } + + @Test + void shouldThrowExceptionWhenDimensionValueContainsWhitespace() { + // When/Then + assertThatThrownBy(() -> Validator.validateDimension("Key", "Value With Space")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Dimension value cannot contain whitespaces: Value With Space"); + } + + @Test + void shouldThrowExceptionWhenDimensionKeyStartsWithColon() { + // When/Then + assertThatThrownBy(() -> Validator.validateDimension(":Key", "Value")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Dimension key cannot start with colon: :Key"); + } + + @Test + void shouldThrowExceptionWhenDimensionKeyExceedsMaxLength() { + // Given + String longKey = "a".repeat(251); // MAX_DIMENSION_NAME_LENGTH + 1 + + // When/Then + assertThatThrownBy(() -> Validator.validateDimension(longKey, "Value")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Dimension name exceeds maximum length of 250: " + longKey); + } + + @Test + void shouldThrowExceptionWhenDimensionValueExceedsMaxLength() { + // Given + String longValue = "a".repeat(1025); // MAX_DIMENSION_VALUE_LENGTH + 1 + + // When/Then + assertThatThrownBy(() -> Validator.validateDimension("Key", longValue)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Dimension value exceeds maximum length of 1024: " + longValue); + } + + @Test + void shouldThrowExceptionWhenDimensionKeyContainsNonAsciiCharacters() { + // Given + String keyWithNonAscii = "Key\u0080"; // Non-ASCII character + + // When/Then + assertThatThrownBy(() -> Validator.validateDimension(keyWithNonAscii, "Value")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Dimension name has invalid characters: " + keyWithNonAscii); + } + + @Test + void shouldThrowExceptionWhenDimensionValueContainsNonAsciiCharacters() { + // Given + String valueWithNonAscii = "Value\u0080"; // Non-ASCII character + + // When/Then + assertThatThrownBy(() -> Validator.validateDimension("Key", valueWithNonAscii)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Dimension value has invalid characters: " + valueWithNonAscii); + } + + @Test + void shouldAcceptValidDimension() { + // When/Then + assertThatCode(() -> Validator.validateDimension("ValidKey", "ValidValue")) + .doesNotThrowAnyException(); + } + + @Test + void shouldThrowExceptionWhenTimestampIsNull() { + // When/Then + assertThatThrownBy(() -> Validator.validateTimestamp(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Timestamp cannot be null"); + } + + @Test + void shouldThrowExceptionWhenTimestampIsTooFarInFuture() { + // Given + Instant futureTooFar = Instant.now().plusSeconds(Validator.MAX_TIMESTAMP_FUTURE_AGE_SECONDS + 1); + + // When/Then + assertThatThrownBy(() -> Validator.validateTimestamp(futureTooFar)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Timestamp cannot be more than " + Validator.MAX_TIMESTAMP_FUTURE_AGE_SECONDS + + " seconds in the future"); + } + + @Test + void shouldThrowExceptionWhenTimestampIsTooFarInPast() { + // Given + Instant pastTooFar = Instant.now().minusSeconds(Validator.MAX_TIMESTAMP_PAST_AGE_SECONDS + 1); + + // When/Then + assertThatThrownBy(() -> Validator.validateTimestamp(pastTooFar)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Timestamp cannot be more than " + Validator.MAX_TIMESTAMP_PAST_AGE_SECONDS + + " seconds in the past"); + } + + @Test + void shouldAcceptValidTimestamp() { + // Given + Instant validTimestamp = Instant.now(); + + // When/Then + assertThatCode(() -> Validator.validateTimestamp(validTimestamp)) + .doesNotThrowAnyException(); + } +} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/model/DimensionSetTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/model/DimensionSetTest.java new file mode 100644 index 000000000..ac059f117 --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/model/DimensionSetTest.java @@ -0,0 +1,166 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics.model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +class DimensionSetTest { + + @Test + void shouldCreateEmptyDimensionSet() { + // When + DimensionSet dimensionSet = DimensionSet.of(Collections.emptyMap()); + + // Then + assertThat(dimensionSet.getDimensions()).isEmpty(); + assertThat(dimensionSet.getDimensionKeys()).isEmpty(); + } + + @Test + void shouldCreateDimensionSetWithSingleKeyValue() { + // When + DimensionSet dimensionSet = DimensionSet.of("Key", "Value"); + + // Then + assertThat(dimensionSet.getDimensions()).containsExactly(Map.entry("Key", "Value")); + assertThat(dimensionSet.getDimensionKeys()).containsExactly("Key"); + } + + @Test + void shouldCreateDimensionSetWithTwoKeyValues() { + // When + DimensionSet dimensionSet = DimensionSet.of("Key1", "Value1", "Key2", "Value2"); + + // Then + assertThat(dimensionSet.getDimensions()) + .containsEntry("Key1", "Value1") + .containsEntry("Key2", "Value2"); + assertThat(dimensionSet.getDimensionKeys()).containsExactly("Key1", "Key2"); + } + + @Test + void shouldCreateDimensionSetWithThreeKeyValues() { + // When + DimensionSet dimensionSet = DimensionSet.of( + "Key1", "Value1", + "Key2", "Value2", + "Key3", "Value3"); + + // Then + assertThat(dimensionSet.getDimensions()) + .containsEntry("Key1", "Value1") + .containsEntry("Key2", "Value2") + .containsEntry("Key3", "Value3"); + assertThat(dimensionSet.getDimensionKeys()).containsExactly("Key1", "Key2", "Key3"); + } + + @Test + void shouldCreateDimensionSetWithFourKeyValues() { + // When + DimensionSet dimensionSet = DimensionSet.of( + "Key1", "Value1", + "Key2", "Value2", + "Key3", "Value3", + "Key4", "Value4"); + + // Then + assertThat(dimensionSet.getDimensions()) + .containsEntry("Key1", "Value1") + .containsEntry("Key2", "Value2") + .containsEntry("Key3", "Value3") + .containsEntry("Key4", "Value4"); + assertThat(dimensionSet.getDimensionKeys()).containsExactly("Key1", "Key2", "Key3", "Key4"); + } + + @Test + void shouldCreateDimensionSetWithFiveKeyValues() { + // When + DimensionSet dimensionSet = DimensionSet.of( + "Key1", "Value1", + "Key2", "Value2", + "Key3", "Value3", + "Key4", "Value4", + "Key5", "Value5"); + + // Then + assertThat(dimensionSet.getDimensions()) + .containsEntry("Key1", "Value1") + .containsEntry("Key2", "Value2") + .containsEntry("Key3", "Value3") + .containsEntry("Key4", "Value4") + .containsEntry("Key5", "Value5"); + assertThat(dimensionSet.getDimensionKeys()).containsExactly("Key1", "Key2", "Key3", "Key4", "Key5"); + } + + @Test + void shouldCreateDimensionSetFromMap() { + // Given + Map<String, String> dimensions = Map.of( + "Key1", "Value1", + "Key2", "Value2"); + + // When + DimensionSet dimensionSet = DimensionSet.of(dimensions); + + // Then + assertThat(dimensionSet.getDimensions()).isEqualTo(dimensions); + assertThat(dimensionSet.getDimensionKeys()).containsExactlyInAnyOrder("Key1", "Key2"); + } + + @Test + void shouldGetDimensionValue() { + // Given + DimensionSet dimensionSet = DimensionSet.of("Key1", "Value1", "Key2", "Value2"); + + // When + String value = dimensionSet.getDimensionValue("Key1"); + + // Then + assertThat(value).isEqualTo("Value1"); + } + + @Test + void shouldReturnNullForNonExistentDimension() { + // Given + DimensionSet dimensionSet = DimensionSet.of("Key1", "Value1"); + + // When + String value = dimensionSet.getDimensionValue("NonExistentKey"); + + // Then + assertThat(value).isNull(); + } + + @Test + void shouldThrowExceptionWhenExceedingMaxDimensions() { + // Given + // Create a dimension set with 30 dimensions (30 is maximum) + DimensionSet dimensionSet = new DimensionSet(); + for (int i = 1; i <= 30; i++) { + dimensionSet.addDimension("Key" + i, "Value" + i); + } + + // When/Then + assertThatThrownBy(() -> dimensionSet.addDimension("Key31", "Value31")) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Cannot exceed 30 dimensions per dimension set"); + } +} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/provider/EmfMetricsProviderTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/provider/EmfMetricsProviderTest.java new file mode 100644 index 000000000..2b2268ea8 --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/provider/EmfMetricsProviderTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.metrics.provider; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.internal.EmfMetricsLogger; + +class EmfMetricsProviderTest { + + @Test + void shouldCreateEmfMetricsLogger() { + // Given + EmfMetricsProvider provider = new EmfMetricsProvider(); + + // When + Metrics metrics = provider.getMetricsInstance(); + + // Then + assertThat(metrics) + .isNotNull() + .isInstanceOf(EmfMetricsLogger.class); + } +} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/testutils/TestMetrics.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/testutils/TestMetrics.java new file mode 100644 index 000000000..4a2e33a78 --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/testutils/TestMetrics.java @@ -0,0 +1,91 @@ +package software.amazon.lambda.powertools.metrics.testutils; + +import java.time.Instant; +import java.util.Collections; +import java.util.function.Consumer; + +import com.amazonaws.services.lambda.runtime.Context; + +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.model.DimensionSet; +import software.amazon.lambda.powertools.metrics.model.MetricResolution; +import software.amazon.lambda.powertools.metrics.model.MetricUnit; + +public class TestMetrics implements Metrics { + @Override + public void addMetric(String name, double value, MetricUnit unit) { + // Test placeholder + } + + @Override + public void flush() { + // Test placeholder + } + + @Override + public void addMetric(String key, double value, MetricUnit unit, MetricResolution resolution) { + // Test placeholder + } + + @Override + public void addDimension(DimensionSet dimensionSet) { + // Test placeholder + } + + @Override + public void addMetadata(String key, Object value) { + // Test placeholder + } + + @Override + public void setDefaultDimensions(DimensionSet dimensionSet) { + // Test placeholder + } + + @Override + public DimensionSet getDefaultDimensions() { + // Test placeholder + return DimensionSet.of(Collections.emptyMap()); + } + + @Override + public void setNamespace(String namespace) { + // Test placeholder + } + + @Override + public void setRaiseOnEmptyMetrics(boolean raiseOnEmptyMetrics) { + // Test placeholder + } + + @Override + public void clearDefaultDimensions() { + // Test placeholder + } + + @Override + public void setTimestamp(Instant timestamp) { + // Test placeholder + } + + @Override + public void captureColdStartMetric(Context context, DimensionSet dimensions) { + // Test placeholder + } + + @Override + public void captureColdStartMetric(DimensionSet dimensions) { + // Test placeholder + } + + @Override + public void flushMetrics(Consumer<Metrics> metricsConsumer) { + // Test placeholder + } + + @Override + public void flushSingleMetric(String name, double value, MetricUnit unit, String namespace, + DimensionSet dimensions) { + // Test placeholder + } +} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/testutils/TestMetricsProvider.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/testutils/TestMetricsProvider.java new file mode 100644 index 000000000..4a5fc23dd --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/testutils/TestMetricsProvider.java @@ -0,0 +1,11 @@ +package software.amazon.lambda.powertools.metrics.testutils; + +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.provider.MetricsProvider; + +public class TestMetricsProvider implements MetricsProvider { + @Override + public Metrics getMetricsInstance() { + return new TestMetrics(); + } +} diff --git a/powertools-metrics/src/test/resources/simplelogger.properties b/powertools-metrics/src/test/resources/simplelogger.properties new file mode 100644 index 000000000..6c626ab4d --- /dev/null +++ b/powertools-metrics/src/test/resources/simplelogger.properties @@ -0,0 +1,7 @@ +org.slf4j.simpleLogger.logFile=target/metrics-test.log +org.slf4j.simpleLogger.defaultLogLevel=warn +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS +org.slf4j.simpleLogger.showThreadName=false +org.slf4j.simpleLogger.showLogName=true +org.slf4j.simpleLogger.showShortLogName=false diff --git a/powertools-parameters/pom.xml b/powertools-parameters/pom.xml index 653a9e5a0..124e22186 100644 --- a/powertools-parameters/pom.xml +++ b/powertools-parameters/pom.xml @@ -1,73 +1,43 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + <project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <artifactId>powertools-parent</artifactId> <groupId>software.amazon.lambda</groupId> - <version>1.10.2</version> + <version>2.9.0</version> </parent> <artifactId>powertools-parameters</artifactId> - <name>AWS Lambda Powertools Java library Parameters</name> - - <description> - Set of utilities to retrieve parameters from Secrets Manager or SSM Parameter Store - </description> - <url>https://aws.amazon.com/lambda/</url> - <issueManagement> - <system>GitHub Issues</system> - <url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url> - </issueManagement> - <scm> - <url>https://github.com/awslabs/aws-lambda-powertools-java.git</url> - </scm> - <developers> - <developer> - <name>AWS Lambda Powertools team</name> - <organization>Amazon Web Services</organization> - <organizationUrl>https://aws.amazon.com/</organizationUrl> - </developer> - </developers> - - <distributionManagement> - <snapshotRepository> - <id>ossrh</id> - <url>https://aws.oss.sonatype.org/content/repositories/snapshots</url> - </snapshotRepository> - </distributionManagement> + <name>Powertools for AWS Lambda (Java) - Parameters</name> + <description>Set of utilities to retrieve parameters - common interface</description> <dependencies> <dependency> - <groupId>software.amazon.awssdk</groupId> - <artifactId>ssm</artifactId> - <exclusions> - <exclusion> - <groupId>software.amazon.awssdk</groupId> - <artifactId>apache-client</artifactId> - </exclusion> - <exclusion> - <groupId>software.amazon.awssdk</groupId> - <artifactId>netty-nio-client</artifactId> - </exclusion> - </exclusions> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> </dependency> <dependency> - <groupId>software.amazon.awssdk</groupId> - <artifactId>secretsmanager</artifactId> - <exclusions> - <exclusion> - <groupId>software.amazon.awssdk</groupId> - <artifactId>apache-client</artifactId> - </exclusion> - <exclusion> - <groupId>software.amazon.awssdk</groupId> - <artifactId>netty-nio-client</artifactId> - </exclusion> - </exclusions> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> @@ -77,11 +47,7 @@ <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> - <dependency> - <groupId>org.aspectj</groupId> - <artifactId>aspectjrt</artifactId> - <scope>compile</scope> - </dependency> + <!-- Test dependencies --> <dependency> <groupId>org.junit.jupiter</groupId> @@ -93,16 +59,6 @@ <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-inline</artifactId> - <scope>test</scope> - </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> @@ -120,4 +76,72 @@ </dependency> </dependencies> + <build> + <plugins> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <AWS_REGION>eu-central-1</AWS_REGION> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>generate-graalvm-files</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-parameters</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> </project> diff --git a/powertools-parameters/powertools-parameters-appconfig/pom.xml b/powertools-parameters/powertools-parameters-appconfig/pom.xml new file mode 100644 index 000000000..406f715d3 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/pom.xml @@ -0,0 +1,185 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parent</artifactId> + <version>2.9.0</version> + <relativePath>../../pom.xml</relativePath> + </parent> + + <artifactId>powertools-parameters-appconfig</artifactId> + <name>Powertools for AWS Lambda (Java) library Parameters - AppConfig</name> + <description>AppConfig implementation for the Parameters module</description> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>appconfigdata</artifactId> + <exclusions> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>apache-client</artifactId> + </exclusion> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>netty-nio-client</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjweaver</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>generate-graalvm-files</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-appconfig,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + + <!-- Required explicitly for @Captor ArgumentCaptor --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-parameters-appconfig</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + + <build> + <plugins> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <AWS_REGION>eu-central-1</AWS_REGION> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParam.java b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParam.java new file mode 100644 index 000000000..eb959be76 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParam.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.appconfig; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import software.amazon.lambda.powertools.parameters.transform.Transformer; + +/** + * Use this annotation to inject AWS AppConfig parameters into fields in your application. You + * can also use {@code AppConfigProviderBuilder} to obtain AppConfig values directly, rather than + * injecting them implicitly. + * Both {@code environment} and {@code application} fields are necessary. + * + * @see AppConfigProviderBuilder + * @see <a href="https://docs.aws.amazon.com/appconfig>AWS AppConfig</a> + * @see <a href="https://docs.powertools.aws.dev/lambda/java/utilities/parameters/">Powertools for AWS Lambda (Java) parameters documentation</a> + * + * <pre> + * @AppConfigParam(key = "my-param", environment = "my-env", application = "my-app") + * String appConfigParam; + * </pre> + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface AppConfigParam { + String key(); + + /** + * <b>Mandatory</b>. Provide an environment to the {@link AppConfigProvider} + */ + String environment(); + + /** + * <b>Mandatory</b>. Provide an application to the {@link AppConfigProvider} + */ + String application(); + + /** + * <b>Optional</b> Provide a Transformer to transform the returned parameter values. + */ + Class<? extends Transformer> transformer() default Transformer.class; +} diff --git a/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParametersAspect.java b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParametersAspect.java new file mode 100644 index 000000000..7ab4cf23e --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParametersAspect.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.appconfig; + +import java.util.function.BiFunction; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.FieldSignature; +import software.amazon.lambda.powertools.parameters.BaseParamAspect; + +/** + * Provides the AppConfig parameter aspect. This aspect is responsible for injecting + * parameters from AWS AppConfig into fields annotated with @AppConfigParam. See the + * README and Powertools for Lambda (Java) documentation for information on using this feature. + */ +@Aspect +public class AppConfigParametersAspect extends BaseParamAspect { + + private static BiFunction<String, String, AppConfigProvider> providerBuilder = + (String env, String app) -> AppConfigProvider.builder() + .withEnvironment(env) + .withApplication(app) + .build(); + + + @Pointcut("get(* *) && @annotation(appConfigParamAnnotation)") + public void getParam(AppConfigParam appConfigParamAnnotation) { + } + + @Around("getParam(appConfigParamAnnotation)") + public Object injectParam(final ProceedingJoinPoint joinPoint, final AppConfigParam appConfigParamAnnotation) { + + AppConfigProvider provider = providerBuilder.apply + (appConfigParamAnnotation.environment(), appConfigParamAnnotation.application()); + + return getAndTransform(appConfigParamAnnotation.key(), appConfigParamAnnotation.transformer(), provider, + (FieldSignature) joinPoint.getSignature()); + } + +} diff --git a/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProvider.java b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProvider.java new file mode 100644 index 000000000..06d00ffbe --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProvider.java @@ -0,0 +1,131 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.appconfig; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; +import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationRequest; +import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationResponse; +import software.amazon.awssdk.services.appconfigdata.model.StartConfigurationSessionRequest; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.ParamProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * Implements a {@link ParamProvider} on top of the AppConfig service. AppConfig provides + * a mechanism to retrieve and update configuration of applications over time. + * AppConfig requires the user to create an application, environment, and configuration profile. + * The configuration profile's value can then be retrieved, by key name, through this provider. + * <p> + * Because AppConfig is designed to handle rollouts of configuration over time, we must first + * establish a session for each key we wish to retrieve, and then poll the session for the latest + * value when the user re-requests it. This means we must hold a keyed set of session tokens + * and values. + * + * @see <a href="https://docs.powertools.aws.dev/lambda/java/utilities/parameters/">Parameters provider documentation</a> + * @see <a href="https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-working.html">AppConfig documentation</a> + */ +public class AppConfigProvider extends BaseProvider { + + private final AppConfigDataClient client; + private final String application; + private final String environment; + private final Map<String, EstablishedSession> establishedSessions = new ConcurrentHashMap<>(); + + AppConfigProvider(CacheManager cacheManager, TransformationManager transformationManager, + AppConfigDataClient client, String environment, String application) { + super(cacheManager, transformationManager); + this.client = client; + this.application = application; + this.environment = environment; + } + + /** + * Create a builder that can be used to configure and create a {@link AppConfigProvider}. + * + * @return a new instance of {@link AppConfigProviderBuilder} + */ + public static AppConfigProviderBuilder builder() { + return new AppConfigProviderBuilder(); + } + + /** + * Retrieve the parameter value from the AppConfig parameter store.<br /> + * + * @param key key of the parameter. This ties back to AppConfig's 'profile' concept + * @return the value of the parameter identified by the key + */ + @Override + protected String getValue(String key) { + // Start a configuration session if we don't already have one for the key requested + // so that we can the initial token. If we already have a session, we can take + // the next request token from there. + EstablishedSession establishedSession = establishedSessions.getOrDefault(key, null); + String sessionToken = establishedSession != null ? establishedSession.nextSessionToken + : client.startConfigurationSession(StartConfigurationSessionRequest.builder() + .applicationIdentifier(this.application) + .environmentIdentifier(this.environment) + .configurationProfileIdentifier(key) + .build()) + .initialConfigurationToken(); + + // Get the configuration using the token + GetLatestConfigurationResponse response = client.getLatestConfiguration(GetLatestConfigurationRequest.builder() + .configurationToken(sessionToken) + .build()); + + // Get the next session token we'll use next time we are asked for this key + String nextSessionToken = response.nextPollConfigurationToken(); + + // Get the value of the key. Note that AppConfig will return an empty value if the configuration has not changed + // since we last asked for it in this session - in this case we return the value we stashed at last request. + // https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-code-samples-using-API-read-configuration.html + SdkBytes configFromApi = response.configuration(); + String value; + if (configFromApi != null && configFromApi.asByteArray().length != 0) { + value = configFromApi.asUtf8String(); + } else if (establishedSession != null) { + value = establishedSession.lastConfigurationValue; + } else { + value = null; + } + // Update the cache so we can get the next value later + establishedSessions.put(key, new EstablishedSession(nextSessionToken, value)); + + return value; + } + + @Override + protected Map<String, String> getMultipleValues(String path) { + // Retrieving multiple values is not supported with the AppConfig provider. + throw new RuntimeException( + "Retrieving multiple parameter values is not supported with the AWS App Config Provider"); + } + + private static final class EstablishedSession { + private final String nextSessionToken; + private final String lastConfigurationValue; + + private EstablishedSession(String nextSessionToken, String value) { + this.nextSessionToken = nextSessionToken; + this.lastConfigurationValue = value; + } + } + +} diff --git a/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderBuilder.java b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderBuilder.java new file mode 100644 index 000000000..b6f6cd809 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderBuilder.java @@ -0,0 +1,126 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.appconfig; + +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.ParamProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * Implements a {@link ParamProvider} on top of the AppConfig service. AppConfig provides + */ +public class AppConfigProviderBuilder { + private AppConfigDataClient client; + private CacheManager cacheManager; + private TransformationManager transformationManager; + private String environment; + private String application; + + /** + * Create a {@link AppConfigProvider} instance. + * + * @return a {@link AppConfigProvider} + */ + public AppConfigProvider build() { + if (cacheManager == null) { + cacheManager = new CacheManager(); + } + if (environment == null) { + throw new IllegalStateException("No environment provided; please provide one"); + } + if (application == null) { + throw new IllegalStateException("No application provided; please provide one"); + } + if (transformationManager == null) { + transformationManager = new TransformationManager(); + } + // Create a AppConfigDataClient if we haven't been given one + if (client == null) { + client = AppConfigDataClient.builder() + .httpClientBuilder(UrlConnectionHttpClient.builder()) + .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, + UserAgentConfigurator.getUserAgent(BaseProvider.PARAMETERS)).build()) + .build(); + } + + return new AppConfigProvider(cacheManager, transformationManager, client, environment, application); + } + + /** + * Set custom {@link AppConfigDataClient} to pass to the {@link AppConfigProvider}. <br/> + * Use it if you want to customize the region or any other part of the client. + * + * @param client Custom client + * @return the builder to chain calls (eg. <pre>builder.withClient().build()</pre>) + */ + public AppConfigProviderBuilder withClient(AppConfigDataClient client) { + this.client = client; + return this; + } + + /** + * <b>Mandatory</b>. Provide an environment to the {@link AppConfigProvider} + * + * @param environment the AppConfig environment + * @return the builder to chain calls (eg. <pre>builder.withCacheManager().build()</pre>) + */ + public AppConfigProviderBuilder withEnvironment(String environment) { + this.environment = environment; + return this; + } + + /** + * <b>Mandatory</b>. Provide an application to the {@link AppConfigProvider} + * + * @param application the application to pull configuration from + * @return the builder to chain calls (eg. <pre>builder.withCacheManager().build()</pre>) + */ + public AppConfigProviderBuilder withApplication(String application) { + this.application = application; + return this; + } + + /** + * Provide a CacheManager to the {@link AppConfigProvider} + * + * @param cacheManager the manager that will handle the cache of parameters + * @return the builder to chain calls (eg. <pre>builder.withCacheManager().build()</pre>) + */ + public AppConfigProviderBuilder withCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + return this; + } + + /** + * Provide a transformationManager to the {@link AppConfigProvider} + * + * @param transformationManager the manager that will handle transformation of parameters + * @return the builder to chain calls (eg. <pre>builder.withTransformationManager().build()</pre>) + */ + public AppConfigProviderBuilder withTransformationManager(TransformationManager transformationManager) { + this.transformationManager = transformationManager; + return this; + } +} diff --git a/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/internal/ParametersAppconfigUserAgentInterceptor.java b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/internal/ParametersAppconfigUserAgentInterceptor.java new file mode 100644 index 000000000..01fc8d096 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/internal/ParametersAppconfigUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.parameters.appconfig.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-parameters-appconfig module is on the classpath. + */ +public final class ParametersAppconfigUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("parameters-appconfig"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-parameters/powertools-parameters-appconfig/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-appconfig/jni-config.json b/powertools-parameters/powertools-parameters-appconfig/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-appconfig/jni-config.json new file mode 100644 index 000000000..2c4de0562 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-appconfig/jni-config.json @@ -0,0 +1,26 @@ +[ +{ + "name":"java.lang.Boolean", + "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.String", + "methods":[{"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }] +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"org.apache.maven.surefire.booter.ForkedBooter", + "methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }] +}, +{ + "name":"sun.instrument.InstrumentationImpl", + "methods":[{"name":"<init>","parameterTypes":["long","boolean","boolean","boolean"] }, {"name":"loadClassAndCallAgentmain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"loadClassAndCallPremain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"transform","parameterTypes":["java.lang.Module","java.lang.ClassLoader","java.lang.String","java.lang.Class","java.security.ProtectionDomain","byte[]","boolean"] }] +}, +{ + "name":"sun.management.VMManagementImpl", + "fields":[{"name":"compTimeMonitoringSupport"}, {"name":"currentThreadCpuTimeSupport"}, {"name":"objectMonitorUsageSupport"}, {"name":"otherThreadCpuTimeSupport"}, {"name":"remoteDiagnosticCommandsSupport"}, {"name":"synchronizerUsageSupport"}, {"name":"threadAllocatedMemorySupport"}, {"name":"threadContentionMonitoringSupport"}] +} +] diff --git a/powertools-parameters/powertools-parameters-appconfig/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-appconfig/reflect-config.json b/powertools-parameters/powertools-parameters-appconfig/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-appconfig/reflect-config.json new file mode 100644 index 000000000..b9ae934d6 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-appconfig/reflect-config.json @@ -0,0 +1,369 @@ +[ +{ + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ARCFOURCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESedeCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.tools.attach.VirtualMachine" +}, +{ + "name":"java.io.Serializable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.AutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.Class", + "methods":[{"name":"forName","parameterTypes":["java.lang.String"] }, {"name":"getAnnotatedInterfaces","parameterTypes":[] }, {"name":"getAnnotatedSuperclass","parameterTypes":[] }, {"name":"getDeclaredMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getModule","parameterTypes":[] }, {"name":"getNestHost","parameterTypes":[] }, {"name":"getNestMembers","parameterTypes":[] }, {"name":"getPermittedSubclasses","parameterTypes":[] }, {"name":"getRecordComponents","parameterTypes":[] }, {"name":"isNestmateOf","parameterTypes":["java.lang.Class"] }, {"name":"isRecord","parameterTypes":[] }, {"name":"isSealed","parameterTypes":[] }] +}, +{ + "name":"java.lang.ClassLoader", + "methods":[{"name":"getDefinedPackage","parameterTypes":["java.lang.String"] }, {"name":"getUnnamedModule","parameterTypes":[] }, {"name":"registerAsParallelCapable","parameterTypes":[] }] +}, +{ + "name":"java.lang.Module", + "methods":[{"name":"addExports","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"addReads","parameterTypes":["java.lang.Module"] }, {"name":"canRead","parameterTypes":["java.lang.Module"] }, {"name":"getClassLoader","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPackages","parameterTypes":[] }, {"name":"getResourceAsStream","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"isNamed","parameterTypes":[] }, {"name":"isOpen","parameterTypes":["java.lang.String","java.lang.Module"] }] +}, +{ + "name":"java.lang.Object", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"clone","parameterTypes":[] }, {"name":"getClass","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"java.lang.ProcessHandle", + "methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime", + "methods":[{"name":"version","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime$Version", + "methods":[{"name":"feature","parameterTypes":[] }] +}, +{ + "name":"java.lang.StackWalker" +}, +{ + "name":"java.lang.String" +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getSecurityManager","parameterTypes":[] }] +}, +{ + "name":"java.lang.annotation.Retention", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.annotation.Target", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.invoke.MethodHandle", + "methods":[{"name":"bindTo","parameterTypes":["java.lang.Object"] }, {"name":"invokeWithArguments","parameterTypes":["java.lang.Object[]"] }] +}, +{ + "name":"java.lang.invoke.MethodHandles", + "methods":[{"name":"lookup","parameterTypes":[] }] +}, +{ + "name":"java.lang.invoke.MethodHandles$Lookup", + "methods":[{"name":"findVirtual","parameterTypes":["java.lang.Class","java.lang.String","java.lang.invoke.MethodType"] }] +}, +{ + "name":"java.lang.invoke.MethodType", + "methods":[{"name":"methodType","parameterTypes":["java.lang.Class","java.lang.Class[]"] }] +}, +{ + "name":"java.lang.reflect.AccessibleObject", + "methods":[{"name":"setAccessible","parameterTypes":["boolean"] }] +}, +{ + "name":"java.lang.reflect.AnnotatedArrayType", + "methods":[{"name":"getAnnotatedGenericComponentType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedParameterizedType", + "methods":[{"name":"getAnnotatedActualTypeArguments","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedType", + "methods":[{"name":"getType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedTypeVariable", + "methods":[{"name":"getAnnotatedBounds","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedWildcardType", + "methods":[{"name":"getAnnotatedUpperBounds","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Executable", + "methods":[{"name":"getAnnotatedExceptionTypes","parameterTypes":[] }, {"name":"getAnnotatedParameterTypes","parameterTypes":[] }, {"name":"getAnnotatedReceiverType","parameterTypes":[] }, {"name":"getParameterCount","parameterTypes":[] }, {"name":"getParameters","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"getAnnotatedReturnType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Parameter", + "methods":[{"name":"getModifiers","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"isNamePresent","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.TypeVariable", + "methods":[{"name":"getAnnotatedBounds","parameterTypes":[] }] +}, +{ + "name":"java.security.AccessController", + "methods":[{"name":"doPrivileged","parameterTypes":["java.security.PrivilegedAction"] }, {"name":"doPrivileged","parameterTypes":["java.security.PrivilegedExceptionAction"] }] +}, +{ + "name":"java.security.AlgorithmParametersSpi" +}, +{ + "name":"java.security.KeyStoreSpi" +}, +{ + "name":"java.security.SecureRandomParameters" +}, +{ + "name":"java.util.concurrent.ForkJoinTask", + "fields":[{"name":"aux"}, {"name":"status"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"javax.security.auth.x500.X500Principal", + "fields":[{"name":"thisX500Name"}], + "methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }] +}, +{ + "name":"jdk.internal.misc.Unsafe" +}, +{ + "name":"kotlin.Unit" +}, +{ + "name":"kotlin.jvm.JvmInline" +}, +{ + "name":"org.apache.maven.surefire.junitplatform.JUnitPlatformProvider", + "methods":[{"name":"<init>","parameterTypes":["org.apache.maven.surefire.api.provider.ProviderParameters"] }] +}, +{ + "name":"org.apiguardian.api.API", + "queryAllPublicMethods":true +}, +{ + "name":"org.junit.internal.AssumptionViolatedException" +}, +{ + "name":"org.junit.jupiter.api.Test", + "queryAllPublicMethods":true +}, +{ + "name":"org.junit.platform.commons.annotation.Testable", + "queryAllPublicMethods":true +}, +{ + "name":"org.junit.platform.launcher.LauncherSession", + "methods":[{"name":"getLauncher","parameterTypes":[] }] +}, +{ + "name":"org.junit.platform.launcher.core.LauncherFactory", + "methods":[{"name":"openSession","parameterTypes":[] }] +}, +{ + "name":"scala.util.Properties" +}, +{ + "name":"software.amazon.awssdk.awscore.AwsClient", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"software.amazon.awssdk.core.SdkClient", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"serviceName","parameterTypes":[] }] +}, +{ + "name":"software.amazon.awssdk.services.appconfigdata.AppConfigDataClient", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getLatestConfiguration","parameterTypes":["java.util.function.Consumer"] }, {"name":"getLatestConfiguration","parameterTypes":["software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationRequest"] }, {"name":"serviceClientConfiguration","parameterTypes":[] }, {"name":"startConfigurationSession","parameterTypes":["java.util.function.Consumer"] }, {"name":"startConfigurationSession","parameterTypes":["software.amazon.awssdk.services.appconfigdata.model.StartConfigurationSessionRequest"] }] +}, +{ + "name":"software.amazon.awssdk.utils.SdkAutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"close","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.BaseProvider", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"get","parameterTypes":["java.lang.String"] }, {"name":"get","parameterTypes":["java.lang.String","java.lang.Class"] }, {"name":"getMultiple","parameterTypes":["java.lang.String"] }, {"name":"now","parameterTypes":[] }, {"name":"resetToDefaults","parameterTypes":[] }, {"name":"withMaxAge","parameterTypes":["int","java.time.temporal.ChronoUnit"] }, {"name":"withTransformation","parameterTypes":["java.lang.Class"] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.ParamProvider", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"software.amazon.lambda.powertools.parameters.appconfig.AppConfigParamAspectTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"parameterInjectedByProvider","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.appconfig.AppConfigParamAspectTest$MyInjectedClass", + "fields":[{"name":"myParameter"}] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.appconfig.AppConfigParametersAspect", + "fields":[{"name":"providerBuilder"}] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.appconfig.AppConfigProvider", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getMultipleValues","parameterTypes":["java.lang.String"] }, {"name":"getValue","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.appconfig.AppConfigProviderTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getMultipleValuesThrowsException","parameterTypes":[] }, {"name":"getValueNoValueExists","parameterTypes":[] }, {"name":"getValueRetrievesValue","parameterTypes":[] }, {"name":"init","parameterTypes":[] }, {"name":"multipleKeysRetrievalWorks","parameterTypes":[] }, {"name":"testAppConfigProviderBuilderMissingApplication_throwsException","parameterTypes":[] }, {"name":"testAppConfigProviderBuilderMissingEnvironment_throwsException","parameterTypes":[] }, {"name":"testAppConfigProvider_withoutParameter_shouldHaveDefaultTransformationManager","parameterTypes":[] }] +}, +{ + "name":"sun.reflect.ReflectionFactory", + "methods":[{"name":"getReflectionFactory","parameterTypes":[] }, {"name":"newConstructorForSerialization","parameterTypes":["java.lang.Class","java.lang.reflect.Constructor"] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.SHA", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.X509Factory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAKeyFactory$Legacy", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.SSLContextImpl$TLSContext", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.x509.AuthorityInfoAccessExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.AuthorityKeyIdentifierExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.BasicConstraintsExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CRLDistributionPointsExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CertificatePoliciesExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.ExtendedKeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.IssuerAlternativeNameExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.KeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.NetscapeCertTypeExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.PrivateKeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectAlternativeNameExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectKeyIdentifierExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.appconfig.internal.ParametersAppconfigUserAgentInterceptor", + "methods":[{"name":"<init>","parameterTypes":[] }] +} +] diff --git a/powertools-parameters/powertools-parameters-appconfig/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-appconfig/resource-config.json b/powertools-parameters/powertools-parameters-appconfig/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-appconfig/resource-config.json new file mode 100644 index 000000000..63bcd679e --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-appconfig/resource-config.json @@ -0,0 +1,59 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory\\E" + }, { + "pattern":"\\QMETA-INF/services/org.assertj.core.configuration.Configuration\\E" + }, { + "pattern":"\\QMETA-INF/services/org.assertj.core.presentation.Representation\\E" + }, { + "pattern":"\\QMETA-INF/services/org.junit.platform.engine.TestEngine\\E" + }, { + "pattern":"\\QMETA-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener\\E" + }, { + "pattern":"\\QMETA-INF/services/org.junit.platform.launcher.LauncherSessionListener\\E" + }, { + "pattern":"\\QMETA-INF/services/org.junit.platform.launcher.PostDiscoveryFilter\\E" + }, { + "pattern":"\\QMETA-INF/services/org.junit.platform.launcher.TestExecutionListener\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\Qjunit-platform.properties\\E" + }, { + "pattern":"\\Qmockito-extensions/org.mockito.plugins.AnnotationEngine\\E" + }, { + "pattern":"\\Qmockito-extensions/org.mockito.plugins.DoNotMockEnforcer\\E" + }, { + "pattern":"\\Qmockito-extensions/org.mockito.plugins.InstantiatorProvider2\\E" + }, { + "pattern":"\\Qmockito-extensions/org.mockito.plugins.MemberAccessor\\E" + }, { + "pattern":"\\Qmockito-extensions/org.mockito.plugins.MockMaker\\E" + }, { + "pattern":"\\Qmockito-extensions/org.mockito.plugins.MockResolver\\E" + }, { + "pattern":"\\Qmockito-extensions/org.mockito.plugins.MockitoLogger\\E" + }, { + "pattern":"\\Qmockito-extensions/org.mockito.plugins.PluginSwitch\\E" + }, { + "pattern":"\\Qmockito-extensions/org.mockito.plugins.StackTraceCleanerProvider\\E" + }, { + "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/global/handlers/execution.interceptors\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/services/appconfigdata/execution.interceptors\\E" + }, { + "pattern":"\\Qversion.properties\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/nfc.nrm\\E" + }]}, + "bundles":[] +} diff --git a/powertools-parameters/powertools-parameters-appconfig/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-parameters/powertools-parameters-appconfig/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..c37ecc083 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.parameters.appconfig.internal.ParametersAppconfigUserAgentInterceptor diff --git a/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParamAspectTest.java b/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParamAspectTest.java new file mode 100644 index 000000000..a32cc20a5 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParamAspectTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.appconfig; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.BiFunction; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class AppConfigParamAspectTest { + + @Test + void parameterInjectedByProvider() throws Exception { + // Setup our aspect to return a mocked AppConfigProvider + String environment = "myEnvironment"; + String appName = "myApp"; + String key = "myKey"; + String value = "myValue"; + AppConfigProvider provider = Mockito.mock(AppConfigProvider.class); + BiFunction<String, String, AppConfigProvider> providerBuilder = (String env, String app) -> { + if (env.equals(environment) && app.equals(appName)) { + return provider; + } + throw new RuntimeException("Whoops! Asked for an app/env that we weren't configured for"); + }; + writeStaticField(AppConfigParametersAspect.class, "providerBuilder", providerBuilder, true); + + // Setup our mocked AppConfigProvider to return a value for our test data + Mockito.when(provider.get(key)).thenReturn(value); + + // Create an instance of a class and let the AppConfigParametersAspect inject it + MyInjectedClass obj = new MyInjectedClass(); + assertThat(obj.myParameter).isEqualTo(value); + } + + class MyInjectedClass { + @AppConfigParam(application = "myApp", environment = "myEnvironment", key = "myKey") + public String myParameter; + } + +} diff --git a/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderTest.java b/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderTest.java new file mode 100644 index 000000000..7f06ed412 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderTest.java @@ -0,0 +1,234 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.appconfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatRuntimeException; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static software.amazon.lambda.powertools.parameters.transform.Transformer.json; + +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.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; +import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationRequest; +import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationResponse; +import software.amazon.awssdk.services.appconfigdata.model.StartConfigurationSessionRequest; +import software.amazon.awssdk.services.appconfigdata.model.StartConfigurationSessionResponse; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +@ExtendWith(MockitoExtension.class) +class AppConfigProviderTest { + + private static final String ENVIRONMENT_NAME = "test"; + private static final String DEFAULT_TEST_KEY = "key1"; + private static final String APPLICATION_NAME = "fakeApp"; + + @Mock + AppConfigDataClient client; + + @Captor + ArgumentCaptor<StartConfigurationSessionRequest> startSessionRequestCaptor; + + @Captor + ArgumentCaptor<GetLatestConfigurationRequest> getLatestConfigurationRequestCaptor; + private AppConfigProvider provider; + + @BeforeEach + void init() { + provider = AppConfigProvider.builder() + .withClient(client) + .withApplication(APPLICATION_NAME) + .withEnvironment(ENVIRONMENT_NAME) + .withCacheManager(new CacheManager()) + .withTransformationManager(new TransformationManager()) + .build(); + } + + /** + * Tests repeated calls to the AppConfigProvider for the same key behave correctly. This is more complicated than + * it seems, as the service itself will return no-data if the value of a property remains unchanged since the + * start of a session. This means the provider must cache the result and return it again if it gets no data, but + * subsequent calls should once again return the new data. + */ + @Test + void getValueRetrievesValue() { + // Arrange + StartConfigurationSessionResponse firstSession = StartConfigurationSessionResponse.builder() + .initialConfigurationToken("token1") + .build(); + // first response returns 'value1' + GetLatestConfigurationResponse firstResponse = GetLatestConfigurationResponse.builder() + .nextPollConfigurationToken("token2") + .configuration(SdkBytes.fromUtf8String("value1")) + .build(); + // Second response returns 'value2' + GetLatestConfigurationResponse secondResponse = GetLatestConfigurationResponse.builder() + .nextPollConfigurationToken("token3") + .configuration(SdkBytes.fromUtf8String("value2")) + .build(); + // Third response returns nothing, which means the provider should yield the previous value again + GetLatestConfigurationResponse thirdResponse = GetLatestConfigurationResponse.builder() + .nextPollConfigurationToken("token4") + .build(); + // Forth response returns empty, which means the provider should yield the previous value again + GetLatestConfigurationResponse forthResponse = GetLatestConfigurationResponse.builder() + .nextPollConfigurationToken("token5") + .configuration(SdkBytes.fromUtf8String("")) + .build(); + Mockito.when(client.startConfigurationSession(startSessionRequestCaptor.capture())) + .thenReturn(firstSession); + Mockito.when(client.getLatestConfiguration(getLatestConfigurationRequestCaptor.capture())) + .thenReturn(firstResponse, secondResponse, thirdResponse, forthResponse); + + // Act + String returnedValue1 = provider.getValue(DEFAULT_TEST_KEY); + String returnedValue2 = provider.getValue(DEFAULT_TEST_KEY); + String returnedValue3 = provider.getValue(DEFAULT_TEST_KEY); + String returnedValue4 = provider.getValue(DEFAULT_TEST_KEY); + + // Assert + assertThat(returnedValue1).isEqualTo(firstResponse.configuration().asUtf8String()); + assertThat(returnedValue2).isEqualTo(secondResponse.configuration().asUtf8String()); + assertThat(returnedValue3).isEqualTo(secondResponse.configuration() + .asUtf8String()); // Third response is mocked to return null and should re-use previous value + assertThat(returnedValue4).isEqualTo(secondResponse.configuration() + .asUtf8String()); // Forth response is mocked to return empty and should re-use previous value + assertThat(startSessionRequestCaptor.getValue().applicationIdentifier()).isEqualTo(APPLICATION_NAME); + assertThat(startSessionRequestCaptor.getValue().environmentIdentifier()).isEqualTo(ENVIRONMENT_NAME); + assertThat(startSessionRequestCaptor.getValue().configurationProfileIdentifier()).isEqualTo(DEFAULT_TEST_KEY); + assertThat(getLatestConfigurationRequestCaptor.getAllValues().get(0).configurationToken()).isEqualTo( + firstSession.initialConfigurationToken()); + assertThat(getLatestConfigurationRequestCaptor.getAllValues().get(1).configurationToken()).isEqualTo( + firstResponse.nextPollConfigurationToken()); + assertThat(getLatestConfigurationRequestCaptor.getAllValues().get(2).configurationToken()).isEqualTo( + secondResponse.nextPollConfigurationToken()); + } + + @Test + void getValueNoValueExists() { + // Arrange + StartConfigurationSessionResponse session = StartConfigurationSessionResponse.builder() + .initialConfigurationToken("token1") + .build(); + GetLatestConfigurationResponse response = GetLatestConfigurationResponse.builder() + .nextPollConfigurationToken("token2") + .build(); + Mockito.when(client.startConfigurationSession(startSessionRequestCaptor.capture())) + .thenReturn(session); + Mockito.when(client.getLatestConfiguration(getLatestConfigurationRequestCaptor.capture())) + .thenReturn(response); + + // Act + String returnedValue = provider.getValue(DEFAULT_TEST_KEY); + + // Assert + assertThat(returnedValue).isNull(); + } + + /** + * If we mix requests for different keys together through the same provider, retrieval should + * work as expected. This means two separate configuration sessions should be established with AppConfig. + */ + @Test + void multipleKeysRetrievalWorks() { + // Arrange + String param1Key = "key1"; + StartConfigurationSessionResponse param1Session = StartConfigurationSessionResponse.builder() + .initialConfigurationToken("token1a") + .build(); + GetLatestConfigurationResponse param1Response = GetLatestConfigurationResponse.builder() + .nextPollConfigurationToken("token1b") + .configuration(SdkBytes.fromUtf8String("value1")) + .build(); + String param2Key = "key2"; + StartConfigurationSessionResponse param2Session = StartConfigurationSessionResponse.builder() + .initialConfigurationToken("token2a") + .build(); + GetLatestConfigurationResponse param2Response = GetLatestConfigurationResponse.builder() + .nextPollConfigurationToken("token2b") + .configuration(SdkBytes.fromUtf8String("value1")) + .build(); + Mockito.when(client.startConfigurationSession(startSessionRequestCaptor.capture())) + .thenReturn(param1Session, param2Session); + Mockito.when(client.getLatestConfiguration(getLatestConfigurationRequestCaptor.capture())) + .thenReturn(param1Response, param2Response); + + // Act + String firstKeyValue = provider.getValue(param1Key); + String secondKeyValue = provider.getValue(param2Key); + + // Assert + assertThat(firstKeyValue).isEqualTo(param1Response.configuration().asUtf8String()); + assertThat(secondKeyValue).isEqualTo(param2Response.configuration().asUtf8String()); + assertThat(startSessionRequestCaptor.getAllValues().get(0).configurationProfileIdentifier()).isEqualTo( + param1Key); + assertThat(startSessionRequestCaptor.getAllValues().get(1).configurationProfileIdentifier()).isEqualTo( + param2Key); + assertThat(getLatestConfigurationRequestCaptor.getAllValues().get(0).configurationToken()).isEqualTo( + param1Session.initialConfigurationToken()); + assertThat(getLatestConfigurationRequestCaptor.getAllValues().get(1).configurationToken()).isEqualTo( + param2Session.initialConfigurationToken()); + } + + @Test + void getMultipleValuesThrowsException() { + // Act & Assert + assertThatRuntimeException().isThrownBy(() -> provider.getMultipleValues("path")) + .withMessage("Retrieving multiple parameter values is not supported with the AWS App Config Provider"); + } + + @Test + void testAppConfigProviderBuilderMissingEnvironment_throwsException() { + // Act & Assert + assertThatIllegalStateException().isThrownBy(() -> AppConfigProvider.builder() + .withCacheManager(new CacheManager()) + .withApplication(APPLICATION_NAME) + .withClient(client) + .build()) + .withMessage("No environment provided; please provide one"); + } + + @Test + void testAppConfigProviderBuilderMissingApplication_throwsException() { + // Act & Assert + assertThatIllegalStateException().isThrownBy(() -> AppConfigProvider.builder() + .withCacheManager(new CacheManager()) + .withEnvironment(ENVIRONMENT_NAME) + .withClient(client) + .build()) + .withMessage("No application provided; please provide one"); + } + + @Test + void testAppConfigProvider_withoutParameter_shouldHaveDefaultTransformationManager() { + // Act + AppConfigProvider appConfigProvider = AppConfigProvider.builder() + .withEnvironment("test") + .withApplication("app") + .build(); + // Assert + assertDoesNotThrow(() -> appConfigProvider.withTransformation(json)); + } +} diff --git a/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/internal/ParametersAppconfigUserAgentInterceptorTest.java b/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/internal/ParametersAppconfigUserAgentInterceptorTest.java new file mode 100644 index 000000000..124b71ef3 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/internal/ParametersAppconfigUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.parameters.appconfig.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class ParametersAppconfigUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/PARAMETERS-APPCONFIG/"); + } +} \ No newline at end of file diff --git a/powertools-parameters/powertools-parameters-dynamodb/pom.xml b/powertools-parameters/powertools-parameters-dynamodb/pom.xml new file mode 100644 index 000000000..eb5604046 --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/pom.xml @@ -0,0 +1,186 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parent</artifactId> + <version>2.9.0</version> + <relativePath>../../pom.xml</relativePath> + </parent> + + <artifactId>powertools-parameters-dynamodb</artifactId> + <name>Powertools for AWS Lambda (Java) library Parameters - DynamoDB</name> + <description>DynamoDB implementation for the Parameters module</description> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>dynamodb</artifactId> + <exclusions> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>apache-client</artifactId> + </exclusion> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>netty-nio-client</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjweaver</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>generate-graalvm-files</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-dynamodb,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + + <!-- Required explicitly for @Captor ArgumentCaptor --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-parameters-dynamodb</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + + <build> + <plugins> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <AWS_REGION>eu-central-1</AWS_REGION> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParam.java b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParam.java new file mode 100644 index 000000000..946786cb4 --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParam.java @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.dynamodb; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import software.amazon.lambda.powertools.parameters.transform.Transformer; + +/** + * Inject a parameter from the DynamoDB Parameter Store into a field. You can also use + * {@code DynamoDbProviderBuilder} to obtain DynamoDB values directly, rather than injecting them implicitly. + * + * Usage: + * <pre> + * @DynamoDbParam(key = "my-param", table = "my-table") + * String myParameter; + * </pre> + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DynamoDbParam { + /** + * <b>Mandatory</b>. Partition key from the DynamoDB table + */ + String key(); + + /** + * <b>Mandatory</b>. Table name for the DynamoDB table + * @return + */ + String table(); + + /** + * <b>Optional</b> Provide a Transformer to transform the returned parameter values. + */ + Class<? extends Transformer> transformer() default Transformer.class; +} diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParamAspect.java b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParamAspect.java new file mode 100644 index 000000000..1aa022cbd --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParamAspect.java @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.dynamodb; + +import java.util.function.Function; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.FieldSignature; +import software.amazon.lambda.powertools.parameters.BaseParamAspect; +import software.amazon.lambda.powertools.parameters.BaseProvider; + +/** + * Provides the Amazon DynamoDB parameter aspect. This aspect is responsible for injecting + * parameters from DynamoDB into fields annotated with @DynamoDbParam. See the + * README and Powertools for Lambda (Java) documentation for information on using this feature. + */ +@Aspect +public class DynamoDbParamAspect extends BaseParamAspect { + + private static Function<String, DynamoDbProvider> providerBuilder = + (String table) -> DynamoDbProvider.builder() + .withTable(table) + .build(); + + @Pointcut("get(* *) && @annotation(ddbConfigParam)") + public void getParam(DynamoDbParam ddbConfigParam) { + } + + @Around("getParam(ddbConfigParam)") + public Object injectParam(final ProceedingJoinPoint joinPoint, final DynamoDbParam ddbConfigParam) { + + BaseProvider provider = providerBuilder.apply(ddbConfigParam.table()); + return getAndTransform(ddbConfigParam.key(), ddbConfigParam.transformer(), provider, + (FieldSignature) joinPoint.getSignature()); + } + +} diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProvider.java b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProvider.java new file mode 100644 index 000000000..4a1476e38 --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProvider.java @@ -0,0 +1,121 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.dynamodb; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryResponse; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.ParamProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.dynamodb.exception.DynamoDbProviderSchemaException; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * Implements a {@link ParamProvider} on top of Amazon DynamoDB. The schema of the table + * is described in the Powertools for AWS Lambda (Java) documentation. + * + * @see <a href="https://docs.powertools.aws.dev/lambda-java/utilities/parameters">Parameters provider documentation</a> + */ +public class DynamoDbProvider extends BaseProvider { + + private final DynamoDbClient client; + private final String tableName; + + DynamoDbProvider(CacheManager cacheManager, TransformationManager transformationManager, DynamoDbClient client, + String tableName) { + super(cacheManager, transformationManager); + this.client = client; + this.tableName = tableName; + } + + /** + * Create a builder that can be used to configure and create a {@link DynamoDbProvider}. + * + * @return a new instance of {@link DynamoDbProviderBuilder} + */ + public static DynamoDbProviderBuilder builder() { + return new DynamoDbProviderBuilder(); + } + + /** + * Return a single value from the DynamoDB parameter provider. + * + * @param key key of the parameter + * @return The value, if it exists, null if it doesn't. Throws if the row exists but doesn't match the schema. + */ + @Override + protected String getValue(String key) { + GetItemResponse resp = client.getItem(GetItemRequest.builder() + .tableName(tableName) + .key(Collections.singletonMap("id", AttributeValue.fromS(key))) + .attributesToGet("value") + .build()); + + // If we have an item at the key, we should be able to get a 'val' out of it. If not it's + // exceptional. + // If we don't have an item at the key, we should return null. + if (resp.hasItem() && !resp.item().values().isEmpty()) { + if (!resp.item().containsKey("value")) { + throw new DynamoDbProviderSchemaException("Missing 'value': " + resp.item()); + } + return resp.item().get("value").s(); + } + + return null; + } + + /** + * Returns multiple values from the DynamoDB parameter provider. + * + * @param path Parameter store path + * @return All values matching the given path, and an empty map if none do. Throws if any records exist that don't match the schema. + */ + @Override + protected Map<String, String> getMultipleValues(String path) { + + QueryResponse resp = client.query(QueryRequest.builder() + .tableName(tableName) + .keyConditionExpression("id = :v_id") + .expressionAttributeValues(Collections.singletonMap(":v_id", AttributeValue.fromS(path))) + .build()); + + return resp + .items() + .stream() + .peek((i) -> + { + if (!i.containsKey("sk")) { + throw new DynamoDbProviderSchemaException("Missing 'sk': " + i); + } + if (!i.containsKey("value")) { + throw new DynamoDbProviderSchemaException("Missing 'value': " + i); + } + }) + .collect( + Collectors.toMap( + (i) -> i.get("sk").s(), + (i) -> i.get("value").s())); + + + } + +} diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderBuilder.java b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderBuilder.java new file mode 100644 index 000000000..b98ff285d --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderBuilder.java @@ -0,0 +1,116 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.dynamodb; + +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.ParamProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * Implements a {@link ParamProvider} on top of the DynamoDB service. DynamoDB provides + */ +public class DynamoDbProviderBuilder { + private DynamoDbClient client; + private String table; + private CacheManager cacheManager; + private TransformationManager transformationManager; + + static DynamoDbClient createClient() { + return DynamoDbClient.builder() + .httpClientBuilder(UrlConnectionHttpClient.builder()) + .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, + UserAgentConfigurator.getUserAgent(BaseProvider.PARAMETERS)).build()) + .build(); + } + + /** + * Create a {@link DynamoDbProvider} instance. + * + * @return a {@link DynamoDbProvider} + */ + public DynamoDbProvider build() { + if (cacheManager == null) { + cacheManager = new CacheManager(); + } + if (table == null) { + throw new IllegalStateException("No DynamoDB table name provided; please provide one"); + } + DynamoDbProvider provider; + if (client == null) { + client = createClient(); + } + if (transformationManager == null) { + transformationManager = new TransformationManager(); + } + provider = new DynamoDbProvider(cacheManager, transformationManager, client, table); + + return provider; + } + + /** + * Set custom {@link DynamoDbClient} to pass to the {@link DynamoDbClient}. <br/> + * Use it if you want to customize the region or any other part of the client. + * + * @param client Custom client + * @return the builder to chain calls (eg. <pre>builder.withClient().build()</pre>) + */ + public DynamoDbProviderBuilder withClient(DynamoDbClient client) { + this.client = client; + return this; + } + + /** + * Provide a CacheManager to the {@link DynamoDbProvider} + * + * @param cacheManager the manager that will handle the cache of parameters + * @return the builder to chain calls (eg. <pre>builder.withCacheManager().build()</pre>) + */ + public DynamoDbProviderBuilder withCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + return this; + } + + /** + * <b>Mandatory</b>. Provide a DynamoDB table to the {@link DynamoDbProvider} + * + * @param table the table that parameters will be retrieved from. + * @return the builder to chain calls (eg. <pre>builder.withTable().build()</pre>) + */ + public DynamoDbProviderBuilder withTable(String table) { + this.table = table; + return this; + } + + /** + * Provide a transformationManager to the {@link DynamoDbProvider} + * + * @param transformationManager the manager that will handle transformation of parameters + * @return the builder to chain calls (eg. <pre>builder.withTransformationManager().build()</pre>) + */ + public DynamoDbProviderBuilder withTransformationManager(TransformationManager transformationManager) { + this.transformationManager = transformationManager; + return this; + } +} diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/exception/DynamoDbProviderSchemaException.java b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/exception/DynamoDbProviderSchemaException.java new file mode 100644 index 000000000..4a22dbc99 --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/exception/DynamoDbProviderSchemaException.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.dynamodb.exception; + +/** + * Thrown when the DynamoDbProvider comes across parameter data that + * does not meet the DynamoDB parameters schema. + */ +public class DynamoDbProviderSchemaException extends RuntimeException { + public DynamoDbProviderSchemaException(String msg) { + super(msg); + } +} diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/internal/ParametersDynamodbUserAgentInterceptor.java b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/internal/ParametersDynamodbUserAgentInterceptor.java new file mode 100644 index 000000000..0b8b01dcb --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/internal/ParametersDynamodbUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.parameters.dynamodb.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-parameters-dynamodb module is on the classpath. + */ +public final class ParametersDynamodbUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("parameters-dynamodb"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-dynamodb/jni-config.json b/powertools-parameters/powertools-parameters-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-dynamodb/jni-config.json new file mode 100644 index 000000000..2689203aa --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-dynamodb/jni-config.json @@ -0,0 +1,22 @@ +[ +{ + "name":"java.lang.Boolean", + "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.String", + "methods":[{"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }] +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"sun.instrument.InstrumentationImpl", + "methods":[{"name":"<init>","parameterTypes":["long","boolean","boolean","boolean"] }, {"name":"loadClassAndCallAgentmain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"loadClassAndCallPremain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"transform","parameterTypes":["java.lang.Module","java.lang.ClassLoader","java.lang.String","java.lang.Class","java.security.ProtectionDomain","byte[]","boolean"] }] +}, +{ + "name":"sun.management.VMManagementImpl", + "fields":[{"name":"compTimeMonitoringSupport"}, {"name":"currentThreadCpuTimeSupport"}, {"name":"objectMonitorUsageSupport"}, {"name":"otherThreadCpuTimeSupport"}, {"name":"remoteDiagnosticCommandsSupport"}, {"name":"synchronizerUsageSupport"}, {"name":"threadAllocatedMemorySupport"}, {"name":"threadContentionMonitoringSupport"}] +} +] diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-dynamodb/reflect-config.json b/powertools-parameters/powertools-parameters-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-dynamodb/reflect-config.json new file mode 100644 index 000000000..e49454b83 --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-dynamodb/reflect-config.json @@ -0,0 +1,330 @@ +[ +{ + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ARCFOURCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESedeCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.tools.attach.VirtualMachine" +}, +{ + "name":"java.io.Serializable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.AutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.Class", + "methods":[{"name":"forName","parameterTypes":["java.lang.String"] }, {"name":"getAnnotatedInterfaces","parameterTypes":[] }, {"name":"getAnnotatedSuperclass","parameterTypes":[] }, {"name":"getDeclaredMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getModule","parameterTypes":[] }, {"name":"getNestHost","parameterTypes":[] }, {"name":"getNestMembers","parameterTypes":[] }, {"name":"getPermittedSubclasses","parameterTypes":[] }, {"name":"getRecordComponents","parameterTypes":[] }, {"name":"isNestmateOf","parameterTypes":["java.lang.Class"] }, {"name":"isRecord","parameterTypes":[] }, {"name":"isSealed","parameterTypes":[] }] +}, +{ + "name":"java.lang.ClassLoader", + "methods":[{"name":"getDefinedPackage","parameterTypes":["java.lang.String"] }, {"name":"getUnnamedModule","parameterTypes":[] }, {"name":"registerAsParallelCapable","parameterTypes":[] }] +}, +{ + "name":"java.lang.Module", + "methods":[{"name":"addExports","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"addReads","parameterTypes":["java.lang.Module"] }, {"name":"canRead","parameterTypes":["java.lang.Module"] }, {"name":"getClassLoader","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPackages","parameterTypes":[] }, {"name":"getResourceAsStream","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"isNamed","parameterTypes":[] }, {"name":"isOpen","parameterTypes":["java.lang.String","java.lang.Module"] }] +}, +{ + "name":"java.lang.Object", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"clone","parameterTypes":[] }, {"name":"getClass","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"java.lang.ProcessHandle", + "methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime", + "methods":[{"name":"version","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime$Version", + "methods":[{"name":"feature","parameterTypes":[] }] +}, +{ + "name":"java.lang.StackWalker" +}, +{ + "name":"java.lang.String" +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getSecurityManager","parameterTypes":[] }] +}, +{ + "name":"java.lang.annotation.Retention", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.annotation.Target", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.invoke.MethodHandle", + "methods":[{"name":"bindTo","parameterTypes":["java.lang.Object"] }, {"name":"invokeWithArguments","parameterTypes":["java.lang.Object[]"] }] +}, +{ + "name":"java.lang.invoke.MethodHandles", + "methods":[{"name":"lookup","parameterTypes":[] }] +}, +{ + "name":"java.lang.invoke.MethodHandles$Lookup", + "methods":[{"name":"findVirtual","parameterTypes":["java.lang.Class","java.lang.String","java.lang.invoke.MethodType"] }] +}, +{ + "name":"java.lang.invoke.MethodType", + "methods":[{"name":"methodType","parameterTypes":["java.lang.Class","java.lang.Class[]"] }] +}, +{ + "name":"java.lang.reflect.AccessibleObject", + "methods":[{"name":"setAccessible","parameterTypes":["boolean"] }] +}, +{ + "name":"java.lang.reflect.AnnotatedArrayType", + "methods":[{"name":"getAnnotatedGenericComponentType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedParameterizedType", + "methods":[{"name":"getAnnotatedActualTypeArguments","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedType", + "methods":[{"name":"getType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedTypeVariable", + "methods":[{"name":"getAnnotatedBounds","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedWildcardType", + "methods":[{"name":"getAnnotatedUpperBounds","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Executable", + "methods":[{"name":"getAnnotatedExceptionTypes","parameterTypes":[] }, {"name":"getAnnotatedParameterTypes","parameterTypes":[] }, {"name":"getAnnotatedReceiverType","parameterTypes":[] }, {"name":"getParameterCount","parameterTypes":[] }, {"name":"getParameters","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"getAnnotatedReturnType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Parameter", + "methods":[{"name":"getModifiers","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"isNamePresent","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.TypeVariable", + "methods":[{"name":"getAnnotatedBounds","parameterTypes":[] }] +}, +{ + "name":"java.security.AccessController", + "methods":[{"name":"doPrivileged","parameterTypes":["java.security.PrivilegedAction"] }, {"name":"doPrivileged","parameterTypes":["java.security.PrivilegedExceptionAction"] }] +}, +{ + "name":"java.security.AlgorithmParametersSpi" +}, +{ + "name":"java.security.KeyStoreSpi" +}, +{ + "name":"java.security.SecureRandomParameters" +}, +{ + "name":"java.util.concurrent.ForkJoinTask", + "fields":[{"name":"aux"}, {"name":"status"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"javax.security.auth.x500.X500Principal", + "fields":[{"name":"thisX500Name"}], + "methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }] +}, +{ + "name":"jdk.internal.misc.Unsafe" +}, +{ + "name":"kotlin.Unit" +}, +{ + "name":"kotlin.jvm.JvmInline" +}, +{ + "name":"org.apiguardian.api.API", + "queryAllPublicMethods":true +}, +{ + "name":"scala.util.Properties" +}, +{ + "name":"software.amazon.awssdk.awscore.AwsClient", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"software.amazon.awssdk.core.SdkClient", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"serviceName","parameterTypes":[] }] +}, +{ + "name":"software.amazon.awssdk.services.dynamodb.DynamoDbClient", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"batchExecuteStatement","parameterTypes":["java.util.function.Consumer"] }, {"name":"batchExecuteStatement","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.BatchExecuteStatementRequest"] }, {"name":"batchGetItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"batchGetItem","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.BatchGetItemRequest"] }, {"name":"batchGetItemPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"batchGetItemPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.BatchGetItemRequest"] }, {"name":"batchWriteItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"batchWriteItem","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.BatchWriteItemRequest"] }, {"name":"createBackup","parameterTypes":["java.util.function.Consumer"] }, {"name":"createBackup","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.CreateBackupRequest"] }, {"name":"createGlobalTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"createGlobalTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.CreateGlobalTableRequest"] }, {"name":"createTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"createTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.CreateTableRequest"] }, {"name":"deleteBackup","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteBackup","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DeleteBackupRequest"] }, {"name":"deleteItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteItem","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest"] }, {"name":"deleteResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteResourcePolicy","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DeleteResourcePolicyRequest"] }, {"name":"deleteTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest"] }, {"name":"describeBackup","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeBackup","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeBackupRequest"] }, {"name":"describeContinuousBackups","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeContinuousBackups","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeContinuousBackupsRequest"] }, {"name":"describeContributorInsights","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeContributorInsights","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeContributorInsightsRequest"] }, {"name":"describeEndpoints","parameterTypes":[] }, {"name":"describeEndpoints","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeEndpoints","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeEndpointsRequest"] }, {"name":"describeExport","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeExport","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeExportRequest"] }, {"name":"describeGlobalTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeGlobalTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeGlobalTableRequest"] }, {"name":"describeGlobalTableSettings","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeGlobalTableSettings","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeGlobalTableSettingsRequest"] }, {"name":"describeImport","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeImport","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeImportRequest"] }, {"name":"describeKinesisStreamingDestination","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeKinesisStreamingDestination","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeKinesisStreamingDestinationRequest"] }, {"name":"describeLimits","parameterTypes":[] }, {"name":"describeLimits","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeLimits","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeLimitsRequest"] }, {"name":"describeTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest"] }, {"name":"describeTableReplicaAutoScaling","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeTableReplicaAutoScaling","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeTableReplicaAutoScalingRequest"] }, {"name":"describeTimeToLive","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeTimeToLive","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeTimeToLiveRequest"] }, {"name":"disableKinesisStreamingDestination","parameterTypes":["java.util.function.Consumer"] }, {"name":"disableKinesisStreamingDestination","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DisableKinesisStreamingDestinationRequest"] }, {"name":"enableKinesisStreamingDestination","parameterTypes":["java.util.function.Consumer"] }, {"name":"enableKinesisStreamingDestination","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.EnableKinesisStreamingDestinationRequest"] }, {"name":"executeStatement","parameterTypes":["java.util.function.Consumer"] }, {"name":"executeStatement","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ExecuteStatementRequest"] }, {"name":"executeTransaction","parameterTypes":["java.util.function.Consumer"] }, {"name":"executeTransaction","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ExecuteTransactionRequest"] }, {"name":"exportTableToPointInTime","parameterTypes":["java.util.function.Consumer"] }, {"name":"exportTableToPointInTime","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ExportTableToPointInTimeRequest"] }, {"name":"getItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"getItem","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.GetItemRequest"] }, {"name":"getResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"getResourcePolicy","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.GetResourcePolicyRequest"] }, {"name":"importTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"importTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ImportTableRequest"] }, {"name":"listBackups","parameterTypes":[] }, {"name":"listBackups","parameterTypes":["java.util.function.Consumer"] }, {"name":"listBackups","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListBackupsRequest"] }, {"name":"listContributorInsights","parameterTypes":["java.util.function.Consumer"] }, {"name":"listContributorInsights","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListContributorInsightsRequest"] }, {"name":"listContributorInsightsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listContributorInsightsPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListContributorInsightsRequest"] }, {"name":"listExports","parameterTypes":["java.util.function.Consumer"] }, {"name":"listExports","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListExportsRequest"] }, {"name":"listExportsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listExportsPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListExportsRequest"] }, {"name":"listGlobalTables","parameterTypes":[] }, {"name":"listGlobalTables","parameterTypes":["java.util.function.Consumer"] }, {"name":"listGlobalTables","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListGlobalTablesRequest"] }, {"name":"listImports","parameterTypes":["java.util.function.Consumer"] }, {"name":"listImports","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListImportsRequest"] }, {"name":"listImportsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listImportsPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListImportsRequest"] }, {"name":"listTables","parameterTypes":[] }, {"name":"listTables","parameterTypes":["java.util.function.Consumer"] }, {"name":"listTables","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListTablesRequest"] }, {"name":"listTablesPaginator","parameterTypes":[] }, {"name":"listTablesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listTablesPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListTablesRequest"] }, {"name":"listTagsOfResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"listTagsOfResource","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListTagsOfResourceRequest"] }, {"name":"putItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"putItem","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.PutItemRequest"] }, {"name":"putResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"putResourcePolicy","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.PutResourcePolicyRequest"] }, {"name":"query","parameterTypes":["java.util.function.Consumer"] }, {"name":"query","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.QueryRequest"] }, {"name":"queryPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"queryPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.QueryRequest"] }, {"name":"restoreTableFromBackup","parameterTypes":["java.util.function.Consumer"] }, {"name":"restoreTableFromBackup","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.RestoreTableFromBackupRequest"] }, {"name":"restoreTableToPointInTime","parameterTypes":["java.util.function.Consumer"] }, {"name":"restoreTableToPointInTime","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.RestoreTableToPointInTimeRequest"] }, {"name":"scan","parameterTypes":["java.util.function.Consumer"] }, {"name":"scan","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ScanRequest"] }, {"name":"scanPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"scanPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ScanRequest"] }, {"name":"serviceClientConfiguration","parameterTypes":[] }, {"name":"tagResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"tagResource","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.TagResourceRequest"] }, {"name":"transactGetItems","parameterTypes":["java.util.function.Consumer"] }, {"name":"transactGetItems","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.TransactGetItemsRequest"] }, {"name":"transactWriteItems","parameterTypes":["java.util.function.Consumer"] }, {"name":"transactWriteItems","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsRequest"] }, {"name":"untagResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"untagResource","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UntagResourceRequest"] }, {"name":"updateContinuousBackups","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateContinuousBackups","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateContinuousBackupsRequest"] }, {"name":"updateContributorInsights","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateContributorInsights","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateContributorInsightsRequest"] }, {"name":"updateGlobalTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateGlobalTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateGlobalTableRequest"] }, {"name":"updateGlobalTableSettings","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateGlobalTableSettings","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateGlobalTableSettingsRequest"] }, {"name":"updateItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateItem","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest"] }, {"name":"updateKinesisStreamingDestination","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateKinesisStreamingDestination","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateKinesisStreamingDestinationRequest"] }, {"name":"updateTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateTableRequest"] }, {"name":"updateTableReplicaAutoScaling","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateTableReplicaAutoScaling","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateTableReplicaAutoScalingRequest"] }, {"name":"updateTimeToLive","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateTimeToLive","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateTimeToLiveRequest"] }, {"name":"waiter","parameterTypes":[] }] +}, +{ + "name":"software.amazon.awssdk.utils.SdkAutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"close","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.BaseProvider", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"get","parameterTypes":["java.lang.String"] }, {"name":"get","parameterTypes":["java.lang.String","java.lang.Class"] }, {"name":"getMultiple","parameterTypes":["java.lang.String"] }, {"name":"now","parameterTypes":[] }, {"name":"resetToDefaults","parameterTypes":[] }, {"name":"withMaxAge","parameterTypes":["int","java.time.temporal.ChronoUnit"] }, {"name":"withTransformation","parameterTypes":["java.lang.Class"] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.ParamProvider", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"software.amazon.lambda.powertools.parameters.dynamodb.DynamoDbParamAspect", + "fields":[{"name":"providerBuilder"}] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.dynamodb.DynamoDbProvider", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getMultipleValues","parameterTypes":["java.lang.String"] }, {"name":"getValue","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.transform.TransformationManager", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"performBasicTransformation","parameterTypes":["java.lang.String"] }, {"name":"performComplexTransformation","parameterTypes":["java.lang.String","java.lang.Class"] }, {"name":"setTransformer","parameterTypes":["java.lang.Class"] }, {"name":"shouldTransform","parameterTypes":[] }] +}, +{ + "name":"sun.reflect.ReflectionFactory", + "methods":[{"name":"getReflectionFactory","parameterTypes":[] }, {"name":"newConstructorForSerialization","parameterTypes":["java.lang.Class","java.lang.reflect.Constructor"] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.SHA", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.X509Factory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAKeyFactory$Legacy", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.SSLContextImpl$TLSContext", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.x509.AuthorityInfoAccessExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.AuthorityKeyIdentifierExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.BasicConstraintsExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CRLDistributionPointsExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CertificatePoliciesExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.ExtendedKeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.IssuerAlternativeNameExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.KeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.NetscapeCertTypeExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.PrivateKeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectAlternativeNameExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectKeyIdentifierExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.dynamodb.internal.ParametersDynamodbUserAgentInterceptor", + "methods":[{"name":"<init>","parameterTypes":[] }] +} +] diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-dynamodb/resource-config.json b/powertools-parameters/powertools-parameters-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-dynamodb/resource-config.json new file mode 100644 index 000000000..abbae66cf --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-dynamodb/resource-config.json @@ -0,0 +1,23 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/global/handlers/execution.interceptors\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/services/dynamodb/execution.interceptors\\E" + }, { + "pattern":"\\Qversion.properties\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/nfc.nrm\\E" + }]}, + "bundles":[] +} diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-parameters/powertools-parameters-dynamodb/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..f7de6e9be --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.parameters.dynamodb.internal.ParametersDynamodbUserAgentInterceptor diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParamAspectTest.java b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParamAspectTest.java new file mode 100644 index 000000000..4294eca48 --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParamAspectTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.dynamodb; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Function; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class DynamoDbParamAspectTest { + + @Test + void parameterInjectedByProvider() throws Exception { + // Setup our aspect to return a mocked DynamoDbProvider + String tableName = "my-test-tablename"; + String key = "myKey"; + String value = "myValue"; + DynamoDbProvider provider = Mockito.mock(DynamoDbProvider.class); + + Function<String, DynamoDbProvider> providerBuilder = (String table) -> { + if (table.equals(tableName)) { + return provider; + } + throw new RuntimeException("Whoops! Asked for an app/env that we weren't configured for"); + }; + writeStaticField(DynamoDbParamAspect.class, "providerBuilder", providerBuilder, true); + + // Setup our mocked DynamoDbProvider to return a value for our test data + Mockito.when(provider.get(key)).thenReturn(value); + + // Create an instance of a class and let the AppConfigParametersAspect inject it + MyInjectedClass obj = new MyInjectedClass(); + assertThat(obj.myParameter).isEqualTo(value); + } + + class MyInjectedClass { + @DynamoDbParam(table = "my-test-tablename", key = "myKey") + public String myParameter; + } + +} diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderE2ETest.java b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderE2ETest.java new file mode 100644 index 000000000..af2617edf --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderE2ETest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.dynamodb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; + +/** + * This class provides simple end-to-end style testing of the DynamoDBProvider class. + * It is ignored, for now, as it requires AWS access and that's not yet run as part + * of our unit test suite in the cloud. + * <p> + * The test is kept here for 1/ local development and 2/ in preparation for future + * E2E tests running in the cloud CI. Once the E2E test structure is merged we + * will move this across. + */ +@Disabled +class DynamoDbProviderE2ETest { + + private static final String PARAMS_TEST_TABLE = "ddb-params-test"; + private static final String MULTI_PARAMS_TEST_TABLE = "ddb-multiparams-test"; + private final DynamoDbClient ddbClient; + + public DynamoDbProviderE2ETest() { + // Create a DDB client to inject test data into our test tables + ddbClient = DynamoDbClient.builder() + .httpClientBuilder(UrlConnectionHttpClient.builder()) + .build(); + + + } + + @Test + void TestGetValue() { + + // Arrange + Map<String, AttributeValue> testItem = new HashMap<>(); + testItem.put("id", AttributeValue.fromS("test_param")); + testItem.put("value", AttributeValue.fromS("the_value_is_hello!")); + ddbClient.putItem(PutItemRequest.builder() + .tableName(PARAMS_TEST_TABLE) + .item(testItem) + .build()); + + // Act + DynamoDbProvider provider = makeProvider(PARAMS_TEST_TABLE); + String value = provider.getValue("test_param"); + + // Assert + assertThat(value).isEqualTo("the_value_is_hello!"); + } + + @Test + void TestGetValues() { + + // Arrange + Map<String, AttributeValue> testItem = new HashMap<>(); + testItem.put("id", AttributeValue.fromS("test_param")); + testItem.put("sk", AttributeValue.fromS("test_param_part_1")); + testItem.put("value", AttributeValue.fromS("the_value_is_hello!")); + ddbClient.putItem(PutItemRequest.builder() + .tableName(MULTI_PARAMS_TEST_TABLE) + .item(testItem) + .build()); + + Map<String, AttributeValue> testItem2 = new HashMap<>(); + testItem2.put("id", AttributeValue.fromS("test_param")); + testItem2.put("sk", AttributeValue.fromS("test_param_part_2")); + testItem2.put("value", AttributeValue.fromS("the_value_is_still_hello!")); + ddbClient.putItem(PutItemRequest.builder() + .tableName(MULTI_PARAMS_TEST_TABLE) + .item(testItem2) + .build()); + + // Act + DynamoDbProvider provider = makeProvider(MULTI_PARAMS_TEST_TABLE); + Map<String, String> values = provider.getMultipleValues("test_param"); + + // Assert + assertThat(values.size()).isEqualTo(2); + assertThat(values.get("test_param_part_1")).isEqualTo("the_value_is_hello!"); + assertThat(values.get("test_param_part_2")).isEqualTo("the_value_is_still_hello!"); + } + + private DynamoDbProvider makeProvider(String tableName) { + return new DynamoDbProvider(new CacheManager(), null, DynamoDbClient.builder() + .httpClientBuilder(UrlConnectionHttpClient.builder()).build(), + tableName); + } + +} diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderTest.java b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderTest.java new file mode 100644 index 000000000..64f29db79 --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderTest.java @@ -0,0 +1,245 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.dynamodb; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.MockitoAnnotations.openMocks; +import static software.amazon.lambda.powertools.parameters.transform.Transformer.json; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.junit.jupiter.api.Assertions; +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.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryResponse; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.dynamodb.exception.DynamoDbProviderSchemaException; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +@ExtendWith(MockitoExtension.class) +class DynamoDbProviderTest { + + private static final String TABLE_NAME = "ddb-test-table"; + + @Mock + DynamoDbClient client; + + @Mock + TransformationManager transformationManager; + + @Captor + ArgumentCaptor<GetItemRequest> getItemValueCaptor; + + @Captor + ArgumentCaptor<QueryRequest> queryRequestCaptor; + + private DynamoDbProvider provider; + + @BeforeEach + void init() { + openMocks(this); + CacheManager cacheManager = new CacheManager(); + provider = new DynamoDbProvider(cacheManager, transformationManager, client, TABLE_NAME); + } + + @Test + void getValue() { + + // Arrange + String key = "Key1"; + String expectedValue = "Value1"; + Map<String, AttributeValue> responseData = new HashMap<>(); + responseData.put("id", AttributeValue.fromS(key)); + responseData.put("value", AttributeValue.fromS(expectedValue)); + GetItemResponse response = GetItemResponse.builder() + .item(responseData) + .build(); + Mockito.when(client.getItem(getItemValueCaptor.capture())).thenReturn(response); + + // Act + String value = provider.getValue(key); + + // Assert + assertThat(value).isEqualTo(expectedValue); + assertThat(getItemValueCaptor.getValue().tableName()).isEqualTo(TABLE_NAME); + assertThat(getItemValueCaptor.getValue().key().get("id").s()).isEqualTo(key); + } + + @Test + void getValueWithNullResultsReturnsNull() { + // Arrange + Mockito.when(client.getItem(getItemValueCaptor.capture())).thenReturn(GetItemResponse.builder() + .item(null) + .build()); + + // Act + String value = provider.getValue("key"); + + // Assert + assertThat(value).isEqualTo(null); + } + + @Test + void getValueWithoutResultsReturnsNull() { + // Arrange + Mockito.when(client.getItem(getItemValueCaptor.capture())).thenReturn(GetItemResponse.builder() + .item(new HashMap<>()) + .build()); + + // Act + String value = provider.getValue("key"); + + // Assert + assertThat(value).isEqualTo(null); + } + + @Test + void getValueWithMalformedRowThrows() { + // Arrange + String key = "Key1"; + Map<String, AttributeValue> responseData = new HashMap<>(); + responseData.put("id", AttributeValue.fromS(key)); + responseData.put("not-value", AttributeValue.fromS("something")); + Mockito.when(client.getItem(getItemValueCaptor.capture())).thenReturn(GetItemResponse.builder() + .item(responseData) + .build()); + // Act + Assertions.assertThrows(DynamoDbProviderSchemaException.class, () -> { + provider.getValue(key); + }); + } + + @Test + void getValues() { + + // Arrange + String key = "Key1"; + String subkey1 = "Subkey1"; + String val1 = "Val1"; + String subkey2 = "Subkey2"; + String val2 = "Val2"; + Map<String, AttributeValue> item1 = new HashMap<>(); + item1.put("id", AttributeValue.fromS(key)); + item1.put("sk", AttributeValue.fromS(subkey1)); + item1.put("value", AttributeValue.fromS(val1)); + Map<String, AttributeValue> item2 = new HashMap<>(); + item2.put("id", AttributeValue.fromS(key)); + item2.put("sk", AttributeValue.fromS(subkey2)); + item2.put("value", AttributeValue.fromS(val2)); + QueryResponse response = QueryResponse.builder() + .items(item1, item2) + .build(); + Mockito.when(client.query(queryRequestCaptor.capture())).thenReturn(response); + + // Act + Map<String, String> values = provider.getMultipleValues(key); + + // Assert + assertThat(values.size()).isEqualTo(2); + assertThat(values.get(subkey1)).isEqualTo(val1); + assertThat(values.get(subkey2)).isEqualTo(val2); + assertThat(queryRequestCaptor.getValue().tableName()).isEqualTo(TABLE_NAME); + assertThat(queryRequestCaptor.getValue().keyConditionExpression()).isEqualTo("id = :v_id"); + assertThat(queryRequestCaptor.getValue().expressionAttributeValues().get(":v_id").s()).isEqualTo(key); + } + + @Test + void getValuesWithoutResultsReturnsNull() { + // Arrange + Mockito.when(client.query(queryRequestCaptor.capture())).thenReturn( + QueryResponse.builder().items().build()); + + // Act + Map<String, String> values = provider.getMultipleValues(UUID.randomUUID().toString()); + + // Assert + assertThat(values.size()).isEqualTo(0); + } + + @Test + void getMultipleValuesMissingSortKey_throwsException() { + // Arrange + String key = "Key1"; + Map<String, AttributeValue> item = new HashMap<>(); + item.put("id", AttributeValue.fromS(key)); + item.put("value", AttributeValue.fromS("somevalue")); + QueryResponse response = QueryResponse.builder() + .items(item) + .build(); + Mockito.when(client.query(queryRequestCaptor.capture())).thenReturn(response); + + // Assert + Assertions.assertThrows(DynamoDbProviderSchemaException.class, () -> { + // Act + provider.getMultipleValues(key); + }); + } + + @Test + void getValuesWithMalformedRowThrows() { + // Arrange + String key = "Key1"; + Map<String, AttributeValue> item1 = new HashMap<>(); + item1.put("id", AttributeValue.fromS(key)); + item1.put("sk", AttributeValue.fromS("some-subkey")); + item1.put("not-value", AttributeValue.fromS("somevalue")); + QueryResponse response = QueryResponse.builder() + .items(item1) + .build(); + Mockito.when(client.query(queryRequestCaptor.capture())).thenReturn(response); + + // Assert + Assertions.assertThrows(DynamoDbProviderSchemaException.class, () -> { + // Act + provider.getMultipleValues(key); + }); + } + + @Test + void testDynamoDBBuilderMissingTable_throwsException() { + + // Act & Assert + assertThatIllegalStateException().isThrownBy(() -> DynamoDbProvider.builder() + .withCacheManager(new CacheManager()) + .build()); + } + + @Test + void testDynamoDBBuilder_withoutParameter_shouldHaveDefaultTransformationManager() { + + // Act + DynamoDbProvider dynamoDbProvider = DynamoDbProvider.builder().withTable("test-table") + .build(); + // Assert + assertDoesNotThrow(() -> dynamoDbProvider.withTransformation(json)); + } + +} diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/internal/ParametersDynamodbUserAgentInterceptorTest.java b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/internal/ParametersDynamodbUserAgentInterceptorTest.java new file mode 100644 index 000000000..f9c8ebea5 --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/internal/ParametersDynamodbUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.parameters.dynamodb.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class ParametersDynamodbUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/PARAMETERS-DYNAMODB/"); + } +} \ No newline at end of file diff --git a/powertools-parameters/powertools-parameters-secrets/pom.xml b/powertools-parameters/powertools-parameters-secrets/pom.xml new file mode 100644 index 000000000..b9535269e --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/pom.xml @@ -0,0 +1,186 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parent</artifactId> + <version>2.9.0</version> + <relativePath>../../pom.xml</relativePath> + </parent> + + <artifactId>powertools-parameters-secrets</artifactId> + <name>Powertools for AWS Lambda (Java) library Parameters - Secrets Manager</name> + <description>Secrets Manager implementation for the Parameters module</description> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>secretsmanager</artifactId> + <exclusions> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>apache-client</artifactId> + </exclusion> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>netty-nio-client</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjweaver</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>generate-graalvm-files</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-secrets,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + + <!-- Required explicitly for @Captor ArgumentCaptor --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-parameters-secrets</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + + <build> + <plugins> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <AWS_REGION>eu-central-1</AWS_REGION> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParam.java b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParam.java new file mode 100644 index 000000000..f9c110c49 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParam.java @@ -0,0 +1,46 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.secrets; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import software.amazon.lambda.powertools.parameters.transform.Transformer; + +/** + * Inject a parameter from the Secrets Manager into a field. You can also use + * {@code SecretsProviderBuilder} to obtain Secrets Manager values directly, rather than + * injecting them implicitly. + * + * <pre> + * @SecretsParam(key = "my-secret") + * String mySecret; + * </pre> + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SecretsParam { + /** + * <b>Mandatory</b>. key from the secrets manager store. + * @return + */ + String key(); + + /** + * <b>Optional</b>. a transfer to apply to the value + */ + Class<? extends Transformer> transformer() default Transformer.class; +} diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParamAspect.java b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParamAspect.java new file mode 100644 index 000000000..748c88cc9 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParamAspect.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.secrets; + +import java.util.function.Supplier; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.FieldSignature; +import software.amazon.lambda.powertools.parameters.BaseParamAspect; + +/** + * Provides the Secrets parameter aspect. This aspect is responsible for injecting + * parameters from AWS Secrets Manager into fields annotated with @SecretsParam. See the + * README and Powertools for Lambda (Java) documentation for information on using this feature. + */ +@Aspect +public class SecretsParamAspect extends BaseParamAspect { + + private static Supplier<SecretsProvider> providerBuilder = () -> SecretsProvider.builder() + .build(); + + @Pointcut("get(* *) && @annotation(secretsParam)") + public void getParam(SecretsParam secretsParam) { + } + + @Around("getParam(secretsParam)") + public Object injectParam(final ProceedingJoinPoint joinPoint, final SecretsParam secretsParam) { + + SecretsProvider provider = providerBuilder.get(); + return getAndTransform(secretsParam.key(), secretsParam.transformer(), provider, + (FieldSignature) joinPoint.getSignature()); + } + +} diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProvider.java b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProvider.java new file mode 100644 index 000000000..9087c1ad6 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProvider.java @@ -0,0 +1,115 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.secrets; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.util.Base64; +import java.util.Map; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; +import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * AWS Secrets Manager Parameter Provider<br/><br/> + * + * <u>Samples:</u> + * <pre> + * SecretsProvider provider = SecretsProvider.builder().build(); + * + * String value = provider.get("key"); + * System.out.println(value); + * >>> "value" + * + * // Get a value and cache it for 30 seconds (all others values will now be cached for 30 seconds) + * String value = provider.defaultMaxAge(30, ChronoUnit.SECONDS).get("key"); + * + * // Get a value and cache it for 1 minute (all others values are cached for 5 seconds by default) + * String value = provider.withMaxAge(1, ChronoUnit.MINUTES).get("key"); + * + * // Get a base64 encoded value, decoded into a String, and store it in the cache + * String value = provider.withTransformation(Transformer.base64).get("key"); + * + * // Get a json value, transform it into an Object, and store it in the cache + * TargetObject = provider.withTransformation(Transformer.json).get("key", TargetObject.class); + * </pre> + */ +public class SecretsProvider extends BaseProvider { + + private final SecretsManagerClient client; + + /** + * Use the {@link SecretsProviderBuilder} to create an instance! + * + * @param client custom client you would like to use. + */ + SecretsProvider(CacheManager cacheManager, TransformationManager transformationManager, + SecretsManagerClient client) { + super(cacheManager, transformationManager); + this.client = client; + } + + /** + * Create a builder that can be used to configure and create a {@link SecretsProvider}. + * + * @return a new instance of {@link SecretsProviderBuilder} + */ + public static SecretsProviderBuilder builder() { + return new SecretsProviderBuilder(); + } + + /** + * Create a SecretsProvider with all default settings. + */ + public static SecretsProvider create() { + return new SecretsProviderBuilder().build(); + } + + /** + * Retrieve the parameter value from the AWS Secrets Manager. + * + * @param key key of the parameter + * @return the value of the parameter identified by the key + */ + @Override + protected String getValue(String key) { + GetSecretValueRequest request = GetSecretValueRequest.builder().secretId(key).build(); + + String secretValue = client.getSecretValue(request).secretString(); + if (secretValue == null) { + secretValue = + new String(Base64.getDecoder().decode(client.getSecretValue(request).secretBinary().asByteArray()), + UTF_8); + } + return secretValue; + } + + /** + * @throws UnsupportedOperationException as it is not possible to get multiple values simultaneously from Secrets Manager + */ + @Override + protected Map<String, String> getMultipleValues(String path) { + throw new UnsupportedOperationException("Impossible to get multiple values from AWS Secrets Manager"); + } + + + // For test purpose only + SecretsManagerClient getClient() { + return client; + } + +} diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProviderBuilder.java b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProviderBuilder.java new file mode 100644 index 000000000..517274e19 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProviderBuilder.java @@ -0,0 +1,102 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.secrets; + +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.ParamProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * Implements a {@link ParamProvider} on top of the SecretsManager service. SecretsManager provides + */ +public class SecretsProviderBuilder { + + private SecretsManagerClient client; + private CacheManager cacheManager; + private TransformationManager transformationManager; + + private static SecretsManagerClient createClient() { + return SecretsManagerClient.builder() + .httpClientBuilder(UrlConnectionHttpClient.builder()) + .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, + UserAgentConfigurator.getUserAgent(BaseProvider.PARAMETERS)).build()) + .build(); + } + + /** + * Create a {@link SecretsProvider} instance. + * + * @return a {@link SecretsProvider} + */ + public SecretsProvider build() { + if (cacheManager == null) { + cacheManager = new CacheManager(); + } + SecretsProvider provider; + if (client == null) { + client = createClient(); + } + if(transformationManager == null){ + transformationManager = new TransformationManager(); + } + provider = new SecretsProvider(cacheManager, transformationManager, client); + + return provider; + } + + /** + * Set custom {@link SecretsManagerClient} to pass to the {@link SecretsProvider}. <br/> + * Use it if you want to customize the region or any other part of the client. + * + * @param client Custom client + * @return the builder to chain calls (eg. <pre>builder.withClient().build()</pre>) + */ + public SecretsProviderBuilder withClient(SecretsManagerClient client) { + this.client = client; + return this; + } + + /** + * Provide a CacheManager to the {@link SecretsProvider} + * + * @param cacheManager the manager that will handle the cache of parameters + * @return the builder to chain calls (eg. <pre>builder.withCacheManager().build()</pre>) + */ + public SecretsProviderBuilder withCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + return this; + } + + /** + * Provide a transformationManager to the {@link SecretsProvider} + * + * @param transformationManager the manager that will handle transformation of parameters + * @return the builder to chain calls (eg. <pre>builder.withTransformationManager().build()</pre>) + */ + public SecretsProviderBuilder withTransformationManager(TransformationManager transformationManager) { + this.transformationManager = transformationManager; + return this; + } +} diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/internal/ParametersSecretsUserAgentInterceptor.java b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/internal/ParametersSecretsUserAgentInterceptor.java new file mode 100644 index 000000000..e3eda5889 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/internal/ParametersSecretsUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.parameters.secrets.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-parameters-secrets module is on the classpath. + */ +public final class ParametersSecretsUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("parameters-secrets"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-secrets/jni-config.json b/powertools-parameters/powertools-parameters-secrets/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-secrets/jni-config.json new file mode 100644 index 000000000..2689203aa --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-secrets/jni-config.json @@ -0,0 +1,22 @@ +[ +{ + "name":"java.lang.Boolean", + "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.String", + "methods":[{"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }] +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"sun.instrument.InstrumentationImpl", + "methods":[{"name":"<init>","parameterTypes":["long","boolean","boolean","boolean"] }, {"name":"loadClassAndCallAgentmain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"loadClassAndCallPremain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"transform","parameterTypes":["java.lang.Module","java.lang.ClassLoader","java.lang.String","java.lang.Class","java.security.ProtectionDomain","byte[]","boolean"] }] +}, +{ + "name":"sun.management.VMManagementImpl", + "fields":[{"name":"compTimeMonitoringSupport"}, {"name":"currentThreadCpuTimeSupport"}, {"name":"objectMonitorUsageSupport"}, {"name":"otherThreadCpuTimeSupport"}, {"name":"remoteDiagnosticCommandsSupport"}, {"name":"synchronizerUsageSupport"}, {"name":"threadAllocatedMemorySupport"}, {"name":"threadContentionMonitoringSupport"}] +} +] diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-secrets/reflect-config.json b/powertools-parameters/powertools-parameters-secrets/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-secrets/reflect-config.json new file mode 100644 index 000000000..097a30784 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-secrets/reflect-config.json @@ -0,0 +1,327 @@ +[ +{ + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ARCFOURCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESedeCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.tools.attach.VirtualMachine" +}, +{ + "name":"java.io.Serializable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.AutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.Class", + "methods":[{"name":"forName","parameterTypes":["java.lang.String"] }, {"name":"getAnnotatedInterfaces","parameterTypes":[] }, {"name":"getAnnotatedSuperclass","parameterTypes":[] }, {"name":"getDeclaredMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getModule","parameterTypes":[] }, {"name":"getNestHost","parameterTypes":[] }, {"name":"getNestMembers","parameterTypes":[] }, {"name":"getPermittedSubclasses","parameterTypes":[] }, {"name":"getRecordComponents","parameterTypes":[] }, {"name":"isNestmateOf","parameterTypes":["java.lang.Class"] }, {"name":"isRecord","parameterTypes":[] }, {"name":"isSealed","parameterTypes":[] }] +}, +{ + "name":"java.lang.ClassLoader", + "methods":[{"name":"getDefinedPackage","parameterTypes":["java.lang.String"] }, {"name":"getUnnamedModule","parameterTypes":[] }, {"name":"registerAsParallelCapable","parameterTypes":[] }] +}, +{ + "name":"java.lang.Module", + "methods":[{"name":"addExports","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"addReads","parameterTypes":["java.lang.Module"] }, {"name":"canRead","parameterTypes":["java.lang.Module"] }, {"name":"getClassLoader","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPackages","parameterTypes":[] }, {"name":"getResourceAsStream","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"isNamed","parameterTypes":[] }, {"name":"isOpen","parameterTypes":["java.lang.String","java.lang.Module"] }] +}, +{ + "name":"java.lang.Object", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"clone","parameterTypes":[] }, {"name":"getClass","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"java.lang.ProcessHandle", + "methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime", + "methods":[{"name":"version","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime$Version", + "methods":[{"name":"feature","parameterTypes":[] }] +}, +{ + "name":"java.lang.StackWalker" +}, +{ + "name":"java.lang.String" +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getSecurityManager","parameterTypes":[] }] +}, +{ + "name":"java.lang.annotation.Retention", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.annotation.Target", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.invoke.MethodHandle", + "methods":[{"name":"bindTo","parameterTypes":["java.lang.Object"] }, {"name":"invokeWithArguments","parameterTypes":["java.lang.Object[]"] }] +}, +{ + "name":"java.lang.invoke.MethodHandles", + "methods":[{"name":"lookup","parameterTypes":[] }] +}, +{ + "name":"java.lang.invoke.MethodHandles$Lookup", + "methods":[{"name":"findVirtual","parameterTypes":["java.lang.Class","java.lang.String","java.lang.invoke.MethodType"] }] +}, +{ + "name":"java.lang.invoke.MethodType", + "methods":[{"name":"methodType","parameterTypes":["java.lang.Class","java.lang.Class[]"] }] +}, +{ + "name":"java.lang.reflect.AccessibleObject", + "methods":[{"name":"setAccessible","parameterTypes":["boolean"] }] +}, +{ + "name":"java.lang.reflect.AnnotatedArrayType", + "methods":[{"name":"getAnnotatedGenericComponentType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedParameterizedType", + "methods":[{"name":"getAnnotatedActualTypeArguments","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedType", + "methods":[{"name":"getType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedTypeVariable", + "methods":[{"name":"getAnnotatedBounds","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedWildcardType", + "methods":[{"name":"getAnnotatedUpperBounds","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Executable", + "methods":[{"name":"getAnnotatedExceptionTypes","parameterTypes":[] }, {"name":"getAnnotatedParameterTypes","parameterTypes":[] }, {"name":"getAnnotatedReceiverType","parameterTypes":[] }, {"name":"getParameterCount","parameterTypes":[] }, {"name":"getParameters","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"getAnnotatedReturnType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Parameter", + "methods":[{"name":"getModifiers","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"isNamePresent","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.TypeVariable", + "methods":[{"name":"getAnnotatedBounds","parameterTypes":[] }] +}, +{ + "name":"java.security.AccessController", + "methods":[{"name":"doPrivileged","parameterTypes":["java.security.PrivilegedAction"] }, {"name":"doPrivileged","parameterTypes":["java.security.PrivilegedExceptionAction"] }] +}, +{ + "name":"java.security.AlgorithmParametersSpi" +}, +{ + "name":"java.security.KeyStoreSpi" +}, +{ + "name":"java.security.SecureRandomParameters" +}, +{ + "name":"java.util.concurrent.ForkJoinTask", + "fields":[{"name":"aux"}, {"name":"status"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"javax.security.auth.x500.X500Principal", + "fields":[{"name":"thisX500Name"}], + "methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }] +}, +{ + "name":"jdk.internal.misc.Unsafe" +}, +{ + "name":"kotlin.jvm.JvmInline" +}, +{ + "name":"org.apiguardian.api.API", + "queryAllPublicMethods":true +}, +{ + "name":"scala.util.Properties" +}, +{ + "name":"software.amazon.awssdk.awscore.AwsClient", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"software.amazon.awssdk.core.SdkClient", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"serviceName","parameterTypes":[] }] +}, +{ + "name":"software.amazon.awssdk.services.secretsmanager.SecretsManagerClient", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"batchGetSecretValue","parameterTypes":["java.util.function.Consumer"] }, {"name":"batchGetSecretValue","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.BatchGetSecretValueRequest"] }, {"name":"batchGetSecretValuePaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"batchGetSecretValuePaginator","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.BatchGetSecretValueRequest"] }, {"name":"cancelRotateSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"cancelRotateSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.CancelRotateSecretRequest"] }, {"name":"createSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"createSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest"] }, {"name":"deleteResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteResourcePolicy","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.DeleteResourcePolicyRequest"] }, {"name":"deleteSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.DeleteSecretRequest"] }, {"name":"describeSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.DescribeSecretRequest"] }, {"name":"getRandomPassword","parameterTypes":[] }, {"name":"getRandomPassword","parameterTypes":["java.util.function.Consumer"] }, {"name":"getRandomPassword","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.GetRandomPasswordRequest"] }, {"name":"getResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"getResourcePolicy","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.GetResourcePolicyRequest"] }, {"name":"getSecretValue","parameterTypes":["java.util.function.Consumer"] }, {"name":"getSecretValue","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest"] }, {"name":"listSecretVersionIds","parameterTypes":["java.util.function.Consumer"] }, {"name":"listSecretVersionIds","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.ListSecretVersionIdsRequest"] }, {"name":"listSecretVersionIdsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listSecretVersionIdsPaginator","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.ListSecretVersionIdsRequest"] }, {"name":"listSecrets","parameterTypes":[] }, {"name":"listSecrets","parameterTypes":["java.util.function.Consumer"] }, {"name":"listSecrets","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.ListSecretsRequest"] }, {"name":"listSecretsPaginator","parameterTypes":[] }, {"name":"listSecretsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listSecretsPaginator","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.ListSecretsRequest"] }, {"name":"putResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"putResourcePolicy","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.PutResourcePolicyRequest"] }, {"name":"putSecretValue","parameterTypes":["java.util.function.Consumer"] }, {"name":"putSecretValue","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.PutSecretValueRequest"] }, {"name":"removeRegionsFromReplication","parameterTypes":["java.util.function.Consumer"] }, {"name":"removeRegionsFromReplication","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.RemoveRegionsFromReplicationRequest"] }, {"name":"replicateSecretToRegions","parameterTypes":["java.util.function.Consumer"] }, {"name":"replicateSecretToRegions","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.ReplicateSecretToRegionsRequest"] }, {"name":"restoreSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"restoreSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.RestoreSecretRequest"] }, {"name":"rotateSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"rotateSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.RotateSecretRequest"] }, {"name":"serviceClientConfiguration","parameterTypes":[] }, {"name":"stopReplicationToReplica","parameterTypes":["java.util.function.Consumer"] }, {"name":"stopReplicationToReplica","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.StopReplicationToReplicaRequest"] }, {"name":"tagResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"tagResource","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.TagResourceRequest"] }, {"name":"untagResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"untagResource","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.UntagResourceRequest"] }, {"name":"updateSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.UpdateSecretRequest"] }, {"name":"updateSecretVersionStage","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateSecretVersionStage","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.UpdateSecretVersionStageRequest"] }, {"name":"validateResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"validateResourcePolicy","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.ValidateResourcePolicyRequest"] }] +}, +{ + "name":"software.amazon.awssdk.utils.SdkAutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"close","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.BaseProvider", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"get","parameterTypes":["java.lang.String"] }, {"name":"get","parameterTypes":["java.lang.String","java.lang.Class"] }, {"name":"getMultiple","parameterTypes":["java.lang.String"] }, {"name":"now","parameterTypes":[] }, {"name":"resetToDefaults","parameterTypes":[] }, {"name":"withMaxAge","parameterTypes":["int","java.time.temporal.ChronoUnit"] }, {"name":"withTransformation","parameterTypes":["java.lang.Class"] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.ParamProvider", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"software.amazon.lambda.powertools.parameters.secrets.SecretsParamAspect", + "fields":[{"name":"providerBuilder"}] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.secrets.SecretsProvider", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getMultipleValues","parameterTypes":["java.lang.String"] }, {"name":"getValue","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.transform.TransformationManager", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"performBasicTransformation","parameterTypes":["java.lang.String"] }, {"name":"performComplexTransformation","parameterTypes":["java.lang.String","java.lang.Class"] }, {"name":"setTransformer","parameterTypes":["java.lang.Class"] }, {"name":"shouldTransform","parameterTypes":[] }] +}, +{ + "name":"sun.reflect.ReflectionFactory", + "methods":[{"name":"getReflectionFactory","parameterTypes":[] }, {"name":"newConstructorForSerialization","parameterTypes":["java.lang.Class","java.lang.reflect.Constructor"] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.SHA", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.X509Factory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAKeyFactory$Legacy", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.SSLContextImpl$TLSContext", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.x509.AuthorityInfoAccessExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.AuthorityKeyIdentifierExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.BasicConstraintsExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CRLDistributionPointsExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CertificatePoliciesExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.ExtendedKeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.IssuerAlternativeNameExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.KeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.NetscapeCertTypeExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.PrivateKeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectAlternativeNameExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectKeyIdentifierExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.secrets.internal.ParametersSecretsUserAgentInterceptor", + "methods":[{"name":"<init>","parameterTypes":[] }] +} +] diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-secrets/resource-config.json b/powertools-parameters/powertools-parameters-secrets/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-secrets/resource-config.json new file mode 100644 index 000000000..f914dbf44 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-secrets/resource-config.json @@ -0,0 +1,23 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/global/handlers/execution.interceptors\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/services/secretsmanager/execution.interceptors\\E" + }, { + "pattern":"\\Qversion.properties\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/nfc.nrm\\E" + }]}, + "bundles":[] +} diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-parameters/powertools-parameters-secrets/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..ca08369c5 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.parameters.secrets.internal.ParametersSecretsUserAgentInterceptor diff --git a/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParamAspectTest.java b/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParamAspectTest.java new file mode 100644 index 000000000..5523cbb0e --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParamAspectTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.secrets; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Supplier; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class SecretsParamAspectTest { + + @Test + void parameterInjectedByProvider() throws Exception { + // Setup our aspect to return a mocked SecretsProvider + String key = "myKey"; + String value = "mySecretValue"; + SecretsProvider provider = Mockito.mock(SecretsProvider.class); + + Supplier<SecretsProvider> providerBuilder = () -> provider; + writeStaticField(SecretsParamAspect.class, "providerBuilder", providerBuilder, true); + + // Setup our mocked SecretsProvider to return a value for our test data + Mockito.when(provider.get(key)).thenReturn(value); + + // Create an instance of a class and let the SecretsParamAspect inject it + MyInjectedClass obj = new MyInjectedClass(); + assertThat(obj.mySecret).isEqualTo(value); + } + + class MyInjectedClass { + @SecretsParam(key = "myKey") + public String mySecret; + } + +} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SecretsProviderTest.java b/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProviderTest.java similarity index 55% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SecretsProviderTest.java rename to powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProviderTest.java index 611f05fa9..a6fbe1c8e 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SecretsProviderTest.java +++ b/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,30 +11,43 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.parameters; + +package software.amazon.lambda.powertools.parameters.secrets; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatRuntimeException; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static software.amazon.lambda.powertools.parameters.transform.Transformer.json; + +import java.time.temporal.ChronoUnit; +import java.util.Base64; 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.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse; import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; -import java.util.Base64; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.MockitoAnnotations.openMocks; - -public class SecretsProviderTest { +@ExtendWith(MockitoExtension.class) +class SecretsProviderTest { @Mock SecretsManagerClient client; + @Mock + TransformationManager transformationManager; + @Captor ArgumentCaptor<GetSecretValueRequest> paramCaptor; @@ -43,18 +56,18 @@ public class SecretsProviderTest { SecretsProvider provider; @BeforeEach - public void init() { - openMocks(this); + void init() { cacheManager = new CacheManager(); - provider = new SecretsProvider(cacheManager, client); + provider = new SecretsProvider(cacheManager, transformationManager, client); } @Test - public void getValue() { + void getValue() { String key = "Key1"; String expectedValue = "Value1"; GetSecretValueResponse response = GetSecretValueResponse.builder().secretString(expectedValue).build(); Mockito.when(client.getSecretValue(paramCaptor.capture())).thenReturn(response); + provider.withMaxAge(2, ChronoUnit.DAYS); String value = provider.getValue(key); @@ -63,11 +76,12 @@ public void getValue() { } @Test - public void getValueBase64() { + void getValueBase64() { String key = "Key2"; String expectedValue = "Value2"; byte[] valueb64 = Base64.getEncoder().encode(expectedValue.getBytes()); - GetSecretValueResponse response = GetSecretValueResponse.builder().secretBinary(SdkBytes.fromByteArray(valueb64)).build(); + GetSecretValueResponse response = GetSecretValueResponse.builder() + .secretBinary(SdkBytes.fromByteArray(valueb64)).build(); Mockito.when(client.getSecretValue(paramCaptor.capture())).thenReturn(response); String value = provider.getValue(key); @@ -75,4 +89,31 @@ public void getValueBase64() { assertThat(value).isEqualTo(expectedValue); assertThat(paramCaptor.getValue().secretId()).isEqualTo(key); } + + @Test + void getMultipleValuesThrowsException() { + // Act & Assert + assertThatRuntimeException().isThrownBy(() -> provider.getMultipleValues("path")) + .withMessage("Impossible to get multiple values from AWS Secrets Manager"); + } + + @Test + void testGetSecretsProvider_withoutParameter_shouldCreateDefaultClient() { + // Act + SecretsProvider secretsProvider = SecretsProvider.builder() + .build(); + + // Assert + assertNotNull(secretsProvider); + assertNotNull(secretsProvider.getClient()); + } + + @Test + void testGetSecretsProvider_withoutParameter_shouldHaveDefaultTransformationManager() { + // Act + SecretsProvider secretsProvider = SecretsProvider.builder() + .build(); + // Assert + assertDoesNotThrow(() -> secretsProvider.withTransformation(json)); + } } diff --git a/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/internal/ParametersSecretsUserAgentInterceptorTest.java b/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/internal/ParametersSecretsUserAgentInterceptorTest.java new file mode 100644 index 000000000..6d59ff717 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/internal/ParametersSecretsUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.parameters.secrets.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class ParametersSecretsUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/PARAMETERS-SECRETS/"); + } +} \ No newline at end of file diff --git a/powertools-parameters/powertools-parameters-ssm/pom.xml b/powertools-parameters/powertools-parameters-ssm/pom.xml new file mode 100644 index 000000000..e0253e10b --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/pom.xml @@ -0,0 +1,186 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parent</artifactId> + <version>2.9.0</version> + <relativePath>../../pom.xml</relativePath> + </parent> + + <artifactId>powertools-parameters-ssm</artifactId> + <name>Powertools for AWS Lambda (Java) library Parameters - SSM</name> + <description>SSM Parameter Store implementation for the Parameters module</description> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>ssm</artifactId> + <exclusions> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>apache-client</artifactId> + </exclusion> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>netty-nio-client</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjweaver</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <AWS_REGION>eu-central-1</AWS_REGION> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>generate-graalvm-files</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-ssm,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + + <!-- Required explicitly for @Captor ArgumentCaptor --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-parameters-ssm</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMParam.java b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMParam.java new file mode 100644 index 000000000..9b1587bb4 --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMParam.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.ssm; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import software.amazon.lambda.powertools.parameters.transform.Transformer; + +/** + * Inject a parameter from the SSM Parameter Store into a field. You can also use + * {@code SSMProviderBuilder} to obtain SSM values directly, rather than injecting them implicitly. + * + * Usage: + * <pre> + * @SSMParam(key = "/my/parameter") + * String myParameter; + * </pre> + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SSMParam { + /** + * <b>Mandatory</b>. Key from the SSM parameter store + * @return + */ + String key(); + + + /** + * <b>Optional</b>. a transfer to apply to the value + */ + Class<? extends Transformer> transformer() default Transformer.class; +} diff --git a/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMParamAspect.java b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMParamAspect.java new file mode 100644 index 000000000..b4d370506 --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMParamAspect.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.ssm; + +import java.util.function.Supplier; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.FieldSignature; +import software.amazon.lambda.powertools.parameters.BaseParamAspect; + +/** + * Provides the SSM parameter store parameter aspect. This aspect is responsible for injecting + * parameters from SSM Parameter Store into fields annotated with @SSMParam. See the + * README and Powertools for Lambda (Java) documentation for information on using this feature. + */ +@Aspect +public class SSMParamAspect extends BaseParamAspect { + + // This supplier produces a new SSMProvider each time it is called + private static Supplier<SSMProvider> providerBuilder = () -> SSMProvider.builder() + .build(); + + @Pointcut("get(* *) && @annotation(secretsParam)") + public void getParam(SSMParam secretsParam) { + } + + @Around("getParam(ssmPaam)") + public Object injectParam(final ProceedingJoinPoint joinPoint, final SSMParam ssmPaam) { + + SSMProvider provider = providerBuilder.get(); + return getAndTransform(ssmPaam.key(), ssmPaam.transformer(), provider, + (FieldSignature) joinPoint.getSignature()); + } + +} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SSMProvider.java b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMProvider.java similarity index 56% rename from powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SSMProvider.java rename to powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMProvider.java index a74e1c095..3cf728219 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SSMProvider.java +++ b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,31 +11,26 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.parameters; -import java.time.temporal.ChronoUnit; +package software.amazon.lambda.powertools.parameters.ssm; + import java.util.HashMap; import java.util.Map; - -import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; -import software.amazon.awssdk.core.SdkSystemSetting; -import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; -import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.GetParameterRequest; import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest; import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse; import software.amazon.awssdk.utils.StringUtils; +import software.amazon.lambda.powertools.parameters.BaseProvider; import software.amazon.lambda.powertools.parameters.cache.CacheManager; import software.amazon.lambda.powertools.parameters.transform.TransformationManager; -import software.amazon.lambda.powertools.parameters.transform.Transformer; /** * AWS System Manager Parameter Store Provider <br/><br/> * * <u>Samples:</u> * <pre> - * SSMProvider provider = ParamManager.getSsmProvider(); + * SSMProvider provider = SSMProvider.builder().build(); * * String value = provider.get("key"); * System.out.println(value); @@ -71,35 +66,37 @@ public class SSMProvider extends BaseProvider { private final SsmClient client; - - private boolean decrypt = false; - private boolean recursive = false; + private final ThreadLocal<Boolean> decrypt = ThreadLocal.withInitial(() -> false); + private final ThreadLocal<Boolean> recursive = ThreadLocal.withInitial(() -> false); /** - * Default constructor with default {@link SsmClient}. <br/> - * Use when you don't need to customize region or any other attribute of the client.<br/><br/> + * Constructor with custom {@link SsmClient}. <br/> + * Use when you need to customize region or any other attribute of the client.<br/><br/> * <p> - * Use the {@link SSMProvider.Builder} to create an instance of it. + * Use the {@link SSMProviderBuilder} to create an instance of it. + * + * @param client custom client you would like to use. + * @param transformationManager Null, or a transformation manager */ - SSMProvider(CacheManager cacheManager) { - this(cacheManager, SsmClient.builder() - .httpClientBuilder(UrlConnectionHttpClient.builder()) - .credentialsProvider(EnvironmentVariableCredentialsProvider.create()) - .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) - .build()); + SSMProvider(CacheManager cacheManager, TransformationManager transformationManager, SsmClient client) { + super(cacheManager, transformationManager); + this.client = client; } /** - * Constructor with custom {@link SsmClient}. <br/> - * Use when you need to customize region or any other attribute of the client.<br/><br/> - * <p> - * Use the {@link SSMProvider.Builder} to create an instance of it. + * Create a builder that can be used to configure and create a {@link SSMProvider}. * - * @param client custom client you would like to use. + * @return a new instance of {@link SSMProviderBuilder} */ - SSMProvider(CacheManager cacheManager, SsmClient client) { - super(cacheManager); - this.client = client; + public static SSMProviderBuilder builder() { + return new SSMProviderBuilder(); + } + + /** + * Create a SSMProvider with all default settings. + */ + public static SSMProvider create() { + return new SSMProviderBuilder().build(); } /** @@ -112,38 +109,11 @@ public class SSMProvider extends BaseProvider { public String getValue(String key) { GetParameterRequest request = GetParameterRequest.builder() .name(key) - .withDecryption(decrypt) + .withDecryption(decrypt.get()) .build(); return client.getParameter(request).parameter().value(); } - /** - * {@inheritDoc} - */ - @Override - public SSMProvider defaultMaxAge(int maxAge, ChronoUnit unit) { - super.defaultMaxAge(maxAge, unit); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public SSMProvider withMaxAge(int maxAge, ChronoUnit unit) { - super.withMaxAge(maxAge, unit); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public SSMProvider withTransformation(Class<? extends Transformer> transformerClass) { - super.withTransformation(transformerClass); - return this; - } - /** * Tells System Manager Parameter Store to decrypt the parameter value.<br/> * By default, parameter values are not decrypted.<br/> @@ -152,7 +122,7 @@ public SSMProvider withTransformation(Class<? extends Transformer> transformerCl * @return the provider itself in order to chain calls (eg. <pre>provider.withDecryption().get("key")</pre>). */ public SSMProvider withDecryption() { - this.decrypt = true; + this.decrypt.set(true); return this; } @@ -163,7 +133,7 @@ public SSMProvider withDecryption() { * @return the provider itself in order to chain calls (eg. <pre>provider.recursive().getMultiple("key")</pre>). */ public SSMProvider recursive() { - this.recursive = true; + this.recursive.set(true); return this; } @@ -190,8 +160,8 @@ protected Map<String, String> getMultipleValues(String path) { private Map<String, String> getMultipleBis(String path, String nextToken) { GetParametersByPathRequest request = GetParametersByPathRequest.builder() .path(path) - .withDecryption(decrypt) - .recursive(recursive) + .withDecryption(decrypt.get()) + .recursive(recursive.get()) .nextToken(nextToken) .build(); @@ -201,10 +171,11 @@ private Map<String, String> getMultipleBis(String path, String nextToken) { GetParametersByPathResponse res = client.getParametersByPath(request); if (res.hasParameters()) { res.parameters().forEach(parameter -> { - /* Standardize the parameter name - The parameter name returned by SSM will contained the full path. - However, for readability, we should return only the part after - the path. + /* + * Standardize the parameter name + * The parameter name returned by SSM will contain the full path. + * However, for readability, we should return only the part after + * the path. */ String name = parameter.name(); if (name.startsWith(path)) { @@ -225,77 +196,13 @@ private Map<String, String> getMultipleBis(String path, String nextToken) { @Override protected void resetToDefaults() { super.resetToDefaults(); - recursive = false; - decrypt = false; + decrypt.remove(); + recursive.remove(); } - /** - * Create a builder that can be used to configure and create a {@link SSMProvider}. - * - * @return a new instance of {@link SSMProvider.Builder} - */ - public static SSMProvider.Builder builder() { - return new SSMProvider.Builder(); + // For tests purpose only + SsmClient getClient() { + return client; } - static class Builder { - private SsmClient client; - private CacheManager cacheManager; - private TransformationManager transformationManager; - - /** - * Create a {@link SSMProvider} instance. - * - * @return a {@link SSMProvider} - */ - public SSMProvider build() { - if (cacheManager == null) { - throw new IllegalStateException("No CacheManager provided, please provide one"); - } - SSMProvider provider; - if (client != null) { - provider = new SSMProvider(cacheManager, client); - } else { - provider = new SSMProvider(cacheManager); - } - if (transformationManager != null) { - provider.setTransformationManager(transformationManager); - } - return provider; - } - - /** - * Set custom {@link SsmClient} to pass to the {@link SSMProvider}. <br/> - * Use it if you want to customize the region or any other part of the client. - * - * @param client Custom client - * @return the builder to chain calls (eg. <pre>builder.withClient().build()</pre>) - */ - public SSMProvider.Builder withClient(SsmClient client) { - this.client = client; - return this; - } - - /** - * <b>Mandatory</b>. Provide a CacheManager to the {@link SSMProvider} - * - * @param cacheManager the manager that will handle the cache of parameters - * @return the builder to chain calls (eg. <pre>builder.withCacheManager().build()</pre>) - */ - public SSMProvider.Builder withCacheManager(CacheManager cacheManager) { - this.cacheManager = cacheManager; - return this; - } - - /** - * Provide a transformationManager to the {@link SSMProvider} - * - * @param transformationManager the manager that will handle transformation of parameters - * @return the builder to chain calls (eg. <pre>builder.withTransformationManager().build()</pre>) - */ - public SSMProvider.Builder withTransformationManager(TransformationManager transformationManager) { - this.transformationManager = transformationManager; - return this; - } - } } diff --git a/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMProviderBuilder.java b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMProviderBuilder.java new file mode 100644 index 000000000..4c26463fb --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMProviderBuilder.java @@ -0,0 +1,101 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.ssm; + +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * Builder for the {@link SSMProvider} + */ +public class SSMProviderBuilder { + private SsmClient client; + private CacheManager cacheManager; + private TransformationManager transformationManager; + + private static SsmClient createClient() { + return SsmClient.builder() + .httpClientBuilder(UrlConnectionHttpClient.builder()) + .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, + UserAgentConfigurator.getUserAgent(BaseProvider.PARAMETERS)).build()) + .build(); + } + + /** + * Create a {@link SSMProvider} instance. + * + * @return a {@link SSMProvider} + */ + public SSMProvider build() { + if (cacheManager == null) { + cacheManager = new CacheManager(); + } + SSMProvider provider; + if (client == null) { + client = createClient(); + } + + if(transformationManager == null){ + transformationManager = new TransformationManager(); + } + provider = new SSMProvider(cacheManager, transformationManager, client); + + return provider; + } + + /** + * Set custom {@link SsmClient} to pass to the {@link SSMProvider}. <br/> + * Use it if you want to customize the region or any other part of the client. + * + * @param client Custom client + * @return the builder to chain calls (eg. <pre>builder.withClient().build()</pre>) + */ + public SSMProviderBuilder withClient(SsmClient client) { + this.client = client; + return this; + } + + /** + * Provide a CacheManager to the {@link SSMProvider} + * + * @param cacheManager the manager that will handle the cache of parameters + * @return the builder to chain calls (eg. <pre>builder.withCacheManager().build()</pre>) + */ + public SSMProviderBuilder withCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + return this; + } + + /** + * Provide a transformationManager to the {@link SSMProvider} + * + * @param transformationManager the manager that will handle transformation of parameters + * @return the builder to chain calls (eg. <pre>builder.withTransformationManager().build()</pre>) + */ + public SSMProviderBuilder withTransformationManager(TransformationManager transformationManager) { + this.transformationManager = transformationManager; + return this; + } +} diff --git a/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/internal/ParametersSsmUserAgentInterceptor.java b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/internal/ParametersSsmUserAgentInterceptor.java new file mode 100644 index 000000000..ad1ff65dc --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/internal/ParametersSsmUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.parameters.ssm.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-parameters-ssm module is on the classpath. + */ +public final class ParametersSsmUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("parameters-ssm"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-parameters/powertools-parameters-ssm/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-ssm/jni-config.json b/powertools-parameters/powertools-parameters-ssm/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-ssm/jni-config.json new file mode 100644 index 000000000..2c4de0562 --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-ssm/jni-config.json @@ -0,0 +1,26 @@ +[ +{ + "name":"java.lang.Boolean", + "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.String", + "methods":[{"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }] +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"org.apache.maven.surefire.booter.ForkedBooter", + "methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }] +}, +{ + "name":"sun.instrument.InstrumentationImpl", + "methods":[{"name":"<init>","parameterTypes":["long","boolean","boolean","boolean"] }, {"name":"loadClassAndCallAgentmain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"loadClassAndCallPremain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"transform","parameterTypes":["java.lang.Module","java.lang.ClassLoader","java.lang.String","java.lang.Class","java.security.ProtectionDomain","byte[]","boolean"] }] +}, +{ + "name":"sun.management.VMManagementImpl", + "fields":[{"name":"compTimeMonitoringSupport"}, {"name":"currentThreadCpuTimeSupport"}, {"name":"objectMonitorUsageSupport"}, {"name":"otherThreadCpuTimeSupport"}, {"name":"remoteDiagnosticCommandsSupport"}, {"name":"synchronizerUsageSupport"}, {"name":"threadAllocatedMemorySupport"}, {"name":"threadContentionMonitoringSupport"}] +} +] diff --git a/powertools-parameters/powertools-parameters-ssm/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-ssm/reflect-config.json b/powertools-parameters/powertools-parameters-ssm/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-ssm/reflect-config.json new file mode 100644 index 000000000..a655e62ce --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-ssm/reflect-config.json @@ -0,0 +1,345 @@ +[ +{ + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ARCFOURCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESedeCipher", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.tools.attach.VirtualMachine" +}, +{ + "name":"java.io.Serializable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.AutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.Class", + "methods":[{"name":"forName","parameterTypes":["java.lang.String"] }, {"name":"getAnnotatedInterfaces","parameterTypes":[] }, {"name":"getAnnotatedSuperclass","parameterTypes":[] }, {"name":"getDeclaredMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getModule","parameterTypes":[] }, {"name":"getNestHost","parameterTypes":[] }, {"name":"getNestMembers","parameterTypes":[] }, {"name":"getPermittedSubclasses","parameterTypes":[] }, {"name":"getRecordComponents","parameterTypes":[] }, {"name":"isNestmateOf","parameterTypes":["java.lang.Class"] }, {"name":"isRecord","parameterTypes":[] }, {"name":"isSealed","parameterTypes":[] }] +}, +{ + "name":"java.lang.ClassLoader", + "methods":[{"name":"getDefinedPackage","parameterTypes":["java.lang.String"] }, {"name":"getUnnamedModule","parameterTypes":[] }, {"name":"registerAsParallelCapable","parameterTypes":[] }] +}, +{ + "name":"java.lang.Module", + "methods":[{"name":"addExports","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"addReads","parameterTypes":["java.lang.Module"] }, {"name":"canRead","parameterTypes":["java.lang.Module"] }, {"name":"getClassLoader","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPackages","parameterTypes":[] }, {"name":"getResourceAsStream","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"isNamed","parameterTypes":[] }, {"name":"isOpen","parameterTypes":["java.lang.String","java.lang.Module"] }] +}, +{ + "name":"java.lang.Object", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"clone","parameterTypes":[] }, {"name":"getClass","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"java.lang.ProcessHandle", + "methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime", + "methods":[{"name":"version","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime$Version", + "methods":[{"name":"feature","parameterTypes":[] }] +}, +{ + "name":"java.lang.StackWalker" +}, +{ + "name":"java.lang.String" +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getSecurityManager","parameterTypes":[] }] +}, +{ + "name":"java.lang.annotation.Retention", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.annotation.Target", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.invoke.MethodHandle", + "methods":[{"name":"bindTo","parameterTypes":["java.lang.Object"] }, {"name":"invokeWithArguments","parameterTypes":["java.lang.Object[]"] }] +}, +{ + "name":"java.lang.invoke.MethodHandles", + "methods":[{"name":"lookup","parameterTypes":[] }] +}, +{ + "name":"java.lang.invoke.MethodHandles$Lookup", + "methods":[{"name":"findVirtual","parameterTypes":["java.lang.Class","java.lang.String","java.lang.invoke.MethodType"] }] +}, +{ + "name":"java.lang.invoke.MethodType", + "methods":[{"name":"methodType","parameterTypes":["java.lang.Class","java.lang.Class[]"] }] +}, +{ + "name":"java.lang.reflect.AccessibleObject", + "methods":[{"name":"setAccessible","parameterTypes":["boolean"] }] +}, +{ + "name":"java.lang.reflect.AnnotatedArrayType", + "methods":[{"name":"getAnnotatedGenericComponentType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedParameterizedType", + "methods":[{"name":"getAnnotatedActualTypeArguments","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedType", + "methods":[{"name":"getType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedTypeVariable", + "methods":[{"name":"getAnnotatedBounds","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedWildcardType", + "methods":[{"name":"getAnnotatedUpperBounds","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Executable", + "methods":[{"name":"getAnnotatedExceptionTypes","parameterTypes":[] }, {"name":"getAnnotatedParameterTypes","parameterTypes":[] }, {"name":"getAnnotatedReceiverType","parameterTypes":[] }, {"name":"getParameterCount","parameterTypes":[] }, {"name":"getParameters","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"getAnnotatedReturnType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Parameter", + "methods":[{"name":"getModifiers","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"isNamePresent","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.TypeVariable", + "methods":[{"name":"getAnnotatedBounds","parameterTypes":[] }] +}, +{ + "name":"java.security.AccessController", + "methods":[{"name":"doPrivileged","parameterTypes":["java.security.PrivilegedAction"] }, {"name":"doPrivileged","parameterTypes":["java.security.PrivilegedExceptionAction"] }] +}, +{ + "name":"java.security.AlgorithmParametersSpi" +}, +{ + "name":"java.security.KeyStoreSpi" +}, +{ + "name":"java.security.SecureRandomParameters" +}, +{ + "name":"java.util.concurrent.ForkJoinTask", + "fields":[{"name":"aux"}, {"name":"status"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"javax.security.auth.x500.X500Principal", + "fields":[{"name":"thisX500Name"}], + "methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }] +}, +{ + "name":"jdk.internal.misc.Unsafe" +}, +{ + "name":"kotlin.jvm.JvmInline" +}, +{ + "name":"org.apiguardian.api.API", + "queryAllPublicMethods":true +}, +{ + "name":"scala.util.Properties" +}, +{ + "name":"software.amazon.awssdk.awscore.AwsClient", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"software.amazon.awssdk.core.SdkClient", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"serviceName","parameterTypes":[] }] +}, +{ + "name":"software.amazon.awssdk.services.ssm.SsmClient", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"addTagsToResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"addTagsToResource","parameterTypes":["software.amazon.awssdk.services.ssm.model.AddTagsToResourceRequest"] }, {"name":"associateOpsItemRelatedItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"associateOpsItemRelatedItem","parameterTypes":["software.amazon.awssdk.services.ssm.model.AssociateOpsItemRelatedItemRequest"] }, {"name":"cancelCommand","parameterTypes":["java.util.function.Consumer"] }, {"name":"cancelCommand","parameterTypes":["software.amazon.awssdk.services.ssm.model.CancelCommandRequest"] }, {"name":"cancelMaintenanceWindowExecution","parameterTypes":["java.util.function.Consumer"] }, {"name":"cancelMaintenanceWindowExecution","parameterTypes":["software.amazon.awssdk.services.ssm.model.CancelMaintenanceWindowExecutionRequest"] }, {"name":"createActivation","parameterTypes":["java.util.function.Consumer"] }, {"name":"createActivation","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateActivationRequest"] }, {"name":"createAssociation","parameterTypes":["java.util.function.Consumer"] }, {"name":"createAssociation","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateAssociationRequest"] }, {"name":"createAssociationBatch","parameterTypes":["java.util.function.Consumer"] }, {"name":"createAssociationBatch","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateAssociationBatchRequest"] }, {"name":"createDocument","parameterTypes":["java.util.function.Consumer"] }, {"name":"createDocument","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateDocumentRequest"] }, {"name":"createMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"createMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateMaintenanceWindowRequest"] }, {"name":"createOpsItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"createOpsItem","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateOpsItemRequest"] }, {"name":"createOpsMetadata","parameterTypes":["java.util.function.Consumer"] }, {"name":"createOpsMetadata","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateOpsMetadataRequest"] }, {"name":"createPatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"createPatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreatePatchBaselineRequest"] }, {"name":"createResourceDataSync","parameterTypes":["java.util.function.Consumer"] }, {"name":"createResourceDataSync","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateResourceDataSyncRequest"] }, {"name":"deleteActivation","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteActivation","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteActivationRequest"] }, {"name":"deleteAssociation","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteAssociation","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteAssociationRequest"] }, {"name":"deleteDocument","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteDocument","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteDocumentRequest"] }, {"name":"deleteInventory","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteInventory","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteInventoryRequest"] }, {"name":"deleteMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteMaintenanceWindowRequest"] }, {"name":"deleteOpsItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteOpsItem","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteOpsItemRequest"] }, {"name":"deleteOpsMetadata","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteOpsMetadata","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteOpsMetadataRequest"] }, {"name":"deleteParameter","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteParameter","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteParameterRequest"] }, {"name":"deleteParameters","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteParameters","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteParametersRequest"] }, {"name":"deletePatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"deletePatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeletePatchBaselineRequest"] }, {"name":"deleteResourceDataSync","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteResourceDataSync","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteResourceDataSyncRequest"] }, {"name":"deleteResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteResourcePolicy","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteResourcePolicyRequest"] }, {"name":"deregisterManagedInstance","parameterTypes":["java.util.function.Consumer"] }, {"name":"deregisterManagedInstance","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeregisterManagedInstanceRequest"] }, {"name":"deregisterPatchBaselineForPatchGroup","parameterTypes":["java.util.function.Consumer"] }, {"name":"deregisterPatchBaselineForPatchGroup","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeregisterPatchBaselineForPatchGroupRequest"] }, {"name":"deregisterTargetFromMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"deregisterTargetFromMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeregisterTargetFromMaintenanceWindowRequest"] }, {"name":"deregisterTaskFromMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"deregisterTaskFromMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeregisterTaskFromMaintenanceWindowRequest"] }, {"name":"describeActivations","parameterTypes":[] }, {"name":"describeActivations","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeActivations","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeActivationsRequest"] }, {"name":"describeActivationsPaginator","parameterTypes":[] }, {"name":"describeActivationsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeActivationsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeActivationsRequest"] }, {"name":"describeAssociation","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAssociation","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAssociationRequest"] }, {"name":"describeAssociationExecutionTargets","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAssociationExecutionTargets","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAssociationExecutionTargetsRequest"] }, {"name":"describeAssociationExecutionTargetsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAssociationExecutionTargetsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAssociationExecutionTargetsRequest"] }, {"name":"describeAssociationExecutions","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAssociationExecutions","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAssociationExecutionsRequest"] }, {"name":"describeAssociationExecutionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAssociationExecutionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAssociationExecutionsRequest"] }, {"name":"describeAutomationExecutions","parameterTypes":[] }, {"name":"describeAutomationExecutions","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAutomationExecutions","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAutomationExecutionsRequest"] }, {"name":"describeAutomationExecutionsPaginator","parameterTypes":[] }, {"name":"describeAutomationExecutionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAutomationExecutionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAutomationExecutionsRequest"] }, {"name":"describeAutomationStepExecutions","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAutomationStepExecutions","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAutomationStepExecutionsRequest"] }, {"name":"describeAutomationStepExecutionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAutomationStepExecutionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAutomationStepExecutionsRequest"] }, {"name":"describeAvailablePatches","parameterTypes":[] }, {"name":"describeAvailablePatches","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAvailablePatches","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAvailablePatchesRequest"] }, {"name":"describeAvailablePatchesPaginator","parameterTypes":[] }, {"name":"describeAvailablePatchesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAvailablePatchesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAvailablePatchesRequest"] }, {"name":"describeDocument","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeDocument","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeDocumentRequest"] }, {"name":"describeDocumentPermission","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeDocumentPermission","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeDocumentPermissionRequest"] }, {"name":"describeEffectiveInstanceAssociations","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeEffectiveInstanceAssociations","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeEffectiveInstanceAssociationsRequest"] }, {"name":"describeEffectiveInstanceAssociationsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeEffectiveInstanceAssociationsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeEffectiveInstanceAssociationsRequest"] }, {"name":"describeEffectivePatchesForPatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeEffectivePatchesForPatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeEffectivePatchesForPatchBaselineRequest"] }, {"name":"describeEffectivePatchesForPatchBaselinePaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeEffectivePatchesForPatchBaselinePaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeEffectivePatchesForPatchBaselineRequest"] }, {"name":"describeInstanceAssociationsStatus","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstanceAssociationsStatus","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstanceAssociationsStatusRequest"] }, {"name":"describeInstanceAssociationsStatusPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstanceAssociationsStatusPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstanceAssociationsStatusRequest"] }, {"name":"describeInstanceInformation","parameterTypes":[] }, {"name":"describeInstanceInformation","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstanceInformation","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstanceInformationRequest"] }, {"name":"describeInstanceInformationPaginator","parameterTypes":[] }, {"name":"describeInstanceInformationPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstanceInformationPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstanceInformationRequest"] }, {"name":"describeInstancePatchStates","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePatchStates","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePatchStatesRequest"] }, {"name":"describeInstancePatchStatesForPatchGroup","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePatchStatesForPatchGroup","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePatchStatesForPatchGroupRequest"] }, {"name":"describeInstancePatchStatesForPatchGroupPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePatchStatesForPatchGroupPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePatchStatesForPatchGroupRequest"] }, {"name":"describeInstancePatchStatesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePatchStatesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePatchStatesRequest"] }, {"name":"describeInstancePatches","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePatches","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePatchesRequest"] }, {"name":"describeInstancePatchesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePatchesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePatchesRequest"] }, {"name":"describeInstanceProperties","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstanceProperties","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePropertiesRequest"] }, {"name":"describeInstancePropertiesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePropertiesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePropertiesRequest"] }, {"name":"describeInventoryDeletions","parameterTypes":[] }, {"name":"describeInventoryDeletions","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInventoryDeletions","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInventoryDeletionsRequest"] }, {"name":"describeInventoryDeletionsPaginator","parameterTypes":[] }, {"name":"describeInventoryDeletionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInventoryDeletionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInventoryDeletionsRequest"] }, {"name":"describeMaintenanceWindowExecutionTaskInvocations","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowExecutionTaskInvocations","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowExecutionTaskInvocationsRequest"] }, {"name":"describeMaintenanceWindowExecutionTaskInvocationsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowExecutionTaskInvocationsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowExecutionTaskInvocationsRequest"] }, {"name":"describeMaintenanceWindowExecutionTasks","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowExecutionTasks","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowExecutionTasksRequest"] }, {"name":"describeMaintenanceWindowExecutionTasksPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowExecutionTasksPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowExecutionTasksRequest"] }, {"name":"describeMaintenanceWindowExecutions","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowExecutions","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowExecutionsRequest"] }, {"name":"describeMaintenanceWindowExecutionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowExecutionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowExecutionsRequest"] }, {"name":"describeMaintenanceWindowSchedule","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowSchedule","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowScheduleRequest"] }, {"name":"describeMaintenanceWindowSchedulePaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowSchedulePaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowScheduleRequest"] }, {"name":"describeMaintenanceWindowTargets","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowTargets","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowTargetsRequest"] }, {"name":"describeMaintenanceWindowTargetsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowTargetsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowTargetsRequest"] }, {"name":"describeMaintenanceWindowTasks","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowTasks","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowTasksRequest"] }, {"name":"describeMaintenanceWindowTasksPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowTasksPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowTasksRequest"] }, {"name":"describeMaintenanceWindows","parameterTypes":[] }, {"name":"describeMaintenanceWindows","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindows","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowsRequest"] }, {"name":"describeMaintenanceWindowsForTarget","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowsForTarget","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowsForTargetRequest"] }, {"name":"describeMaintenanceWindowsForTargetPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowsForTargetPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowsForTargetRequest"] }, {"name":"describeMaintenanceWindowsPaginator","parameterTypes":[] }, {"name":"describeMaintenanceWindowsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowsRequest"] }, {"name":"describeOpsItems","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeOpsItems","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeOpsItemsRequest"] }, {"name":"describeOpsItemsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeOpsItemsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeOpsItemsRequest"] }, {"name":"describeParameters","parameterTypes":[] }, {"name":"describeParameters","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeParameters","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeParametersRequest"] }, {"name":"describeParametersPaginator","parameterTypes":[] }, {"name":"describeParametersPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeParametersPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeParametersRequest"] }, {"name":"describePatchBaselines","parameterTypes":[] }, {"name":"describePatchBaselines","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchBaselines","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchBaselinesRequest"] }, {"name":"describePatchBaselinesPaginator","parameterTypes":[] }, {"name":"describePatchBaselinesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchBaselinesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchBaselinesRequest"] }, {"name":"describePatchGroupState","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchGroupState","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchGroupStateRequest"] }, {"name":"describePatchGroups","parameterTypes":[] }, {"name":"describePatchGroups","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchGroups","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchGroupsRequest"] }, {"name":"describePatchGroupsPaginator","parameterTypes":[] }, {"name":"describePatchGroupsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchGroupsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchGroupsRequest"] }, {"name":"describePatchProperties","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchProperties","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchPropertiesRequest"] }, {"name":"describePatchPropertiesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchPropertiesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchPropertiesRequest"] }, {"name":"describeSessions","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeSessions","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeSessionsRequest"] }, {"name":"describeSessionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeSessionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeSessionsRequest"] }, {"name":"disassociateOpsItemRelatedItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"disassociateOpsItemRelatedItem","parameterTypes":["software.amazon.awssdk.services.ssm.model.DisassociateOpsItemRelatedItemRequest"] }, {"name":"getAutomationExecution","parameterTypes":["java.util.function.Consumer"] }, {"name":"getAutomationExecution","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetAutomationExecutionRequest"] }, {"name":"getCalendarState","parameterTypes":["java.util.function.Consumer"] }, {"name":"getCalendarState","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetCalendarStateRequest"] }, {"name":"getCommandInvocation","parameterTypes":["java.util.function.Consumer"] }, {"name":"getCommandInvocation","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetCommandInvocationRequest"] }, {"name":"getConnectionStatus","parameterTypes":["java.util.function.Consumer"] }, {"name":"getConnectionStatus","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetConnectionStatusRequest"] }, {"name":"getDefaultPatchBaseline","parameterTypes":[] }, {"name":"getDefaultPatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"getDefaultPatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetDefaultPatchBaselineRequest"] }, {"name":"getDeployablePatchSnapshotForInstance","parameterTypes":["java.util.function.Consumer"] }, {"name":"getDeployablePatchSnapshotForInstance","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetDeployablePatchSnapshotForInstanceRequest"] }, {"name":"getDocument","parameterTypes":["java.util.function.Consumer"] }, {"name":"getDocument","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetDocumentRequest"] }, {"name":"getInventory","parameterTypes":[] }, {"name":"getInventory","parameterTypes":["java.util.function.Consumer"] }, {"name":"getInventory","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetInventoryRequest"] }, {"name":"getInventoryPaginator","parameterTypes":[] }, {"name":"getInventoryPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"getInventoryPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetInventoryRequest"] }, {"name":"getInventorySchema","parameterTypes":[] }, {"name":"getInventorySchema","parameterTypes":["java.util.function.Consumer"] }, {"name":"getInventorySchema","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetInventorySchemaRequest"] }, {"name":"getInventorySchemaPaginator","parameterTypes":[] }, {"name":"getInventorySchemaPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"getInventorySchemaPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetInventorySchemaRequest"] }, {"name":"getMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"getMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetMaintenanceWindowRequest"] }, {"name":"getMaintenanceWindowExecution","parameterTypes":["java.util.function.Consumer"] }, {"name":"getMaintenanceWindowExecution","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetMaintenanceWindowExecutionRequest"] }, {"name":"getMaintenanceWindowExecutionTask","parameterTypes":["java.util.function.Consumer"] }, {"name":"getMaintenanceWindowExecutionTask","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetMaintenanceWindowExecutionTaskRequest"] }, {"name":"getMaintenanceWindowExecutionTaskInvocation","parameterTypes":["java.util.function.Consumer"] }, {"name":"getMaintenanceWindowExecutionTaskInvocation","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetMaintenanceWindowExecutionTaskInvocationRequest"] }, {"name":"getMaintenanceWindowTask","parameterTypes":["java.util.function.Consumer"] }, {"name":"getMaintenanceWindowTask","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetMaintenanceWindowTaskRequest"] }, {"name":"getOpsItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"getOpsItem","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetOpsItemRequest"] }, {"name":"getOpsMetadata","parameterTypes":["java.util.function.Consumer"] }, {"name":"getOpsMetadata","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetOpsMetadataRequest"] }, {"name":"getOpsSummary","parameterTypes":["java.util.function.Consumer"] }, {"name":"getOpsSummary","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetOpsSummaryRequest"] }, {"name":"getOpsSummaryPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"getOpsSummaryPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetOpsSummaryRequest"] }, {"name":"getParameter","parameterTypes":["java.util.function.Consumer"] }, {"name":"getParameter","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetParameterRequest"] }, {"name":"getParameterHistory","parameterTypes":["java.util.function.Consumer"] }, {"name":"getParameterHistory","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetParameterHistoryRequest"] }, {"name":"getParameterHistoryPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"getParameterHistoryPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetParameterHistoryRequest"] }, {"name":"getParameters","parameterTypes":["java.util.function.Consumer"] }, {"name":"getParameters","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetParametersRequest"] }, {"name":"getParametersByPath","parameterTypes":["java.util.function.Consumer"] }, {"name":"getParametersByPath","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest"] }, {"name":"getParametersByPathPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"getParametersByPathPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest"] }, {"name":"getPatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"getPatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetPatchBaselineRequest"] }, {"name":"getPatchBaselineForPatchGroup","parameterTypes":["java.util.function.Consumer"] }, {"name":"getPatchBaselineForPatchGroup","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetPatchBaselineForPatchGroupRequest"] }, {"name":"getResourcePolicies","parameterTypes":["java.util.function.Consumer"] }, {"name":"getResourcePolicies","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetResourcePoliciesRequest"] }, {"name":"getResourcePoliciesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"getResourcePoliciesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetResourcePoliciesRequest"] }, {"name":"getServiceSetting","parameterTypes":["java.util.function.Consumer"] }, {"name":"getServiceSetting","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetServiceSettingRequest"] }, {"name":"labelParameterVersion","parameterTypes":["java.util.function.Consumer"] }, {"name":"labelParameterVersion","parameterTypes":["software.amazon.awssdk.services.ssm.model.LabelParameterVersionRequest"] }, {"name":"listAssociationVersions","parameterTypes":["java.util.function.Consumer"] }, {"name":"listAssociationVersions","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListAssociationVersionsRequest"] }, {"name":"listAssociationVersionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listAssociationVersionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListAssociationVersionsRequest"] }, {"name":"listAssociations","parameterTypes":[] }, {"name":"listAssociations","parameterTypes":["java.util.function.Consumer"] }, {"name":"listAssociations","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListAssociationsRequest"] }, {"name":"listAssociationsPaginator","parameterTypes":[] }, {"name":"listAssociationsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listAssociationsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListAssociationsRequest"] }, {"name":"listCommandInvocations","parameterTypes":[] }, {"name":"listCommandInvocations","parameterTypes":["java.util.function.Consumer"] }, {"name":"listCommandInvocations","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListCommandInvocationsRequest"] }, {"name":"listCommandInvocationsPaginator","parameterTypes":[] }, {"name":"listCommandInvocationsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listCommandInvocationsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListCommandInvocationsRequest"] }, {"name":"listCommands","parameterTypes":[] }, {"name":"listCommands","parameterTypes":["java.util.function.Consumer"] }, {"name":"listCommands","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListCommandsRequest"] }, {"name":"listCommandsPaginator","parameterTypes":[] }, {"name":"listCommandsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listCommandsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListCommandsRequest"] }, {"name":"listComplianceItems","parameterTypes":["java.util.function.Consumer"] }, {"name":"listComplianceItems","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListComplianceItemsRequest"] }, {"name":"listComplianceItemsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listComplianceItemsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListComplianceItemsRequest"] }, {"name":"listComplianceSummaries","parameterTypes":[] }, {"name":"listComplianceSummaries","parameterTypes":["java.util.function.Consumer"] }, {"name":"listComplianceSummaries","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListComplianceSummariesRequest"] }, {"name":"listComplianceSummariesPaginator","parameterTypes":[] }, {"name":"listComplianceSummariesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listComplianceSummariesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListComplianceSummariesRequest"] }, {"name":"listDocumentMetadataHistory","parameterTypes":["java.util.function.Consumer"] }, {"name":"listDocumentMetadataHistory","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListDocumentMetadataHistoryRequest"] }, {"name":"listDocumentVersions","parameterTypes":["java.util.function.Consumer"] }, {"name":"listDocumentVersions","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListDocumentVersionsRequest"] }, {"name":"listDocumentVersionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listDocumentVersionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListDocumentVersionsRequest"] }, {"name":"listDocuments","parameterTypes":[] }, {"name":"listDocuments","parameterTypes":["java.util.function.Consumer"] }, {"name":"listDocuments","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListDocumentsRequest"] }, {"name":"listDocumentsPaginator","parameterTypes":[] }, {"name":"listDocumentsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listDocumentsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListDocumentsRequest"] }, {"name":"listInventoryEntries","parameterTypes":["java.util.function.Consumer"] }, {"name":"listInventoryEntries","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListInventoryEntriesRequest"] }, {"name":"listOpsItemEvents","parameterTypes":["java.util.function.Consumer"] }, {"name":"listOpsItemEvents","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListOpsItemEventsRequest"] }, {"name":"listOpsItemEventsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listOpsItemEventsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListOpsItemEventsRequest"] }, {"name":"listOpsItemRelatedItems","parameterTypes":["java.util.function.Consumer"] }, {"name":"listOpsItemRelatedItems","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListOpsItemRelatedItemsRequest"] }, {"name":"listOpsItemRelatedItemsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listOpsItemRelatedItemsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListOpsItemRelatedItemsRequest"] }, {"name":"listOpsMetadata","parameterTypes":["java.util.function.Consumer"] }, {"name":"listOpsMetadata","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListOpsMetadataRequest"] }, {"name":"listOpsMetadataPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listOpsMetadataPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListOpsMetadataRequest"] }, {"name":"listResourceComplianceSummaries","parameterTypes":[] }, {"name":"listResourceComplianceSummaries","parameterTypes":["java.util.function.Consumer"] }, {"name":"listResourceComplianceSummaries","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListResourceComplianceSummariesRequest"] }, {"name":"listResourceComplianceSummariesPaginator","parameterTypes":[] }, {"name":"listResourceComplianceSummariesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listResourceComplianceSummariesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListResourceComplianceSummariesRequest"] }, {"name":"listResourceDataSync","parameterTypes":[] }, {"name":"listResourceDataSync","parameterTypes":["java.util.function.Consumer"] }, {"name":"listResourceDataSync","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListResourceDataSyncRequest"] }, {"name":"listResourceDataSyncPaginator","parameterTypes":[] }, {"name":"listResourceDataSyncPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listResourceDataSyncPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListResourceDataSyncRequest"] }, {"name":"listTagsForResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"listTagsForResource","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListTagsForResourceRequest"] }, {"name":"modifyDocumentPermission","parameterTypes":["java.util.function.Consumer"] }, {"name":"modifyDocumentPermission","parameterTypes":["software.amazon.awssdk.services.ssm.model.ModifyDocumentPermissionRequest"] }, {"name":"putComplianceItems","parameterTypes":["java.util.function.Consumer"] }, {"name":"putComplianceItems","parameterTypes":["software.amazon.awssdk.services.ssm.model.PutComplianceItemsRequest"] }, {"name":"putInventory","parameterTypes":["java.util.function.Consumer"] }, {"name":"putInventory","parameterTypes":["software.amazon.awssdk.services.ssm.model.PutInventoryRequest"] }, {"name":"putParameter","parameterTypes":["java.util.function.Consumer"] }, {"name":"putParameter","parameterTypes":["software.amazon.awssdk.services.ssm.model.PutParameterRequest"] }, {"name":"putResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"putResourcePolicy","parameterTypes":["software.amazon.awssdk.services.ssm.model.PutResourcePolicyRequest"] }, {"name":"registerDefaultPatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"registerDefaultPatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.RegisterDefaultPatchBaselineRequest"] }, {"name":"registerPatchBaselineForPatchGroup","parameterTypes":["java.util.function.Consumer"] }, {"name":"registerPatchBaselineForPatchGroup","parameterTypes":["software.amazon.awssdk.services.ssm.model.RegisterPatchBaselineForPatchGroupRequest"] }, {"name":"registerTargetWithMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"registerTargetWithMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.RegisterTargetWithMaintenanceWindowRequest"] }, {"name":"registerTaskWithMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"registerTaskWithMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.RegisterTaskWithMaintenanceWindowRequest"] }, {"name":"removeTagsFromResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"removeTagsFromResource","parameterTypes":["software.amazon.awssdk.services.ssm.model.RemoveTagsFromResourceRequest"] }, {"name":"resetServiceSetting","parameterTypes":["java.util.function.Consumer"] }, {"name":"resetServiceSetting","parameterTypes":["software.amazon.awssdk.services.ssm.model.ResetServiceSettingRequest"] }, {"name":"resumeSession","parameterTypes":["java.util.function.Consumer"] }, {"name":"resumeSession","parameterTypes":["software.amazon.awssdk.services.ssm.model.ResumeSessionRequest"] }, {"name":"sendAutomationSignal","parameterTypes":["java.util.function.Consumer"] }, {"name":"sendAutomationSignal","parameterTypes":["software.amazon.awssdk.services.ssm.model.SendAutomationSignalRequest"] }, {"name":"sendCommand","parameterTypes":["java.util.function.Consumer"] }, {"name":"sendCommand","parameterTypes":["software.amazon.awssdk.services.ssm.model.SendCommandRequest"] }, {"name":"serviceClientConfiguration","parameterTypes":[] }, {"name":"startAssociationsOnce","parameterTypes":["java.util.function.Consumer"] }, {"name":"startAssociationsOnce","parameterTypes":["software.amazon.awssdk.services.ssm.model.StartAssociationsOnceRequest"] }, {"name":"startAutomationExecution","parameterTypes":["java.util.function.Consumer"] }, {"name":"startAutomationExecution","parameterTypes":["software.amazon.awssdk.services.ssm.model.StartAutomationExecutionRequest"] }, {"name":"startChangeRequestExecution","parameterTypes":["java.util.function.Consumer"] }, {"name":"startChangeRequestExecution","parameterTypes":["software.amazon.awssdk.services.ssm.model.StartChangeRequestExecutionRequest"] }, {"name":"startSession","parameterTypes":["java.util.function.Consumer"] }, {"name":"startSession","parameterTypes":["software.amazon.awssdk.services.ssm.model.StartSessionRequest"] }, {"name":"stopAutomationExecution","parameterTypes":["java.util.function.Consumer"] }, {"name":"stopAutomationExecution","parameterTypes":["software.amazon.awssdk.services.ssm.model.StopAutomationExecutionRequest"] }, {"name":"terminateSession","parameterTypes":["java.util.function.Consumer"] }, {"name":"terminateSession","parameterTypes":["software.amazon.awssdk.services.ssm.model.TerminateSessionRequest"] }, {"name":"unlabelParameterVersion","parameterTypes":["java.util.function.Consumer"] }, {"name":"unlabelParameterVersion","parameterTypes":["software.amazon.awssdk.services.ssm.model.UnlabelParameterVersionRequest"] }, {"name":"updateAssociation","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateAssociation","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateAssociationRequest"] }, {"name":"updateAssociationStatus","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateAssociationStatus","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateAssociationStatusRequest"] }, {"name":"updateDocument","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateDocument","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateDocumentRequest"] }, {"name":"updateDocumentDefaultVersion","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateDocumentDefaultVersion","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateDocumentDefaultVersionRequest"] }, {"name":"updateDocumentMetadata","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateDocumentMetadata","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateDocumentMetadataRequest"] }, {"name":"updateMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateMaintenanceWindowRequest"] }, {"name":"updateMaintenanceWindowTarget","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateMaintenanceWindowTarget","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateMaintenanceWindowTargetRequest"] }, {"name":"updateMaintenanceWindowTask","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateMaintenanceWindowTask","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateMaintenanceWindowTaskRequest"] }, {"name":"updateManagedInstanceRole","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateManagedInstanceRole","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateManagedInstanceRoleRequest"] }, {"name":"updateOpsItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateOpsItem","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateOpsItemRequest"] }, {"name":"updateOpsMetadata","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateOpsMetadata","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateOpsMetadataRequest"] }, {"name":"updatePatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"updatePatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdatePatchBaselineRequest"] }, {"name":"updateResourceDataSync","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateResourceDataSync","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateResourceDataSyncRequest"] }, {"name":"updateServiceSetting","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateServiceSetting","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateServiceSettingRequest"] }, {"name":"waiter","parameterTypes":[] }] +}, +{ + "name":"software.amazon.awssdk.utils.SdkAutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"close","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.BaseProvider", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"get","parameterTypes":["java.lang.String"] }, {"name":"get","parameterTypes":["java.lang.String","java.lang.Class"] }, {"name":"getMultiple","parameterTypes":["java.lang.String"] }, {"name":"now","parameterTypes":[] }, {"name":"withMaxAge","parameterTypes":["int","java.time.temporal.ChronoUnit"] }, {"name":"withTransformation","parameterTypes":["java.lang.Class"] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.ParamProvider", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"software.amazon.lambda.powertools.parameters.ssm.SSMParamAspect", + "fields":[{"name":"providerBuilder"}] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.ssm.SSMProvider", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getMultipleValues","parameterTypes":["java.lang.String"] }, {"name":"getValue","parameterTypes":["java.lang.String"] }, {"name":"recursive","parameterTypes":[] }, {"name":"resetToDefaults","parameterTypes":[] }, {"name":"withDecryption","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.transform.TransformationManager", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"performBasicTransformation","parameterTypes":["java.lang.String"] }, {"name":"performComplexTransformation","parameterTypes":["java.lang.String","java.lang.Class"] }, {"name":"setTransformer","parameterTypes":["java.lang.Class"] }, {"name":"shouldTransform","parameterTypes":[] }] +}, +{ + "name": "software.amazon.lambda.powertools.parameters.transform.JsonTransformer", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods": [{"name": "applyTransformation","parameterTypes":["java.lang.String","java.lang.Class"]}] +}, +{ + "name": "software.amazon.lambda.powertools.parameters.transform.Base64Transformer", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods": [{"name": "applyTransformation","parameterTypes":["java.lang.String"]}] +}, +{ + "name": "software.amazon.lambda.powertools.parameters.transform.BasicTransformer", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods": [{"name": "applyTransformation","parameterTypes":["java.lang.String","java.lang.Class"]}] +}, +{ + "name":"sun.reflect.ReflectionFactory", + "methods":[{"name":"getReflectionFactory","parameterTypes":[] }, {"name":"newConstructorForSerialization","parameterTypes":["java.lang.Class","java.lang.reflect.Constructor"] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.SHA", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.X509Factory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAKeyFactory$Legacy", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.SSLContextImpl$TLSContext", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"sun.security.x509.AuthorityInfoAccessExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.AuthorityKeyIdentifierExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.BasicConstraintsExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CRLDistributionPointsExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CertificatePoliciesExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.ExtendedKeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.IssuerAlternativeNameExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.KeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.NetscapeCertTypeExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.PrivateKeyUsageExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectAlternativeNameExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectKeyIdentifierExtension", + "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.ssm.internal.ParametersSsmUserAgentInterceptor", + "methods":[{"name":"<init>","parameterTypes":[] }] +} +] diff --git a/powertools-parameters/powertools-parameters-ssm/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-ssm/resource-config.json b/powertools-parameters/powertools-parameters-ssm/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-ssm/resource-config.json new file mode 100644 index 000000000..6d8f3660f --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters-ssm/resource-config.json @@ -0,0 +1,23 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/global/handlers/execution.interceptors\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/services/ssm/execution.interceptors\\E" + }, { + "pattern":"\\Qversion.properties\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/nfc.nrm\\E" + }]}, + "bundles":[] +} diff --git a/powertools-parameters/powertools-parameters-ssm/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-parameters/powertools-parameters-ssm/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..4cce863f6 --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.parameters.ssm.internal.ParametersSsmUserAgentInterceptor diff --git a/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/SSMParamAspectTest.java b/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/SSMParamAspectTest.java new file mode 100644 index 000000000..abfc1d7fa --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/SSMParamAspectTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.ssm; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Supplier; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class SSMParamAspectTest { + + // This class tests the SSM Param aspect in the same fashion + // as the tests for the aspects for the other providers. + + @Test + void parameterInjectedByProvider() throws Exception { + + String key = "myKey"; + String value = "mySecretValue"; + SSMProvider provider = Mockito.mock(SSMProvider.class); + + Supplier<SSMProvider> providerBuilder = () -> provider; + writeStaticField(SSMParamAspect.class, "providerBuilder", providerBuilder, true); + + // Setup our mocked SSMProvider to return a value for our test data + Mockito.when(provider.get(key)).thenReturn(value); + + // Create an instance of a class and let the SSMParamAspect inject it + MyInjectedClass obj = new MyInjectedClass(); + assertThat(obj.mySecret).isEqualTo(value); + } + + class MyInjectedClass { + @SSMParam(key = "myKey") + public String mySecret; + } + +} diff --git a/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/SSMProviderTest.java b/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/SSMProviderTest.java new file mode 100644 index 000000000..fb475a737 --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/SSMProviderTest.java @@ -0,0 +1,313 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.ssm; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static software.amazon.lambda.powertools.parameters.transform.Transformer.json; + +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import org.assertj.core.data.MapEntry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.awssdk.services.ssm.model.GetParameterRequest; +import software.amazon.awssdk.services.ssm.model.GetParameterResponse; +import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest; +import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse; +import software.amazon.awssdk.services.ssm.model.Parameter; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +class SSMProviderTest { + + @Mock + SsmClient client; + + @Mock + TransformationManager transformationManager; + + @Captor + ArgumentCaptor<GetParameterRequest> paramCaptor; + + @Captor + ArgumentCaptor<GetParametersByPathRequest> paramByPathCaptor; + + CacheManager cacheManager; + + SSMProvider provider; + + @BeforeEach + void init() { + MockitoAnnotations.openMocks(this); + cacheManager = new CacheManager(); + provider = new SSMProvider(cacheManager, null, client); + } + + @Test + void getValue() { + String key = "Key1"; + String expectedValue = "Value1"; + initMock(expectedValue); + + String value = provider.getValue(key); + + assertThat(value).isEqualTo(expectedValue); + assertThat(paramCaptor.getValue().name()).isEqualTo(key); + assertThat(paramCaptor.getValue().withDecryption()).isFalse(); + } + + @Test + void getValueDecrypted() { + String key = "Key2"; + String expectedValue = "Value2"; + initMock(expectedValue); + + String value = provider.withDecryption().getValue(key); + + assertThat(value).isEqualTo(expectedValue); + assertThat(paramCaptor.getValue().name()).isEqualTo(key); + assertThat(paramCaptor.getValue().withDecryption()).isTrue(); + } + + @Test + void getMultiple() { + List<Parameter> parameters = new ArrayList<>(); + parameters.add(Parameter.builder().name("/prod/app1/key1").value("foo1").build()); + parameters.add(Parameter.builder().name("/prod/app1/key2").value("foo2").build()); + parameters.add(Parameter.builder().name("/prod/app1/key3").value("foo3").build()); + GetParametersByPathResponse response = GetParametersByPathResponse.builder().parameters(parameters).build(); + Mockito.when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response); + + Map<String, String> params = provider.getMultiple("/prod/app1"); + assertThat(params).contains( + MapEntry.entry("key1", "foo1"), + MapEntry.entry("key2", "foo2"), + MapEntry.entry("key3", "foo3")); + assertThat(provider.get("/prod/app1/key1")).isEqualTo("foo1"); + assertThat(provider.get("/prod/app1/key2")).isEqualTo("foo2"); + assertThat(provider.get("/prod/app1/key3")).isEqualTo("foo3"); + + assertThat(paramByPathCaptor.getValue().path()).isEqualTo("/prod/app1"); + assertThat(paramByPathCaptor.getValue().withDecryption()).isFalse(); + assertThat(paramByPathCaptor.getValue().recursive()).isFalse(); + } + + @Test + void getMultipleWithTrailingSlash() { + List<Parameter> parameters = new ArrayList<>(); + parameters.add(Parameter.builder().name("/prod/app1/key1").value("foo1").build()); + parameters.add(Parameter.builder().name("/prod/app1/key2").value("foo2").build()); + parameters.add(Parameter.builder().name("/prod/app1/key3").value("foo3").build()); + GetParametersByPathResponse response = GetParametersByPathResponse.builder().parameters(parameters).build(); + Mockito.when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response); + + Map<String, String> params = provider.getMultiple("/prod/app1/"); + assertThat(params).contains( + MapEntry.entry("key1", "foo1"), + MapEntry.entry("key2", "foo2"), + MapEntry.entry("key3", "foo3")); + assertThat(provider.get("/prod/app1/key1")).isEqualTo("foo1"); + assertThat(provider.get("/prod/app1/key2")).isEqualTo("foo2"); + assertThat(provider.get("/prod/app1/key3")).isEqualTo("foo3"); + + assertThat(paramByPathCaptor.getValue().path()).isEqualTo("/prod/app1"); + assertThat(paramByPathCaptor.getValue().withDecryption()).isFalse(); + assertThat(paramByPathCaptor.getValue().recursive()).isFalse(); + } + + @Test + void getMultiple_cached_shouldNotCallSSM() { + List<Parameter> parameters = new ArrayList<>(); + parameters.add(Parameter.builder().name("/prod/app1/key1").value("foo1").build()); + parameters.add(Parameter.builder().name("/prod/app1/key2").value("foo2").build()); + parameters.add(Parameter.builder().name("/prod/app1/key3").value("foo3").build()); + GetParametersByPathResponse response = GetParametersByPathResponse.builder().parameters(parameters).build(); + Mockito.when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response); + + provider.getMultiple("/prod/app1"); + + // should get the following from cache + provider.getMultiple("/prod/app1"); + provider.get("/prod/app1/key1"); + provider.get("/prod/app1/key2"); + provider.get("/prod/app1/key3"); + + Mockito.verify(client, Mockito.times(1)) + .getParametersByPath(ArgumentMatchers.any(GetParametersByPathRequest.class)); + + } + + @Test + void getMultipleWithNextToken() { + List<Parameter> parameters1 = new ArrayList<>(); + parameters1.add(Parameter.builder().name("/prod/app1/key1").value("foo1").build()); + parameters1.add(Parameter.builder().name("/prod/app1/key2").value("foo2").build()); + GetParametersByPathResponse response1 = GetParametersByPathResponse.builder().parameters(parameters1) + .nextToken("123abc").build(); + + List<Parameter> parameters2 = new ArrayList<>(); + parameters2.add(Parameter.builder().name("/prod/app1/key3").value("foo3").build()); + GetParametersByPathResponse response2 = GetParametersByPathResponse.builder().parameters(parameters2).build(); + + Mockito.when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response1, response2); + + Map<String, String> params = provider.getMultiple("/prod/app1"); + + assertThat(params).contains( + MapEntry.entry("key1", "foo1"), + MapEntry.entry("key2", "foo2"), + MapEntry.entry("key3", "foo3")); + + List<GetParametersByPathRequest> requestParams = paramByPathCaptor.getAllValues(); + GetParametersByPathRequest request1 = requestParams.get(0); + GetParametersByPathRequest request2 = requestParams.get(1); + + assertThat(asList(request1, request2)) + .isNotEmpty() + .allSatisfy(req -> { + assertThat(req.path()).isEqualTo("/prod/app1"); + assertThat(req.withDecryption()).isFalse(); + assertThat(req.recursive()).isFalse(); + }); + + assertThat(request1.nextToken()).isNull(); + assertThat(request2.nextToken()).isEqualTo("123abc"); + } + + @Test + void testSSMProvider_withoutParameter_shouldHaveDefaultTransformationManager() { + + // Act + SSMProvider ssmProvider = SSMProvider.builder() + .build(); + // Assert + assertDoesNotThrow(() -> ssmProvider.withTransformation(json)); + } + + @Test + void withDecryption_concurrentCalls_shouldBeThreadSafe() throws InterruptedException { + // GIVEN + Parameter param1 = Parameter.builder().value("value1").build(); + Parameter param2 = Parameter.builder().value("value2").build(); + GetParameterResponse response1 = GetParameterResponse.builder().parameter(param1).build(); + GetParameterResponse response2 = GetParameterResponse.builder().parameter(param2).build(); + CountDownLatch latch = new CountDownLatch(2); + Mockito.when(client.getParameter(paramCaptor.capture())) + .thenReturn(response1, response2); + + // WHEN + Thread thread1 = new Thread(() -> { + try { + latch.countDown(); + latch.await(); + provider.withDecryption().getValue("key1"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + + Thread thread2 = new Thread(() -> { + try { + latch.countDown(); + latch.await(); + provider.getValue("key2"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + + thread1.start(); + thread2.start(); + thread1.join(); + thread2.join(); + + // THEN + List<GetParameterRequest> requests = paramCaptor.getAllValues(); + assertThat(requests) + .hasSize(2) + .anyMatch(GetParameterRequest::withDecryption) + .anyMatch(r -> !r.withDecryption()); + } + + @Test + void recursive_concurrentCalls_shouldBeThreadSafe() throws InterruptedException { + // GIVEN + List<Parameter> params1 = new ArrayList<>(); + params1.add(Parameter.builder().name("/path1/key1").value("value1").build()); + List<Parameter> params2 = new ArrayList<>(); + params2.add(Parameter.builder().name("/path2/key2").value("value2").build()); + GetParametersByPathResponse response1 = GetParametersByPathResponse.builder().parameters(params1).build(); + GetParametersByPathResponse response2 = GetParametersByPathResponse.builder().parameters(params2).build(); + CountDownLatch latch = new CountDownLatch(2); + Mockito.when(client.getParametersByPath(paramByPathCaptor.capture())) + .thenReturn(response1, response2); + + // WHEN + Thread thread1 = new Thread(() -> { + try { + latch.countDown(); + latch.await(); + provider.recursive().getMultiple("/path1"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + + Thread thread2 = new Thread(() -> { + try { + latch.countDown(); + latch.await(); + provider.getMultiple("/path2"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + + thread1.start(); + thread2.start(); + thread1.join(); + thread2.join(); + + // THEN + List<GetParametersByPathRequest> requests = paramByPathCaptor.getAllValues(); + assertThat(requests) + .hasSize(2) + .anyMatch(GetParametersByPathRequest::recursive) + .anyMatch(r -> !r.recursive()); + } + + private void initMock(String expectedValue) { + Parameter parameter = Parameter.builder().value(expectedValue).build(); + GetParameterResponse result = GetParameterResponse.builder().parameter(parameter).build(); + Mockito.when(client.getParameter(paramCaptor.capture())).thenReturn(result); + provider.withMaxAge(2, ChronoUnit.DAYS); + provider.recursive(); + } + +} diff --git a/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/internal/ParametersSsmUserAgentInterceptorTest.java b/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/internal/ParametersSsmUserAgentInterceptorTest.java new file mode 100644 index 000000000..8f6db7e21 --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/internal/ParametersSsmUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.parameters.ssm.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class ParametersSsmUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/PARAMETERS-SSM/"); + } +} \ No newline at end of file diff --git a/powertools-parameters/powertools-parameters-tests/pom.xml b/powertools-parameters/powertools-parameters-tests/pom.xml new file mode 100644 index 000000000..fa2542730 --- /dev/null +++ b/powertools-parameters/powertools-parameters-tests/pom.xml @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parent</artifactId> + <version>2.9.0</version> + <relativePath>../../pom.xml</relativePath> + </parent> + + <name>Powertools for AWS Lambda (Java) library Parameters - Tests</name> + <artifactId>powertools-parameters-tests</artifactId> + <description>Powertools parameters tests that cut across all the parameters providers</description> + + <dependencies> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters-ssm</artifactId> + <scope>test</scope> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters-secrets</artifactId> + <scope>test</scope> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-parameters-dynamodb</artifactId> + <scope>test</scope> + <version>${project.version}</version> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjweaver</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>default</id> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>generate-graalvm-files</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-subclass</artifactId> + <scope>test</scope> + </dependency> + + <!-- Required explicitly for @Captor ArgumentCaptor --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-parameters</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.4</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/BaseProviderTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/BaseProviderTest.java similarity index 76% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/BaseProviderTest.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/BaseProviderTest.java index 8dd2d7658..dd31ce016 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/BaseProviderTest.java +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/BaseProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,20 +11,8 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.parameters; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import software.amazon.lambda.powertools.parameters.cache.CacheManager; -import software.amazon.lambda.powertools.parameters.transform.ObjectToDeserialize; -import software.amazon.lambda.powertools.parameters.transform.TransformationManager; -import software.amazon.lambda.powertools.parameters.transform.Transformer; -import java.time.Clock; -import java.time.temporal.ChronoUnit; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; +package software.amazon.lambda.powertools.parameters; import static java.time.Clock.offset; import static java.time.Duration.of; @@ -32,59 +20,42 @@ import static java.time.temporal.ChronoUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -import static org.mockito.MockitoAnnotations.openMocks; import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; import static software.amazon.lambda.powertools.parameters.transform.Transformer.json; -public class BaseProviderTest { +import java.time.Clock; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.ObjectToDeserialize; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; +import software.amazon.lambda.powertools.parameters.transform.Transformer; + +class BaseProviderTest { Clock clock; CacheManager cacheManager; TransformationManager transformationManager; BasicProvider provider; - boolean getFromStore = false; - class BasicProvider extends BaseProvider { - - public BasicProvider(CacheManager cacheManager) { - super(cacheManager); - } - - private String value = "valueFromStore"; - - public void setValue(String value) { - this.value = value; - } - - @Override - protected String getValue(String key) { - getFromStore = true; - return value; - } - - @Override - protected Map<String, String> getMultipleValues(String path) { - getFromStore = true; - Map<String, String> map = new HashMap<>(); - map.put(path, value); - return map; - } - } - @BeforeEach - public void setup() { - openMocks(this); - + void setup() { clock = Clock.systemDefaultZone(); cacheManager = new CacheManager(); - provider = new BasicProvider(cacheManager); transformationManager = new TransformationManager(); - provider.setTransformationManager(transformationManager); + provider = new BasicProvider(cacheManager, transformationManager); } @Test - public void get_notCached_shouldGetValue() { + void get_notCached_shouldGetValue() { String foo = provider.get("toto"); assertThat(foo).isEqualTo("valueFromStore"); @@ -92,7 +63,7 @@ public void get_notCached_shouldGetValue() { } @Test - public void get_cached_shouldGetFromCache() { + void get_cached_shouldGetFromCache() { provider.get("foo"); getFromStore = false; @@ -102,7 +73,7 @@ public void get_cached_shouldGetFromCache() { } @Test - public void get_expired_shouldGetValue() { + void get_expired_shouldGetValue() { provider.get("bar"); getFromStore = false; @@ -113,7 +84,7 @@ public void get_expired_shouldGetValue() { } @Test - public void getMultiple_notCached_shouldGetValue() { + void getMultiple_notCached_shouldGetValue() { Map<String, String> foo = provider.getMultiple("toto"); assertThat(foo.get("toto")).isEqualTo("valueFromStore"); @@ -121,7 +92,7 @@ public void getMultiple_notCached_shouldGetValue() { } @Test - public void getMultiple_cached_shouldGetFromCache() { + void getMultiple_cached_shouldGetFromCache() { provider.getMultiple("foo"); getFromStore = false; @@ -131,7 +102,7 @@ public void getMultiple_cached_shouldGetFromCache() { } @Test - public void getMultiple_expired_shouldGetValue() { + void getMultiple_expired_shouldGetValue() { provider.getMultiple("bar"); getFromStore = false; @@ -142,7 +113,7 @@ public void getMultiple_expired_shouldGetValue() { } @Test - public void get_customTTL_cached_shouldGetFromCache() { + void get_customTTL_cached_shouldGetFromCache() { provider.withMaxAge(12, ChronoUnit.MINUTES).get("key"); getFromStore = false; @@ -153,7 +124,7 @@ public void get_customTTL_cached_shouldGetFromCache() { } @Test - public void get_customTTL_expired_shouldGetValue() { + void get_customTTL_expired_shouldGetValue() { provider.withMaxAge(2, ChronoUnit.MINUTES).get("mykey"); getFromStore = false; @@ -164,8 +135,9 @@ public void get_customTTL_expired_shouldGetValue() { } @Test - public void get_customDefaultTTL_cached_shouldGetFromCache() { - provider.defaultMaxAge(12, ChronoUnit.MINUTES).get("foobar"); + void get_customDefaultTTL_cached_shouldGetFromCache() { + provider.cacheManager.setDefaultExpirationTime(Duration.of(12, MINUTES)); + provider.get("foobar"); getFromStore = false; provider.setClock(offset(clock, of(10, MINUTES))); @@ -175,8 +147,8 @@ public void get_customDefaultTTL_cached_shouldGetFromCache() { } @Test - public void get_customDefaultTTL_expired_shouldGetValue() { - provider.defaultMaxAge(2, ChronoUnit.MINUTES).get("barbaz"); + void get_customDefaultTTL_expired_shouldGetValue() { + provider.cacheManager.setDefaultExpirationTime(Duration.of(2, MINUTES)); getFromStore = false; provider.setClock(offset(clock, of(3, MINUTES))); @@ -186,10 +158,8 @@ public void get_customDefaultTTL_expired_shouldGetValue() { } @Test - public void get_customDefaultTTLAndTTL_cached_shouldGetFromCache() { - provider.defaultMaxAge(12, ChronoUnit.MINUTES) - .withMaxAge(5, SECONDS) - .get("foobaz"); + void get_customDefaultTTLAndTTL_cached_shouldGetFromCache() { + provider.get("foobaz"); getFromStore = false; provider.setClock(offset(clock, of(4, SECONDS))); @@ -199,10 +169,11 @@ public void get_customDefaultTTLAndTTL_cached_shouldGetFromCache() { } @Test - public void get_customDefaultTTLAndTTL_expired_shouldGetValue() { - provider.defaultMaxAge(2, ChronoUnit.MINUTES) - .withMaxAge(5, SECONDS) - .get("bariton"); + void get_customDefaultTTLAndTTL_expired_shouldGetValue() { + + provider.cacheManager.setDefaultExpirationTime(Duration.ofMinutes(2)); + + provider.withMaxAge(5, SECONDS).get("bariton"); getFromStore = false; provider.setClock(offset(clock, of(6, SECONDS))); @@ -212,7 +183,7 @@ public void get_customDefaultTTLAndTTL_expired_shouldGetValue() { } @Test - public void get_basicTransformation_shouldTransformInString() { + void get_basicTransformation_shouldTransformInString() { provider.setValue(Base64.getEncoder().encodeToString("bar".getBytes())); String value = provider.withTransformation(Transformer.base64).get("base64"); @@ -221,19 +192,20 @@ public void get_basicTransformation_shouldTransformInString() { } @Test - public void get_complexTransformation_shouldTransformInObject() { + void get_complexTransformation_shouldTransformInObject() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); - ObjectToDeserialize objectToDeserialize = provider.withTransformation(json).get("foo", ObjectToDeserialize.class); + ObjectToDeserialize objectToDeserialize = provider.withTransformation(json).get("foo", + ObjectToDeserialize.class); assertThat(objectToDeserialize).matches( - o -> o.getFoo().equals("Foo") + o -> "Foo".equals(o.getFoo()) && o.getBar() == 42 && o.getBaz() == 123456789); } @Test - public void getObject_notCached_shouldGetValue() { + void getObject_notCached_shouldGetValue() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); ObjectToDeserialize foo = provider.withTransformation(json).get("foo", ObjectToDeserialize.class); @@ -243,7 +215,7 @@ public void getObject_notCached_shouldGetValue() { } @Test - public void getObject_cached_shouldGetFromCache() { + void getObject_cached_shouldGetFromCache() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); provider.withTransformation(json).get("foo", ObjectToDeserialize.class); @@ -255,7 +227,7 @@ public void getObject_cached_shouldGetFromCache() { } @Test - public void getObject_expired_shouldGetValue() { + void getObject_expired_shouldGetValue() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); provider.withTransformation(json).get("foo", ObjectToDeserialize.class); @@ -268,7 +240,7 @@ public void getObject_expired_shouldGetValue() { } @Test - public void getObject_customTTL_cached_shouldGetFromCache() { + void getObject_customTTL_cached_shouldGetFromCache() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); provider.withMaxAge(12, ChronoUnit.MINUTES) @@ -283,7 +255,7 @@ public void getObject_customTTL_cached_shouldGetFromCache() { } @Test - public void getObject_customTTL_expired_shouldGetValue() { + void getObject_customTTL_expired_shouldGetValue() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); provider.withMaxAge(2, ChronoUnit.MINUTES) @@ -298,11 +270,12 @@ public void getObject_customTTL_expired_shouldGetValue() { } @Test - public void getObject_customDefaultTTL_cached_shouldGetFromCache() { + void getObject_customDefaultTTL_cached_shouldGetFromCache() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); - provider.defaultMaxAge(12, ChronoUnit.MINUTES) - .withTransformation(json) + provider.cacheManager.setDefaultExpirationTime(Duration.of(12, MINUTES)); + + provider.withTransformation(json) .get("foo", ObjectToDeserialize.class); getFromStore = false; @@ -313,11 +286,12 @@ public void getObject_customDefaultTTL_cached_shouldGetFromCache() { } @Test - public void getObject_customDefaultTTL_expired_shouldGetValue() { + void getObject_customDefaultTTL_expired_shouldGetValue() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); - provider.defaultMaxAge(2, ChronoUnit.MINUTES) - .withTransformation(json) + provider.cacheManager.setDefaultExpirationTime(Duration.of(2, MINUTES)); + + provider.withTransformation(json) .get("foo", ObjectToDeserialize.class); getFromStore = false; @@ -328,12 +302,13 @@ public void getObject_customDefaultTTL_expired_shouldGetValue() { } @Test - public void getObject_customDefaultTTLAndTTL_cached_shouldGetFromCache() { + void getObject_customDefaultTTLAndTTL_cached_shouldGetFromCache() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); - provider.defaultMaxAge(12, ChronoUnit.MINUTES) + provider.cacheManager.setDefaultExpirationTime(Duration.ofSeconds(5)); + + provider.withTransformation(json) .withMaxAge(5, SECONDS) - .withTransformation(json) .get("foo", ObjectToDeserialize.class); getFromStore = false; @@ -344,11 +319,12 @@ public void getObject_customDefaultTTLAndTTL_cached_shouldGetFromCache() { } @Test - public void getObject_customDefaultTTLAndTTL_expired_shouldGetValue() { + void getObject_customDefaultTTLAndTTL_expired_shouldGetValue() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); - provider.defaultMaxAge(2, ChronoUnit.MINUTES) - .withMaxAge(5, SECONDS) + provider.cacheManager.setDefaultExpirationTime(Duration.ofMinutes(2)); + + provider.withMaxAge(5, SECONDS) .withTransformation(json) .get("foo", ObjectToDeserialize.class); getFromStore = false; @@ -360,23 +336,22 @@ public void getObject_customDefaultTTLAndTTL_expired_shouldGetValue() { } @Test - public void get_noTransformationManager_shouldThrowException() { - provider.setTransformationManager(null); + void get_noTransformationManager_shouldThrowException() { + provider = new BasicProvider(new CacheManager(), null); assertThatIllegalStateException() .isThrownBy(() -> provider.withTransformation(base64).get("foo")); } @Test - public void getObject_noTransformationManager_shouldThrowException() { - provider.setTransformationManager(null); + void getObject_noTransformationManager_shouldThrowException() { assertThatIllegalStateException() .isThrownBy(() -> provider.get("foo", ObjectToDeserialize.class)); } @Test - public void getTwoParams_shouldResetTTLOptionsInBetween() { + void getTwoParams_shouldResetTTLOptionsInBetween() { provider.withMaxAge(50, SECONDS).get("foo50"); provider.get("foo5"); @@ -390,7 +365,7 @@ public void getTwoParams_shouldResetTTLOptionsInBetween() { } @Test - public void getTwoParams_shouldResetTransformationOptionsInBetween() { + void getTwoParams_shouldResetTransformationOptionsInBetween() { provider.setValue(Base64.getEncoder().encodeToString("base64encoded".getBytes())); String foob64 = provider.withTransformation(base64).get("foob64"); @@ -400,4 +375,31 @@ public void getTwoParams_shouldResetTransformationOptionsInBetween() { assertThat(foob64).isEqualTo("base64encoded"); assertThat(foostr).isEqualTo("string"); } + + class BasicProvider extends BaseProvider { + + private String value = "valueFromStore"; + + public BasicProvider(CacheManager cacheManager, TransformationManager transformationManager) { + super(cacheManager, transformationManager); + } + + void setValue(String value) { + this.value = value; + } + + @Override + protected String getValue(String key) { + getFromStore = true; + return value; + } + + @Override + protected Map<String, String> getMultipleValues(String path) { + getFromStore = true; + Map<String, String> map = new HashMap<>(); + map.put(path, value); + return map; + } + } } diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/ParamProvidersIntegrationTest.java similarity index 73% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/ParamProvidersIntegrationTest.java index 0b4a2093f..5bc609777 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/ParamProvidersIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,36 +11,48 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.parameters; +import static org.assertj.core.api.Assertions.assertThat; +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 java.util.ArrayList; +import java.util.List; +import java.util.Map; + import org.assertj.core.data.MapEntry; -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 software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse; import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.ssm.model.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; -import static org.mockito.MockitoAnnotations.openMocks; - -public class ParamManagerIntegrationTest { - +import software.amazon.awssdk.services.ssm.model.GetParameterRequest; +import software.amazon.awssdk.services.ssm.model.GetParameterResponse; +import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest; +import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse; +import software.amazon.awssdk.services.ssm.model.Parameter; +import software.amazon.lambda.powertools.parameters.secrets.SecretsProvider; +import software.amazon.lambda.powertools.parameters.ssm.SSMProvider; + +@ExtendWith(MockitoExtension.class) +class ParamProvidersIntegrationTest { @Mock SsmClient ssmClient; + @Mock + DynamoDbClient ddbClient; + @Captor ArgumentCaptor<GetParameterRequest> ssmParamCaptor; @@ -53,17 +65,11 @@ public class ParamManagerIntegrationTest { @Captor ArgumentCaptor<GetSecretValueRequest> secretsCaptor; - - @BeforeEach - public void setup() throws IllegalAccessException { - openMocks(this); - - writeStaticField(ParamManager.class, "providers", new ConcurrentHashMap<>(), true); - } - @Test - public void ssmProvider_get() { - SSMProvider ssmProvider = ParamManager.getSsmProvider(ssmClient); + void ssmProvider_get() { + SSMProvider ssmProvider = SSMProvider.builder() + .withClient(ssmClient) + .build(); String expectedValue = "value"; Parameter parameter = Parameter.builder().value(expectedValue).build(); @@ -78,8 +84,10 @@ public void ssmProvider_get() { } @Test - public void ssmProvider_getMultiple() { - SSMProvider ssmProvider = ParamManager.getSsmProvider(ssmClient); + void ssmProvider_getMultiple() { + SSMProvider ssmProvider = SSMProvider.builder() + .withClient(ssmClient) + .build(); List<Parameter> parameters = new ArrayList<>(); parameters.add(Parameter.builder().name("/prod/app1/key1").value("foo1").build()); @@ -103,8 +111,10 @@ public void ssmProvider_getMultiple() { } @Test - public void secretsProvider_get() { - SecretsProvider secretsProvider = ParamManager.getSecretsProvider(secretsManagerClient); + void secretsProvider_get() { + SecretsProvider secretsProvider = SecretsProvider.builder() + .withClient(secretsManagerClient) + .build(); String expectedValue = "Value1"; GetSecretValueResponse response = GetSecretValueResponse.builder().secretString(expectedValue).build(); @@ -116,4 +126,5 @@ public void secretsProvider_get() { assertThat(secretsProvider.get("keys")).isEqualTo(expectedValue); // second time is from cache verify(secretsManagerClient, times(1)).getSecretValue(any(GetSecretValueRequest.class)); } + } diff --git a/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/cache/CacheManagerTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/cache/CacheManagerTest.java new file mode 100644 index 000000000..d22572fad --- /dev/null +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/cache/CacheManagerTest.java @@ -0,0 +1,196 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.cache; + +import static java.time.Clock.offset; +import static java.time.Duration.of; +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Clock; +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CacheManagerTest { + + CacheManager manager; + + Clock clock; + + @BeforeEach + void setup() { + clock = Clock.systemDefaultZone(); + manager = new CacheManager(); + } + + @Test + void getIfNotExpired_notExpired_shouldReturnValue() { + manager.putInCache("key", "value"); + + Optional<String> value = manager.getIfNotExpired("key", clock.instant()); + + assertThat(value).isPresent().contains("value"); + } + + @Test + void getIfNotExpired_expired_shouldReturnNothing() { + manager.putInCache("key", "value"); + + Optional<String> value = manager.getIfNotExpired("key", offset(clock, of(6, SECONDS)).instant()); + + assertThat(value).isNotPresent(); + } + + @Test + void getIfNotExpired_withCustomExpirationTime_notExpired_shouldReturnValue() { + manager.setExpirationTime(of(42, SECONDS)); + manager.putInCache("key", "value"); + + Optional<String> value = manager.getIfNotExpired("key", offset(clock, of(40, SECONDS)).instant()); + + assertThat(value).isPresent().contains("value"); + } + + @Test + void getIfNotExpired_withCustomDefaultExpirationTime_notExpired_shouldReturnValue() { + manager.setDefaultExpirationTime(of(42, SECONDS)); + manager.putInCache("key", "value"); + + Optional<String> value = manager.getIfNotExpired("key", offset(clock, of(40, SECONDS)).instant()); + + assertThat(value).isPresent().contains("value"); + } + + @Test + void getIfNotExpired_customDefaultExpirationTime_customExpirationTime_shouldUseExpirationTime() { + manager.setDefaultExpirationTime(of(42, SECONDS)); + manager.setExpirationTime(of(2, SECONDS)); + manager.putInCache("key", "value"); + + Optional<String> value = manager.getIfNotExpired("key", offset(clock, of(40, SECONDS)).instant()); + + assertThat(value).isNotPresent(); + } + + @Test + void getIfNotExpired_resetExpirationTime_shouldUseDefaultExpirationTime() { + manager.setDefaultExpirationTime(of(42, SECONDS)); + manager.setExpirationTime(of(2, SECONDS)); + manager.putInCache("key", "value"); + manager.resetExpirationTime(); + manager.putInCache("key2", "value2"); + + Optional<String> value = manager.getIfNotExpired("key", offset(clock, of(40, SECONDS)).instant()); + Optional<String> value2 = manager.getIfNotExpired("key2", offset(clock, of(40, SECONDS)).instant()); + + assertThat(value).isNotPresent(); + assertThat(value2).isPresent().contains("value2"); + } + + @Test + void putInCache_sharedCache_shouldBeAccessibleAcrossThreads() throws InterruptedException { + // GIVEN + Thread thread1 = new Thread(() -> { + manager.setExpirationTime(of(60, SECONDS)); + manager.putInCache("sharedKey", "valueFromThread1"); + manager.resetExpirationTime(); + }); + + Thread thread2 = new Thread(() -> { + manager.setExpirationTime(of(10, SECONDS)); + // Thread 2 should be able to read the value cached by Thread 1 + Optional<String> value = manager.getIfNotExpired("sharedKey", clock.instant()); + assertThat(value).isPresent().contains("valueFromThread1"); + manager.resetExpirationTime(); + }); + + // WHEN + thread1.start(); + thread1.join(); + thread2.start(); + thread2.join(); + + // THEN - Both threads should be able to access the same cached value + Optional<String> value = manager.getIfNotExpired("sharedKey", clock.instant()); + assertThat(value).isPresent().contains("valueFromThread1"); + } + + @Test + void putInCache_concurrentCalls_shouldBeThreadSafe() throws InterruptedException { + // GIVEN + int threadCount = 10; + Thread[] threads = new Thread[threadCount]; + boolean[] success = new boolean[threadCount]; + Clock testClock = Clock.systemDefaultZone(); + + // WHEN - Multiple threads set different expiration times and cache values concurrently + for (int i = 0; i < threadCount; i++) { + final int threadIndex = i; + final int expirationSeconds = (i % 2 == 0) ? 60 : 10; // Alternate between 60s and 10s + + threads[i] = new Thread(() -> { + try { + manager.setExpirationTime(of(expirationSeconds, SECONDS)); + manager.putInCache("key" + threadIndex, "value" + threadIndex); + manager.resetExpirationTime(); + success[threadIndex] = true; + } catch (Exception e) { + success[threadIndex] = false; + } + }); + } + + // Start all threads + for (Thread thread : threads) { + thread.start(); + } + + // Wait for all threads to complete + for (Thread thread : threads) { + thread.join(); + } + + // THEN - All threads should complete successfully + for (boolean result : success) { + assertThat(result).isTrue(); + } + + // THEN - Each cached value should have the correct expiration time + // Values with 60s TTL should still be present after 9s, values with 10s should expire after 11s + for (int i = 0; i < threadCount; i++) { + final int expirationSeconds = (i % 2 == 0) ? 60 : 10; + + // Check that value is still present just before expiration + Optional<String> valueBeforeExpiry = manager.getIfNotExpired("key" + i, + offset(testClock, of(expirationSeconds - 1, SECONDS)).instant()); + assertThat(valueBeforeExpiry) + .as("Thread %d with %ds expiration should still have value after %ds", i, expirationSeconds, + expirationSeconds - 1) + .isPresent() + .contains("value" + i); + + // Check that value expires after the TTL + Optional<String> valueAfterExpiry = manager.getIfNotExpired("key" + i, + offset(testClock, of(expirationSeconds + 1, SECONDS)).instant()); + assertThat(valueAfterExpiry) + .as("Thread %d with %ds expiration should not have value after %ds", i, expirationSeconds, + expirationSeconds + 1) + .isNotPresent(); + } + } + +} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/cache/DataStoreTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/cache/DataStoreTest.java similarity index 82% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/cache/DataStoreTest.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/cache/DataStoreTest.java index c68992bf1..0de31e63d 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/cache/DataStoreTest.java +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/cache/DataStoreTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,48 +11,48 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.parameters.cache; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import java.time.Clock; -import java.time.Instant; +package software.amazon.lambda.powertools.parameters.cache; import static java.time.Clock.offset; import static java.time.Duration.of; import static java.time.temporal.ChronoUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; -public class DataStoreTest { +import java.time.Clock; +import java.time.Instant; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class DataStoreTest { Clock clock; DataStore store; @BeforeEach - public void setup() { + void setup() { clock = Clock.systemDefaultZone(); store = new DataStore(); } @Test - public void put_shouldInsertInStore() { + void put_shouldInsertInStore() { store.put("key", "value", Instant.now()); assertThat(store.get("key")).isEqualTo("value"); } @Test - public void get_invalidKey_shouldReturnNull() { + void get_invalidKey_shouldReturnNull() { assertThat(store.get("key")).isNull(); } @Test - public void hasExpired_invalidKey_shouldReturnTrue() { + void hasExpired_invalidKey_shouldReturnTrue() { assertThat(store.hasExpired("key", clock.instant())).isTrue(); } @Test - public void hasExpired_notExpired_shouldReturnFalse() { + void hasExpired_notExpired_shouldReturnFalse() { Instant now = Instant.now(); store.put("key", "value", now.plus(10, SECONDS)); @@ -61,7 +61,7 @@ public void hasExpired_notExpired_shouldReturnFalse() { } @Test - public void hasExpired_expired_shouldReturnTrueAndRemoveElement() { + void hasExpired_expired_shouldReturnTrueAndRemoveElement() { Instant now = Instant.now(); store.put("key", "value", now.plus(10, SECONDS)); diff --git a/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java new file mode 100644 index 000000000..5ce2b355f --- /dev/null +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.internal; + +public class AnotherObject { + + private String another; + private int object; + + public AnotherObject() { + } + + public String getAnother() { + return another; + } + + public void setAnother(String another) { + this.another = another; + } + + public int getObject() { + return object; + } + + public void setObject(int object) { + this.object = object; + } +} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java similarity index 57% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java index e58ef746c..a98d68c22 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java @@ -1,18 +1,31 @@ -package software.amazon.lambda.powertools.parameters.internal; +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ -import software.amazon.lambda.powertools.parameters.BaseProvider; -import software.amazon.lambda.powertools.parameters.cache.CacheManager; +package software.amazon.lambda.powertools.parameters.internal; import java.util.Base64; import java.util.HashMap; import java.util.Map; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; public class CustomProvider extends BaseProvider { private final Map<String, String> values = new HashMap<>(); public CustomProvider(CacheManager cacheManager) { - super(cacheManager); + super(cacheManager, null); values.put("/simple", "value"); values.put("/base64", Base64.getEncoder().encodeToString("value".getBytes())); values.put("/json", "{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/Base64TransformerTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/Base64TransformerTest.java similarity index 86% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/Base64TransformerTest.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/Base64TransformerTest.java index 428b7e0ab..8dbddacf6 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/Base64TransformerTest.java +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/Base64TransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,20 +11,20 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.parameters.transform; -import org.junit.jupiter.api.Test; -import software.amazon.lambda.powertools.parameters.exception.TransformationException; - -import java.util.Base64; +package software.amazon.lambda.powertools.parameters.transform; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -public class Base64TransformerTest { +import java.util.Base64; +import org.junit.jupiter.api.Test; +import software.amazon.lambda.powertools.parameters.exception.TransformationException; + +class Base64TransformerTest { @Test - public void transform_base64_shouldTransformInString() { + void transform_base64_shouldTransformInString() { Base64Transformer transformer = new Base64Transformer(); String s = transformer.applyTransformation(Base64.getEncoder().encodeToString("foobar".getBytes())); @@ -33,7 +33,7 @@ public void transform_base64_shouldTransformInString() { } @Test - public void transform_base64WrongFormat_shouldThrowException() { + void transform_base64WrongFormat_shouldThrowException() { Base64Transformer transformer = new Base64Transformer(); assertThatExceptionOfType(TransformationException.class) diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformerTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformerTest.java similarity index 63% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformerTest.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformerTest.java index fe4fae0bb..48cebb6b0 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformerTest.java +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,35 +11,38 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.parameters.transform; -import org.junit.jupiter.api.Test; -import software.amazon.lambda.powertools.parameters.exception.TransformationException; - -import java.util.Map; +package software.amazon.lambda.powertools.parameters.transform; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.data.MapEntry.entry; -public class JsonTransformerTest { +import java.util.Map; +import org.junit.jupiter.api.Test; +import software.amazon.lambda.powertools.parameters.exception.TransformationException; + +class JsonTransformerTest { @Test - public void transform_json_shouldTransformInObject() throws TransformationException { + void transform_json_shouldTransformInObject() throws TransformationException { JsonTransformer<ObjectToDeserialize> transformation = new JsonTransformer<>(); - ObjectToDeserialize objectToDeserialize = transformation.applyTransformation("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}", ObjectToDeserialize.class); + ObjectToDeserialize objectToDeserialize = + transformation.applyTransformation("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}", + ObjectToDeserialize.class); assertThat(objectToDeserialize).matches( - o -> o.getFoo().equals("Foo") - && o.getBar() == 42 - && o.getBaz() == 123456789); + o -> "Foo".equals(o.getFoo()) + && o.getBar() == 42 + && o.getBaz() == 123456789); } @Test - public void transform_json_shouldTransformInHashMap() throws TransformationException { + void transform_json_shouldTransformInHashMap() throws TransformationException { JsonTransformer<Map> transformation = new JsonTransformer<>(); - Map<String, Object> map = transformation.applyTransformation("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}", Map.class); + Map<String, Object> map = + transformation.applyTransformation("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}", Map.class); assertThat(map).contains( entry("foo", "Foo"), entry("bar", 42), @@ -47,10 +50,11 @@ public void transform_json_shouldTransformInHashMap() throws TransformationExcep } @Test - public void transform_badJson_shouldThrowException() { + void transform_badJson_shouldThrowException() { JsonTransformer<ObjectToDeserialize> transformation = new JsonTransformer<>(); assertThatExceptionOfType(TransformationException.class) - .isThrownBy(() -> transformation.applyTransformation("{\"fo\":\"Foo\", \"bat\":42, \"bau\":123456789}", ObjectToDeserialize.class)); + .isThrownBy(() -> transformation.applyTransformation("{\"fo\":\"Foo\", \"bat\":42, \"bau\":123456789}", + ObjectToDeserialize.class)); } } diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/ObjectToDeserialize.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/ObjectToDeserialize.java similarity index 95% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/ObjectToDeserialize.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/ObjectToDeserialize.java index 0e1fd0f5c..8b30858fd 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/ObjectToDeserialize.java +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/ObjectToDeserialize.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,17 +11,18 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.parameters.transform; public class ObjectToDeserialize { - public ObjectToDeserialize() { - } - private String foo; private int bar; private long baz; + public ObjectToDeserialize() { + } + public String getFoo() { return foo; } diff --git a/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java new file mode 100644 index 000000000..fad6f5391 --- /dev/null +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java @@ -0,0 +1,219 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters.transform; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; +import static software.amazon.lambda.powertools.parameters.transform.Transformer.json; + +import java.util.Base64; +import java.util.concurrent.CountDownLatch; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import software.amazon.lambda.powertools.parameters.exception.TransformationException; + +class TransformationManagerTest { + + TransformationManager manager; + + Class<BasicTransformer> basicTransformer = BasicTransformer.class; + + @BeforeEach + void setup() { + manager = new TransformationManager(); + } + + @Test + void setTransformer_shouldTransform() { + manager.setTransformer(json); + + assertThat(manager.shouldTransform()).isTrue(); + } + + @Test + void notSetTransformer_shouldNotTransform() { + assertThat(manager.shouldTransform()).isFalse(); + } + + @Test + void performBasicTransformation_noTransformer_shouldThrowException() { + assertThatIllegalStateException() + .isThrownBy(() -> manager.performBasicTransformation("value")); + } + + @Test + void performBasicTransformation_notBasicTransformer_shouldThrowException() { + manager.setTransformer(json); + + assertThatIllegalStateException() + .isThrownBy(() -> manager.performBasicTransformation("value")); + } + + @Test + void performBasicTransformation_abstractTransformer_throwsTransformationException() { + manager.setTransformer(basicTransformer); + + assertThatExceptionOfType(TransformationException.class) + .isThrownBy(() -> manager.performBasicTransformation("value")); + } + + @Test + void performBasicTransformation_shouldPerformTransformation() { + manager.setTransformer(base64); + + String expectedValue = "bar"; + String value = manager.performBasicTransformation(Base64.getEncoder().encodeToString(expectedValue.getBytes())); + + assertThat(value).isEqualTo(expectedValue); + } + + @Test + void performComplexTransformation_noTransformer_shouldThrowException() { + assertThatIllegalStateException() + .isThrownBy(() -> manager.performComplexTransformation("value", ObjectToDeserialize.class)); + } + + @Test + void performComplexTransformation_shouldPerformTransformation() { + manager.setTransformer(json); + + ObjectToDeserialize object = manager.performComplexTransformation( + "{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}", + ObjectToDeserialize.class); + + assertThat(object).isNotNull(); + } + + @Test + void performComplexTransformation_throwsTransformationException() { + manager.setTransformer(basicTransformer); + + assertThatExceptionOfType(TransformationException.class) + .isThrownBy(() -> manager.performComplexTransformation("value", ObjectToDeserialize.class)); + } + + @Test + void unsetTransformer_shouldCleanUpThreadLocal() { + // GIVEN + manager.setTransformer(json); + assertThat(manager.shouldTransform()).isTrue(); + + // WHEN + manager.unsetTransformer(); + + // THEN + assertThat(manager.shouldTransform()).isFalse(); + } + + @Test + void setTransformer_concurrentCalls_shouldBeThreadSafe() throws InterruptedException { + // GIVEN + boolean[] success = new boolean[2]; + CountDownLatch latch = new CountDownLatch(2); + + Thread thread1 = new Thread(() -> { + try { + latch.countDown(); + latch.await(); + manager.setTransformer(json); + // Thread 1 expects json transformer + String result = manager.performComplexTransformation( + "{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}", + ObjectToDeserialize.class).getFoo(); + success[0] = "Foo".equals(result); + } catch (Exception e) { + e.printStackTrace(); + success[0] = false; + } + }); + + Thread thread2 = new Thread(() -> { + try { + latch.countDown(); + latch.await(); + manager.setTransformer(base64); + // Thread 2 expects base64 transformer + String result = manager.performBasicTransformation( + Base64.getEncoder().encodeToString("bar".getBytes())); + success[1] = "bar".equals(result); + } catch (Exception e) { + e.printStackTrace(); + success[1] = false; + } + }); + + // WHEN - Start both threads concurrently + thread1.start(); + thread2.start(); + + // THEN - Both threads should complete without errors + thread1.join(); + thread2.join(); + + assertThat(success[0]).as("Thread 1 with JSON transformer should succeed").isTrue(); + assertThat(success[1]).as("Thread 2 with Base64 transformer should succeed").isTrue(); + } + + @Test + void unsetTransformer_concurrentCalls_shouldNotAffectOtherThreads() throws InterruptedException { + // GIVEN + boolean[] success = new boolean[2]; + CountDownLatch latch = new CountDownLatch(2); + + Thread thread1 = new Thread(() -> { + try { + latch.countDown(); + latch.await(); + manager.setTransformer(json); + // Thread 1 should still have json transformer even if thread 2 unsets + assertThat(manager.shouldTransform()).isTrue(); + success[0] = true; + } catch (Exception e) { + e.printStackTrace(); + success[0] = false; + } + }); + + Thread thread2 = new Thread(() -> { + try { + latch.countDown(); + latch.await(); + manager.setTransformer(base64); + manager.unsetTransformer(); + // Thread 2 should have no transformer after unset + assertThat(manager.shouldTransform()).isFalse(); + success[1] = true; + } catch (Exception e) { + e.printStackTrace(); + success[1] = false; + } + }); + + // WHEN + thread1.start(); + thread2.start(); + + // THEN + thread1.join(); + thread2.join(); + + assertThat(success[0]).as("Thread 1 should still have transformer").isTrue(); + assertThat(success[1]).as("Thread 2 should have unset transformer").isTrue(); + } +} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseParamAspect.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseParamAspect.java new file mode 100644 index 000000000..f7ebdf97a --- /dev/null +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseParamAspect.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.parameters; + +import org.aspectj.lang.reflect.FieldSignature; +import software.amazon.lambda.powertools.parameters.transform.Transformer; + +/** + * Provides a common base for all parameter aspects. This lets us group functionality that + * we need to reimplement in each aspect. This class should be extended for each + * additional parameter aspect. + */ +public abstract class BaseParamAspect { + + /** + * Gets the parameter value from the provider and transforms it if necessary. This transformation + * is generic across all parameter providers. + * + * @param key The key of the parameter to get + * @param transformer The transformer to use to transform the parameter value + * @param provider A concrete instance of the parameter provider to retrieve the value from + * @param fieldSignature The signature of the field that the parameter is being injected into + * @return The value of the parameter, transformed if necessary + */ + protected Object getAndTransform(String key, Class<? extends Transformer> transformer, BaseProvider provider, + FieldSignature fieldSignature) { + if (transformer.isInterface()) { + // No transformation + return provider.get(key); + } else { + Class fieldType = fieldSignature.getFieldType(); + if (String.class.isAssignableFrom(fieldType)) { + // Basic transformation + return provider + .withTransformation(transformer) + .get(key); + } else { + // Complex transformation + return provider + .withTransformation(transformer) + .get(key, fieldType); + } + } + } +} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseProvider.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseProvider.java index 706b555c1..d83ff0298 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseProvider.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,14 +11,8 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.parameters; -import software.amazon.awssdk.annotations.NotThreadSafe; -import software.amazon.lambda.powertools.parameters.cache.CacheManager; -import software.amazon.lambda.powertools.parameters.exception.TransformationException; -import software.amazon.lambda.powertools.parameters.transform.BasicTransformer; -import software.amazon.lambda.powertools.parameters.transform.TransformationManager; -import software.amazon.lambda.powertools.parameters.transform.Transformer; +package software.amazon.lambda.powertools.parameters; import java.time.Clock; import java.time.Duration; @@ -26,18 +20,39 @@ import java.time.temporal.ChronoUnit; import java.util.Map; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.exception.TransformationException; +import software.amazon.lambda.powertools.parameters.transform.BasicTransformer; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; +import software.amazon.lambda.powertools.parameters.transform.Transformer; + /** * Base class for all parameter providers. + * <p> + * This class is thread-safe when used as a singleton in multi-threaded environments. + * Configuration methods ({@link #withMaxAge(int, ChronoUnit)}, {@link #withTransformation(Class)}) + * use thread-local storage to support concurrent requests with different requirements. + * <p> + * The cache and transformation managers are thread-safe with zero synchronization overhead, + * using lock-free data structures (ThreadLocal, AtomicReference, ConcurrentHashMap) for optimal performance. + * The cache storage is shared across all threads, allowing cached values to be reused across requests. + * <p> + * <b>Implementation Requirements:</b> Subclasses must ensure that implementations of + * {@link #getValue(String)} and {@link #getMultipleValues(String)} are thread-safe to + * guarantee overall thread-safety of the provider. */ -@NotThreadSafe +@ThreadSafe public abstract class BaseProvider implements ParamProvider { + public static final String PARAMETERS = "parameters"; protected final CacheManager cacheManager; - private TransformationManager transformationManager; + private final TransformationManager transformationManager; private Clock clock; - public BaseProvider(CacheManager cacheManager) { + public BaseProvider(CacheManager cacheManager, TransformationManager transformationManager) { this.cacheManager = cacheManager; + this.transformationManager = transformationManager; } /** @@ -58,25 +73,10 @@ public BaseProvider(CacheManager cacheManager) { */ protected abstract Map<String, String> getMultipleValues(String path); - /** - * (Optional) Set the default max age for the cache of all parameters. Override the default 5 seconds.<br/> - * If for some parameters, you need to set a different maxAge, use {@link #withMaxAge(int, ChronoUnit)}.<br /> - * Use {@link #withMaxAge(int, ChronoUnit)} after {#defaultMaxAge(int, ChronoUnit)} in the chain. - * - * @param maxAge Maximum time to cache the parameter, before calling the underlying parameter store. - * @param unit Unit of time - * @return the provider itself in order to chain calls (eg. <pre>provider.defaultMaxAge(10, SECONDS).get("key")</pre>). - */ - protected BaseProvider defaultMaxAge(int maxAge, ChronoUnit unit) { - Duration duration = Duration.of(maxAge, unit); - cacheManager.setDefaultExpirationTime(duration); - return this; - } - /** * (Optional) Builder method to call before {@link #get(String)} or {@link #get(String, Class)} * to set cache max age for the parameter to get.<br/><br/> - * The max age is reset to default (either 5 or a custom value set with {@link #defaultMaxAge}) after each get, + * The max age is reset to default (either 5 or a custom value that may be set on the CacheManager) after each get, * so you need to use this method for each parameter to cache with non-default max age.<br/><br/> * * <b>Not Thread Safe</b>: calling this method simultaneously by several threads @@ -104,9 +104,11 @@ public BaseProvider withMaxAge(int maxAge, ChronoUnit unit) { * @param transformerClass Class of the transformer to apply. For convenience, you can use {@link Transformer#json} or {@link Transformer#base64} shortcuts. * @return the provider itself in order to chain calls (eg. <pre>provider.withTransformation(json).get("key", MyObject.class)</pre>). */ + @SuppressWarnings("rawtypes") // Transformer type parameter determined at runtime public BaseProvider withTransformation(Class<? extends Transformer> transformerClass) { if (transformationManager == null) { - throw new IllegalStateException("Trying to add transformation while no TransformationManager has been provided."); + throw new IllegalStateException( + "Trying to add transformation while no TransformationManager has been provided."); } transformationManager.setTransformer(transformerClass); return this; @@ -122,14 +124,17 @@ public BaseProvider withTransformation(Class<? extends Transformer> transformerC * eg. getMultiple("/foo/bar") will retrieve [key="baz", value="valuebaz"] for parameter "/foo/bar/baz" */ @Override + @SuppressWarnings("unchecked") // Cache stores Object, safe cast as we control what's stored public Map<String, String> getMultiple(String path) { + // remove trailing whitespace + String pathWithoutTrailingSlash = path.replaceAll("\\/+$", ""); try { - return (Map<String, String>) cacheManager.getIfNotExpired(path, now()).orElseGet(() -> { - Map<String, String> params = getMultipleValues(path); + return (Map<String, String>) cacheManager.getIfNotExpired(pathWithoutTrailingSlash, now()).orElseGet(() -> { + Map<String, String> params = getMultipleValues(pathWithoutTrailingSlash); - cacheManager.putInCache(path, params); + cacheManager.putInCache(pathWithoutTrailingSlash, params); - params.forEach((k, v) -> cacheManager.putInCache(path + "/" + k, v)); + params.forEach((k, v) -> cacheManager.putInCache(pathWithoutTrailingSlash + "/" + k, v)); return params; }); @@ -146,8 +151,8 @@ public Map<String, String> getMultiple(String path) { * * @param key key of the parameter * @return the String value of the parameter - * @throws IllegalStateException if a wrong transformer class is provided through {@link #withTransformation(Class)}. Needs to be a {@link BasicTransformer}. - * @throws TransformationException if the transformation could not be done, because of a wrong format or an error during transformation. + * @throws IllegalStateException if a wrong transformer class is provided through {@link #withTransformation(Class)}. Needs to be a {@link BasicTransformer}. + * @throws TransformationException if the transformation could not be done, because of a wrong format or an error during transformation. */ @Override public String get(final String key) { @@ -179,17 +184,19 @@ public String get(final String key) { * @param key key of the parameter * @param targetClass class of the target Object (after transformation) * @return the Object (T) value of the parameter - * @throws IllegalStateException if no transformation class was provided through {@link #withTransformation(Class)} - * @throws TransformationException if the transformation could not be done, because of a wrong format or an error during transformation. + * @throws IllegalStateException if no transformation class was provided through {@link #withTransformation(Class)} + * @throws TransformationException if the transformation could not be done, because of a wrong format or an error during transformation. */ @Override + @SuppressWarnings("unchecked") // Cache stores Object, safe cast as we control what's stored public <T> T get(final String key, final Class<T> targetClass) { try { return (T) cacheManager.getIfNotExpired(key, now()).orElseGet(() -> { String value = getValue(key); if (transformationManager == null) { - throw new IllegalStateException("Trying to transform value while no TransformationManager has been provided."); + throw new IllegalStateException( + "Trying to transform value while no TransformationManager has been provided."); } T transformedValue = transformationManager.performComplexTransformation(value, targetClass); @@ -213,16 +220,13 @@ protected Instant now() { protected void resetToDefaults() { cacheManager.resetExpirationTime(); if (transformationManager != null) { - transformationManager.setTransformer(null); + transformationManager.unsetTransformer(); } } - protected void setTransformationManager(TransformationManager transformationManager) { - this.transformationManager = transformationManager; - } - /** * For test purpose + * * @param clock */ void setClock(Clock clock) { diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java deleted file mode 100644 index ef3d08b72..000000000 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java +++ /dev/null @@ -1,30 +0,0 @@ -package software.amazon.lambda.powertools.parameters; - -import software.amazon.lambda.powertools.parameters.transform.Transformer; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * {@code Param} is used to signal that the annotated field should be - * populated with a value retrieved from a parameter store through a {@link ParamProvider}. - * - * <p>By default {@code Param} use {@link SSMProvider} as parameter provider. This can be overridden specifying - * the annotation variable {@code Param(provider = <Class-of-the-provider>)}.<br/> - * The library provide a provider for AWS System Manager Parameters Store ({@link SSMProvider}) and a provider - * for AWS Secrets Manager ({@link SecretsProvider}). - * The user can implement a custom provider by extending the abstract class {@link BaseProvider}.</p> - * - * <p>If the parameter value requires transformation before being assigned to the annotated field - * users can specify a {@link Transformer} - * </p> - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -public @interface Param { - String key(); - Class<? extends BaseProvider> provider() default SSMProvider.class; - Class<? extends Transformer> transformer() default Transformer.class; -} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java deleted file mode 100644 index 0131ae179..000000000 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. - * 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 - * http://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. - * - */ -package software.amazon.lambda.powertools.parameters; - -import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; -import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.lambda.powertools.parameters.cache.CacheManager; -import software.amazon.lambda.powertools.parameters.transform.TransformationManager; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Utility class to retrieve instances of parameter providers. - * Each instance is unique (singleton). - */ -public final class ParamManager { - - private static final CacheManager cacheManager = new CacheManager(); - private static final TransformationManager transformationManager = new TransformationManager(); - - // NOTE: For testing purposes `providers` cannot be final - private static ConcurrentHashMap<Class<? extends BaseProvider>, BaseProvider> providers = new ConcurrentHashMap<>(); - - /** - * Get a concrete implementation of {@link BaseProvider}.<br/> - * You can specify {@link SecretsProvider} or {@link SSMProvider} or create your custom provider - * by extending {@link BaseProvider} if you need to integrate with a different parameter store. - * @return a {@link SecretsProvider} - */ - public static <T extends BaseProvider> T getProvider(Class<T> providerClass) { - if (providerClass == null) { - throw new IllegalStateException("providerClass cannot be null."); - } - return (T) providers.computeIfAbsent(providerClass, ParamManager::createProvider); - } - - /** - * Get a {@link SecretsProvider} with default {@link SecretsManagerClient}.<br/> - * If you need to customize the region, or other part of the client, use {@link ParamManager#getSecretsProvider(SecretsManagerClient)} instead. - * @return a {@link SecretsProvider} - */ - public static SecretsProvider getSecretsProvider() { - return getProvider(SecretsProvider.class); - } - - /** - * Get a {@link SSMProvider} with default {@link SsmClient}.<br/> - * If you need to customize the region, or other part of the client, use {@link ParamManager#getSsmProvider(SsmClient)} instead. - * @return a {@link SSMProvider} - */ - public static SSMProvider getSsmProvider() { - return getProvider(SSMProvider.class); - } - - /** - * Get a {@link SecretsProvider} with your custom {@link SecretsManagerClient}.<br/> - * Use this to configure region or other part of the client. Use {@link ParamManager#getSsmProvider()} if you don't need this customization. - * @return a {@link SecretsProvider} - */ - public static SecretsProvider getSecretsProvider(SecretsManagerClient client) { - return (SecretsProvider) providers.computeIfAbsent(SecretsProvider.class, (k) -> SecretsProvider.builder() - .withClient(client) - .withCacheManager(cacheManager) - .withTransformationManager(transformationManager) - .build()); - } - - /** - * Get a {@link SSMProvider} with your custom {@link SsmClient}.<br/> - * Use this to configure region or other part of the client. Use {@link ParamManager#getSsmProvider()} if you don't need this customization. - * @return a {@link SSMProvider} - */ - public static SSMProvider getSsmProvider(SsmClient client) { - return (SSMProvider) providers.computeIfAbsent(SSMProvider.class, (k) -> SSMProvider.builder() - .withClient(client) - .withCacheManager(cacheManager) - .withTransformationManager(transformationManager) - .build()); - } - - public static CacheManager getCacheManager() { - return cacheManager; - } - - public static TransformationManager getTransformationManager() { - return transformationManager; - } - - private static <T extends BaseProvider> T createProvider(Class<T> providerClass) { - try { - Constructor<T> constructor = providerClass.getDeclaredConstructor(CacheManager.class); - T provider = constructor.newInstance(cacheManager); - provider.setTransformationManager(transformationManager); - return provider; - } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { - throw new RuntimeException("Unexpected error occurred. Please raise issue at " + - "https://github.com/awslabs/aws-lambda-powertools-java/issues", e); - } - } - -} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamProvider.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamProvider.java index b496ed4f3..ba4232261 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamProvider.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.parameters; import java.util.Map; diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SecretsProvider.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SecretsProvider.java deleted file mode 100644 index a0a8fe51e..000000000 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SecretsProvider.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. - * 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 - * http://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. - * - */ -package software.amazon.lambda.powertools.parameters; - -import java.time.temporal.ChronoUnit; -import java.util.Base64; -import java.util.Map; - -import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; -import software.amazon.awssdk.core.SdkSystemSetting; -import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; -import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; -import software.amazon.lambda.powertools.parameters.cache.CacheManager; -import software.amazon.lambda.powertools.parameters.transform.TransformationManager; -import software.amazon.lambda.powertools.parameters.transform.Transformer; - -import static java.nio.charset.StandardCharsets.UTF_8; - -/** - * AWS Secrets Manager Parameter Provider<br/><br/> - * - * <u>Samples:</u> - * <pre> - * SecretsProvider provider = ParamManager.getSecretsProvider(); - * - * String value = provider.get("key"); - * System.out.println(value); - * >>> "value" - * - * // Get a value and cache it for 30 seconds (all others values will now be cached for 30 seconds) - * String value = provider.defaultMaxAge(30, ChronoUnit.SECONDS).get("key"); - * - * // Get a value and cache it for 1 minute (all others values are cached for 5 seconds by default) - * String value = provider.withMaxAge(1, ChronoUnit.MINUTES).get("key"); - * - * // Get a base64 encoded value, decoded into a String, and store it in the cache - * String value = provider.withTransformation(Transformer.base64).get("key"); - * - * // Get a json value, transform it into an Object, and store it in the cache - * TargetObject = provider.withTransformation(Transformer.json).get("key", TargetObject.class); - * </pre> - */ -public class SecretsProvider extends BaseProvider { - - private final SecretsManagerClient client; - - /** - * Default constructor with default {@link SecretsManagerClient}. <br/> - * Use when you don't need to customize region or any other attribute of the client.<br/><br/> - * - * Use the {@link Builder} to create an instance of it. - */ - SecretsProvider(CacheManager cacheManager) { - this(cacheManager, SecretsManagerClient.builder() - .httpClientBuilder(UrlConnectionHttpClient.builder()) - .credentialsProvider(EnvironmentVariableCredentialsProvider.create()) - .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) - .build()); - } - - /** - * Constructor with custom {@link SecretsManagerClient}. <br/> - * Use when you need to customize region or any other attribute of the client.<br/><br/> - * - * Use the {@link Builder} to create an instance of it. - * - * @param client custom client you would like to use. - */ - SecretsProvider(CacheManager cacheManager, SecretsManagerClient client) { - super(cacheManager); - this.client = client; - } - - /** - * Retrieve the parameter value from the AWS Secrets Manager. - * - * @param key key of the parameter - * @return the value of the parameter identified by the key - */ - @Override - protected String getValue(String key) { - GetSecretValueRequest request = GetSecretValueRequest.builder().secretId(key).build(); - - String secretValue = client.getSecretValue(request).secretString(); - if (secretValue == null) { - secretValue = new String(Base64.getDecoder().decode(client.getSecretValue(request).secretBinary().asByteArray()), UTF_8); - } - return secretValue; - } - - /** - * - * @throws UnsupportedOperationException as it is not possible to get multiple values simultaneously from Secrets Manager - */ - @Override - protected Map<String, String> getMultipleValues(String path) { - throw new UnsupportedOperationException("Impossible to get multiple values from AWS Secrets Manager"); - } - - /** - * {@inheritDoc} - */ - @Override - public SecretsProvider defaultMaxAge(int maxAge, ChronoUnit unit) { - super.defaultMaxAge(maxAge, unit); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public SecretsProvider withMaxAge(int maxAge, ChronoUnit unit) { - super.withMaxAge(maxAge, unit); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public SecretsProvider withTransformation(Class<? extends Transformer> transformerClass) { - super.withTransformation(transformerClass); - return this; - } - - /** - * Create a builder that can be used to configure and create a {@link SecretsProvider}. - * - * @return a new instance of {@link SecretsProvider.Builder} - */ - public static Builder builder() { - return new Builder(); - } - - static class Builder { - - private SecretsManagerClient client; - private CacheManager cacheManager; - private TransformationManager transformationManager; - - /** - * Create a {@link SecretsProvider} instance. - * - * @return a {@link SecretsProvider} - */ - public SecretsProvider build() { - if (cacheManager == null) { - throw new IllegalStateException("No CacheManager provided, please provide one"); - } - SecretsProvider provider; - if (client != null) { - provider = new SecretsProvider(cacheManager, client); - } else { - provider = new SecretsProvider(cacheManager); - } - if (transformationManager != null) { - provider.setTransformationManager(transformationManager); - } - return provider; - } - - /** - * Set custom {@link SecretsManagerClient} to pass to the {@link SecretsProvider}. <br/> - * Use it if you want to customize the region or any other part of the client. - * - * @param client Custom client - * @return the builder to chain calls (eg. <pre>builder.withClient().build()</pre>) - */ - public Builder withClient(SecretsManagerClient client) { - this.client = client; - return this; - } - - /** - * <b>Mandatory</b>. Provide a CacheManager to the {@link SecretsProvider} - * - * @param cacheManager the manager that will handle the cache of parameters - * @return the builder to chain calls (eg. <pre>builder.withCacheManager().build()</pre>) - */ - public Builder withCacheManager(CacheManager cacheManager) { - this.cacheManager = cacheManager; - return this; - } - - /** - * Provide a transformationManager to the {@link SecretsProvider} - * - * @param transformationManager the manager that will handle transformation of parameters - * @return the builder to chain calls (eg. <pre>builder.withTransformationManager().build()</pre>) - */ - public Builder withTransformationManager(TransformationManager transformationManager) { - this.transformationManager = transformationManager; - return this; - } - } -} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/CacheManager.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/CacheManager.java index 687337a96..99c281b3d 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/CacheManager.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/CacheManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,26 +11,36 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.parameters.cache; +import static java.time.temporal.ChronoUnit.SECONDS; + import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; -import static java.time.temporal.ChronoUnit.SECONDS; - +/** + * Manages caching of parameter values with configurable expiration times. + * <p> + * This class is thread-safe. The cache storage is shared across all threads, + * while expiration time configuration is thread-local to support concurrent + * requests with different cache TTL requirements. + */ public class CacheManager { static final Duration DEFAULT_MAX_AGE_SECS = Duration.of(5, SECONDS); private final DataStore store; - private Duration defaultMaxAge = DEFAULT_MAX_AGE_SECS; - private Duration maxAge = defaultMaxAge; + private final AtomicReference<Duration> defaultMaxAge = new AtomicReference<>(DEFAULT_MAX_AGE_SECS); + private final ThreadLocal<Duration> maxAge = ThreadLocal.withInitial(() -> null); public CacheManager() { store = new DataStore(); } + @SuppressWarnings("unchecked") // DataStore stores Object, safe cast as we control what's stored public <T> Optional<T> getIfNotExpired(String key, Instant now) { if (store.hasExpired(key, now)) { return Optional.empty(); @@ -39,19 +49,19 @@ public <T> Optional<T> getIfNotExpired(String key, Instant now) { } public void setExpirationTime(Duration duration) { - this.maxAge = duration; + this.maxAge.set(duration); } public void setDefaultExpirationTime(Duration duration) { - this.defaultMaxAge = duration; - this.maxAge = duration; + this.defaultMaxAge.set(duration); } public <T> void putInCache(String key, T value) { - store.put(key, value, Clock.systemDefaultZone().instant().plus(maxAge)); + Duration effectiveMaxAge = maxAge.get() != null ? maxAge.get() : defaultMaxAge.get(); + store.put(key, value, Clock.systemDefaultZone().instant().plus(effectiveMaxAge)); } public void resetExpirationTime() { - maxAge = defaultMaxAge; + maxAge.remove(); } } diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/DataStore.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/DataStore.java index 351ba054d..4b6350cb5 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/DataStore.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/DataStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,9 +11,11 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.parameters.cache; import java.time.Instant; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** @@ -21,40 +23,45 @@ */ public class DataStore { - private final ConcurrentHashMap<String, ValueNode> store; + private final Map<String, ValueNode> store; public DataStore() { this.store = new ConcurrentHashMap<>(); } - static class ValueNode { - public final Object value; - public final Instant time; - - public ValueNode(Object value, Instant time){ - this.value = value; - this.time = time; - } - } - - public void put(String key, Object value, Instant time){ + public void put(String key, Object value, Instant time) { store.put(key, new ValueNode(value, time)); } - public void remove(String Key){ - store.remove(Key); + public void remove(String key) { + store.remove(key); } public Object get(String key) { - return store.containsKey(key)?store.get(key).value:null; + ValueNode node = store.get(key); + return node != null ? node.value : null; } public boolean hasExpired(String key, Instant now) { - boolean hasExpired = !store.containsKey(key) || now.isAfter(store.get(key).time); + ValueNode node = store.get(key); + if (node == null) { + return true; + } + boolean hasExpired = now.isAfter(node.time); // Auto-clean if the parameter has expired if (hasExpired) { remove(key); } return hasExpired; } + + static class ValueNode { + public final Object value; + public final Instant time; + + public ValueNode(Object value, Instant time) { + this.value = value; + this.time = time; + } + } } diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/TransformationException.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/TransformationException.java index 7d28d12d1..f071c8a6b 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/TransformationException.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/TransformationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.parameters.exception; public class TransformationException extends RuntimeException { @@ -19,6 +20,7 @@ public TransformationException(Exception e) { super(e); } - public TransformationException(String message) { super(message); + public TransformationException(String message) { + super(message); } } diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java deleted file mode 100644 index ea4d465cd..000000000 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java +++ /dev/null @@ -1,43 +0,0 @@ -package software.amazon.lambda.powertools.parameters.internal; - -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.aspectj.lang.reflect.FieldSignature; -import software.amazon.lambda.powertools.parameters.*; - -@Aspect -public class LambdaParametersAspect { - - @Pointcut("get(* *) && @annotation(paramAnnotation)") - public void getParam(Param paramAnnotation) { - } - - @Around("getParam(paramAnnotation)") - public Object injectParam(final ProceedingJoinPoint joinPoint, final Param paramAnnotation) { - if(null == paramAnnotation.provider()) { - throw new IllegalArgumentException("provider for Param annotation cannot be null!"); - } - BaseProvider provider = ParamManager.getProvider(paramAnnotation.provider()); - - if(paramAnnotation.transformer().isInterface()) { - // No transformation - return provider.get(paramAnnotation.key()); - } else { - FieldSignature s = (FieldSignature) joinPoint.getSignature(); - if(String.class.isAssignableFrom(s.getFieldType())) { - // Basic transformation - return provider - .withTransformation(paramAnnotation.transformer()) - .get(paramAnnotation.key()); - } else { - // Complex transformation - return provider - .withTransformation(paramAnnotation.transformer()) - .get(paramAnnotation.key(), s.getFieldType()); - } - } - } - -} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/Base64Transformer.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/Base64Transformer.java index 944f4f03c..e8557ebfd 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/Base64Transformer.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/Base64Transformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,14 +11,14 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.parameters.transform; -import java.util.Base64; +import static java.nio.charset.StandardCharsets.UTF_8; +import java.util.Base64; import software.amazon.lambda.powertools.parameters.exception.TransformationException; -import static java.nio.charset.StandardCharsets.UTF_8; - /** * Transformer that take a base64 encoded string and return a decoded string. */ diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/BasicTransformer.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/BasicTransformer.java index 5251d9f16..92e73d9b0 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/BasicTransformer.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/BasicTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.parameters.transform; import software.amazon.lambda.powertools.parameters.exception.TransformationException; diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformer.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformer.java index d84a1ab3a..0eff58ea8 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformer.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.parameters.transform; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/TransformationManager.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/TransformationManager.java index 00e6f84a9..bddbf81d9 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/TransformationManager.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/TransformationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,35 +11,48 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.parameters.transform; -import software.amazon.lambda.powertools.parameters.exception.TransformationException; +package software.amazon.lambda.powertools.parameters.transform; import java.lang.reflect.InvocationTargetException; +import software.amazon.lambda.powertools.parameters.exception.TransformationException; + /** * Manager in charge of transforming parameter values in another format. <br/> * Leverages a {@link Transformer} in order to perform the transformation. <br/> * The transformer must be passed with {@link #setTransformer(Class)} before performing any transform operation. + * <p> + * This class is thread-safe. Transformer configuration is thread-local to support concurrent + * requests with different transformation requirements. */ public class TransformationManager { - private Class<? extends Transformer> transformer = null; + private final ThreadLocal<Class<? extends Transformer>> transformer = ThreadLocal.withInitial(() -> null); /** * Set the {@link Transformer} to use for transformation. Must be called before any transformation. * * @param transformerClass class of the {@link Transformer} */ + @SuppressWarnings("rawtypes") // Transformer type parameter determined at runtime public void setTransformer(Class<? extends Transformer> transformerClass) { - this.transformer = transformerClass; + this.transformer.set(transformerClass); + } + + /** + * Unset the {@link Transformer} and clean up thread-local storage. + * Should be called after transformation is complete to prevent memory leaks. + */ + public void unsetTransformer() { + this.transformer.remove(); } /** * @return true if a {@link Transformer} has been passed to the Manager */ public boolean shouldTransform() { - return transformer != null; + return transformer.get() != null; } /** @@ -48,17 +61,22 @@ public boolean shouldTransform() { * @param value the value to transform * @return the value transformed */ + @SuppressWarnings("rawtypes") // Transformer type parameter determined at runtime public String performBasicTransformation(String value) { - if (transformer == null) { - throw new IllegalStateException("You cannot perform a transformation without Transformer, use the provider.withTransformation() method to specify it."); + Class<? extends Transformer> transformerClass = transformer.get(); + if (transformerClass == null) { + throw new IllegalStateException( + "You cannot perform a transformation without Transformer, use the provider.withTransformation() method to specify it."); } - if (!BasicTransformer.class.isAssignableFrom(transformer)) { + if (!BasicTransformer.class.isAssignableFrom(transformerClass)) { throw new IllegalStateException("Wrong Transformer for a String, choose a BasicTransformer."); } try { - BasicTransformer basicTransformer = (BasicTransformer) transformer.getDeclaredConstructor().newInstance(null); + BasicTransformer basicTransformer = (BasicTransformer) transformerClass.getDeclaredConstructor() + .newInstance(null); return basicTransformer.applyTransformation(value); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException + | InvocationTargetException e) { throw new TransformationException(e); } } @@ -66,19 +84,23 @@ public String performBasicTransformation(String value) { /** * Transform a String in a Java Object. * - * @param value the value to transform + * @param value the value to transform * @param targetClass the type of the target object. * @return the value transformed in an object ot type T. */ + @SuppressWarnings("rawtypes") // Transformer type parameter determined at runtime public <T> T performComplexTransformation(String value, Class<T> targetClass) { - if (transformer == null) { - throw new IllegalStateException("You cannot perform a transformation without Transformer, use the provider.withTransformation() method to specify it."); + Class<? extends Transformer> transformerClass = transformer.get(); + if (transformerClass == null) { + throw new IllegalStateException( + "You cannot perform a transformation without Transformer, use the provider.withTransformation() method to specify it."); } try { - Transformer<T> complexTransformer = transformer.getDeclaredConstructor().newInstance(null); + Transformer<T> complexTransformer = transformerClass.getDeclaredConstructor().newInstance(null); return complexTransformer.applyTransformation(value, targetClass); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException + | InvocationTargetException e) { throw new TransformationException(e); } } diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/Transformer.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/Transformer.java index 3c57b2aa9..d9aea2644 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/Transformer.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/Transformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.parameters.transform; import software.amazon.lambda.powertools.parameters.exception.TransformationException; @@ -34,7 +35,8 @@ public interface Transformer<T> { /** * Apply a transformation on the input value (String) - * @param value the parameter value to transform + * + * @param value the parameter value to transform * @param targetClass class of the target object * @return a transformed parameter * @throws TransformationException when a transformation error occurs diff --git a/powertools-parameters/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters/reflect-config.json b/powertools-parameters/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters/reflect-config.json new file mode 100644 index 000000000..380489707 --- /dev/null +++ b/powertools-parameters/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-parameters/reflect-config.json @@ -0,0 +1,197 @@ +[ +{ + "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.tools.attach.VirtualMachine" +}, +{ + "name":"java.io.Serializable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.AutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.Class", + "methods":[{"name":"forName","parameterTypes":["java.lang.String"] }, {"name":"getAnnotatedInterfaces","parameterTypes":[] }, {"name":"getAnnotatedSuperclass","parameterTypes":[] }, {"name":"getDeclaredMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getModule","parameterTypes":[] }, {"name":"getNestHost","parameterTypes":[] }, {"name":"getNestMembers","parameterTypes":[] }, {"name":"getPermittedSubclasses","parameterTypes":[] }, {"name":"getRecordComponents","parameterTypes":[] }, {"name":"isNestmateOf","parameterTypes":["java.lang.Class"] }, {"name":"isRecord","parameterTypes":[] }, {"name":"isSealed","parameterTypes":[] }] +}, +{ + "name":"java.lang.ClassLoader", + "methods":[{"name":"getDefinedPackage","parameterTypes":["java.lang.String"] }, {"name":"getUnnamedModule","parameterTypes":[] }, {"name":"registerAsParallelCapable","parameterTypes":[] }] +}, +{ + "name":"java.lang.Module", + "methods":[{"name":"addExports","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"addReads","parameterTypes":["java.lang.Module"] }, {"name":"canRead","parameterTypes":["java.lang.Module"] }, {"name":"getClassLoader","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPackages","parameterTypes":[] }, {"name":"getResourceAsStream","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"isNamed","parameterTypes":[] }, {"name":"isOpen","parameterTypes":["java.lang.String","java.lang.Module"] }] +}, +{ + "name":"java.lang.Object", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"clone","parameterTypes":[] }, {"name":"getClass","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"java.lang.ProcessHandle", + "methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime", + "methods":[{"name":"version","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime$Version", + "methods":[{"name":"feature","parameterTypes":[] }] +}, +{ + "name":"java.lang.StackWalker" +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getSecurityManager","parameterTypes":[] }] +}, +{ + "name":"java.lang.annotation.Retention", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.annotation.Target", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.invoke.MethodHandle", + "methods":[{"name":"bindTo","parameterTypes":["java.lang.Object"] }, {"name":"invokeWithArguments","parameterTypes":["java.lang.Object[]"] }] +}, +{ + "name":"java.lang.invoke.MethodHandles", + "methods":[{"name":"lookup","parameterTypes":[] }] +}, +{ + "name":"java.lang.invoke.MethodHandles$Lookup", + "methods":[{"name":"findVirtual","parameterTypes":["java.lang.Class","java.lang.String","java.lang.invoke.MethodType"] }] +}, +{ + "name":"java.lang.invoke.MethodType", + "methods":[{"name":"methodType","parameterTypes":["java.lang.Class","java.lang.Class[]"] }] +}, +{ + "name":"java.lang.reflect.AccessibleObject", + "methods":[{"name":"setAccessible","parameterTypes":["boolean"] }] +}, +{ + "name":"java.lang.reflect.AnnotatedArrayType", + "methods":[{"name":"getAnnotatedGenericComponentType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedParameterizedType", + "methods":[{"name":"getAnnotatedActualTypeArguments","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedType", + "methods":[{"name":"getType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Executable", + "methods":[{"name":"getAnnotatedExceptionTypes","parameterTypes":[] }, {"name":"getAnnotatedParameterTypes","parameterTypes":[] }, {"name":"getAnnotatedReceiverType","parameterTypes":[] }, {"name":"getParameterCount","parameterTypes":[] }, {"name":"getParameters","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"getAnnotatedReturnType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Parameter", + "methods":[{"name":"getModifiers","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"isNamePresent","parameterTypes":[] }] +}, +{ + "name":"java.security.AccessController", + "methods":[{"name":"doPrivileged","parameterTypes":["java.security.PrivilegedAction"] }, {"name":"doPrivileged","parameterTypes":["java.security.PrivilegedExceptionAction"] }] +}, +{ + "name":"java.util.concurrent.ForkJoinTask", + "fields":[{"name":"aux"}, {"name":"status"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"jdk.internal.misc.Unsafe" +}, +{ + "name":"kotlin.jvm.JvmInline" +}, +{ + "name":"org.apiguardian.api.API", + "queryAllPublicMethods":true +}, +{ + "name":"software.amazon.awssdk.awscore.AwsClient", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"software.amazon.awssdk.core.SdkClient", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"serviceName","parameterTypes":[] }] +}, +{ + "name":"software.amazon.awssdk.services.dynamodb.DynamoDbClient", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"batchExecuteStatement","parameterTypes":["java.util.function.Consumer"] }, {"name":"batchExecuteStatement","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.BatchExecuteStatementRequest"] }, {"name":"batchGetItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"batchGetItem","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.BatchGetItemRequest"] }, {"name":"batchGetItemPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"batchGetItemPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.BatchGetItemRequest"] }, {"name":"batchWriteItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"batchWriteItem","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.BatchWriteItemRequest"] }, {"name":"createBackup","parameterTypes":["java.util.function.Consumer"] }, {"name":"createBackup","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.CreateBackupRequest"] }, {"name":"createGlobalTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"createGlobalTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.CreateGlobalTableRequest"] }, {"name":"createTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"createTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.CreateTableRequest"] }, {"name":"deleteBackup","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteBackup","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DeleteBackupRequest"] }, {"name":"deleteItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteItem","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest"] }, {"name":"deleteResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteResourcePolicy","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DeleteResourcePolicyRequest"] }, {"name":"deleteTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest"] }, {"name":"describeBackup","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeBackup","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeBackupRequest"] }, {"name":"describeContinuousBackups","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeContinuousBackups","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeContinuousBackupsRequest"] }, {"name":"describeContributorInsights","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeContributorInsights","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeContributorInsightsRequest"] }, {"name":"describeEndpoints","parameterTypes":[] }, {"name":"describeEndpoints","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeEndpoints","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeEndpointsRequest"] }, {"name":"describeExport","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeExport","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeExportRequest"] }, {"name":"describeGlobalTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeGlobalTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeGlobalTableRequest"] }, {"name":"describeGlobalTableSettings","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeGlobalTableSettings","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeGlobalTableSettingsRequest"] }, {"name":"describeImport","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeImport","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeImportRequest"] }, {"name":"describeKinesisStreamingDestination","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeKinesisStreamingDestination","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeKinesisStreamingDestinationRequest"] }, {"name":"describeLimits","parameterTypes":[] }, {"name":"describeLimits","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeLimits","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeLimitsRequest"] }, {"name":"describeTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest"] }, {"name":"describeTableReplicaAutoScaling","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeTableReplicaAutoScaling","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeTableReplicaAutoScalingRequest"] }, {"name":"describeTimeToLive","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeTimeToLive","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DescribeTimeToLiveRequest"] }, {"name":"disableKinesisStreamingDestination","parameterTypes":["java.util.function.Consumer"] }, {"name":"disableKinesisStreamingDestination","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.DisableKinesisStreamingDestinationRequest"] }, {"name":"enableKinesisStreamingDestination","parameterTypes":["java.util.function.Consumer"] }, {"name":"enableKinesisStreamingDestination","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.EnableKinesisStreamingDestinationRequest"] }, {"name":"executeStatement","parameterTypes":["java.util.function.Consumer"] }, {"name":"executeStatement","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ExecuteStatementRequest"] }, {"name":"executeTransaction","parameterTypes":["java.util.function.Consumer"] }, {"name":"executeTransaction","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ExecuteTransactionRequest"] }, {"name":"exportTableToPointInTime","parameterTypes":["java.util.function.Consumer"] }, {"name":"exportTableToPointInTime","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ExportTableToPointInTimeRequest"] }, {"name":"getItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"getItem","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.GetItemRequest"] }, {"name":"getResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"getResourcePolicy","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.GetResourcePolicyRequest"] }, {"name":"importTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"importTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ImportTableRequest"] }, {"name":"listBackups","parameterTypes":[] }, {"name":"listBackups","parameterTypes":["java.util.function.Consumer"] }, {"name":"listBackups","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListBackupsRequest"] }, {"name":"listContributorInsights","parameterTypes":["java.util.function.Consumer"] }, {"name":"listContributorInsights","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListContributorInsightsRequest"] }, {"name":"listContributorInsightsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listContributorInsightsPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListContributorInsightsRequest"] }, {"name":"listExports","parameterTypes":["java.util.function.Consumer"] }, {"name":"listExports","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListExportsRequest"] }, {"name":"listExportsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listExportsPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListExportsRequest"] }, {"name":"listGlobalTables","parameterTypes":[] }, {"name":"listGlobalTables","parameterTypes":["java.util.function.Consumer"] }, {"name":"listGlobalTables","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListGlobalTablesRequest"] }, {"name":"listImports","parameterTypes":["java.util.function.Consumer"] }, {"name":"listImports","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListImportsRequest"] }, {"name":"listImportsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listImportsPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListImportsRequest"] }, {"name":"listTables","parameterTypes":[] }, {"name":"listTables","parameterTypes":["java.util.function.Consumer"] }, {"name":"listTables","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListTablesRequest"] }, {"name":"listTablesPaginator","parameterTypes":[] }, {"name":"listTablesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listTablesPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListTablesRequest"] }, {"name":"listTagsOfResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"listTagsOfResource","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ListTagsOfResourceRequest"] }, {"name":"putItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"putItem","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.PutItemRequest"] }, {"name":"putResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"putResourcePolicy","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.PutResourcePolicyRequest"] }, {"name":"query","parameterTypes":["java.util.function.Consumer"] }, {"name":"query","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.QueryRequest"] }, {"name":"queryPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"queryPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.QueryRequest"] }, {"name":"restoreTableFromBackup","parameterTypes":["java.util.function.Consumer"] }, {"name":"restoreTableFromBackup","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.RestoreTableFromBackupRequest"] }, {"name":"restoreTableToPointInTime","parameterTypes":["java.util.function.Consumer"] }, {"name":"restoreTableToPointInTime","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.RestoreTableToPointInTimeRequest"] }, {"name":"scan","parameterTypes":["java.util.function.Consumer"] }, {"name":"scan","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ScanRequest"] }, {"name":"scanPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"scanPaginator","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.ScanRequest"] }, {"name":"serviceClientConfiguration","parameterTypes":[] }, {"name":"tagResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"tagResource","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.TagResourceRequest"] }, {"name":"transactGetItems","parameterTypes":["java.util.function.Consumer"] }, {"name":"transactGetItems","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.TransactGetItemsRequest"] }, {"name":"transactWriteItems","parameterTypes":["java.util.function.Consumer"] }, {"name":"transactWriteItems","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsRequest"] }, {"name":"untagResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"untagResource","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UntagResourceRequest"] }, {"name":"updateContinuousBackups","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateContinuousBackups","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateContinuousBackupsRequest"] }, {"name":"updateContributorInsights","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateContributorInsights","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateContributorInsightsRequest"] }, {"name":"updateGlobalTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateGlobalTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateGlobalTableRequest"] }, {"name":"updateGlobalTableSettings","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateGlobalTableSettings","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateGlobalTableSettingsRequest"] }, {"name":"updateItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateItem","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest"] }, {"name":"updateKinesisStreamingDestination","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateKinesisStreamingDestination","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateKinesisStreamingDestinationRequest"] }, {"name":"updateTable","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateTable","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateTableRequest"] }, {"name":"updateTableReplicaAutoScaling","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateTableReplicaAutoScaling","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateTableReplicaAutoScalingRequest"] }, {"name":"updateTimeToLive","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateTimeToLive","parameterTypes":["software.amazon.awssdk.services.dynamodb.model.UpdateTimeToLiveRequest"] }, {"name":"waiter","parameterTypes":[] }] +}, +{ + "name":"software.amazon.awssdk.services.secretsmanager.SecretsManagerClient", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"batchGetSecretValue","parameterTypes":["java.util.function.Consumer"] }, {"name":"batchGetSecretValue","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.BatchGetSecretValueRequest"] }, {"name":"batchGetSecretValuePaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"batchGetSecretValuePaginator","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.BatchGetSecretValueRequest"] }, {"name":"cancelRotateSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"cancelRotateSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.CancelRotateSecretRequest"] }, {"name":"createSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"createSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest"] }, {"name":"deleteResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteResourcePolicy","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.DeleteResourcePolicyRequest"] }, {"name":"deleteSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.DeleteSecretRequest"] }, {"name":"describeSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.DescribeSecretRequest"] }, {"name":"getRandomPassword","parameterTypes":[] }, {"name":"getRandomPassword","parameterTypes":["java.util.function.Consumer"] }, {"name":"getRandomPassword","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.GetRandomPasswordRequest"] }, {"name":"getResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"getResourcePolicy","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.GetResourcePolicyRequest"] }, {"name":"getSecretValue","parameterTypes":["java.util.function.Consumer"] }, {"name":"getSecretValue","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest"] }, {"name":"listSecretVersionIds","parameterTypes":["java.util.function.Consumer"] }, {"name":"listSecretVersionIds","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.ListSecretVersionIdsRequest"] }, {"name":"listSecretVersionIdsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listSecretVersionIdsPaginator","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.ListSecretVersionIdsRequest"] }, {"name":"listSecrets","parameterTypes":[] }, {"name":"listSecrets","parameterTypes":["java.util.function.Consumer"] }, {"name":"listSecrets","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.ListSecretsRequest"] }, {"name":"listSecretsPaginator","parameterTypes":[] }, {"name":"listSecretsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listSecretsPaginator","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.ListSecretsRequest"] }, {"name":"putResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"putResourcePolicy","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.PutResourcePolicyRequest"] }, {"name":"putSecretValue","parameterTypes":["java.util.function.Consumer"] }, {"name":"putSecretValue","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.PutSecretValueRequest"] }, {"name":"removeRegionsFromReplication","parameterTypes":["java.util.function.Consumer"] }, {"name":"removeRegionsFromReplication","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.RemoveRegionsFromReplicationRequest"] }, {"name":"replicateSecretToRegions","parameterTypes":["java.util.function.Consumer"] }, {"name":"replicateSecretToRegions","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.ReplicateSecretToRegionsRequest"] }, {"name":"restoreSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"restoreSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.RestoreSecretRequest"] }, {"name":"rotateSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"rotateSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.RotateSecretRequest"] }, {"name":"serviceClientConfiguration","parameterTypes":[] }, {"name":"stopReplicationToReplica","parameterTypes":["java.util.function.Consumer"] }, {"name":"stopReplicationToReplica","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.StopReplicationToReplicaRequest"] }, {"name":"tagResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"tagResource","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.TagResourceRequest"] }, {"name":"untagResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"untagResource","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.UntagResourceRequest"] }, {"name":"updateSecret","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateSecret","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.UpdateSecretRequest"] }, {"name":"updateSecretVersionStage","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateSecretVersionStage","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.UpdateSecretVersionStageRequest"] }, {"name":"validateResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"validateResourcePolicy","parameterTypes":["software.amazon.awssdk.services.secretsmanager.model.ValidateResourcePolicyRequest"] }] +}, +{ + "name":"software.amazon.awssdk.services.ssm.SsmClient", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"addTagsToResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"addTagsToResource","parameterTypes":["software.amazon.awssdk.services.ssm.model.AddTagsToResourceRequest"] }, {"name":"associateOpsItemRelatedItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"associateOpsItemRelatedItem","parameterTypes":["software.amazon.awssdk.services.ssm.model.AssociateOpsItemRelatedItemRequest"] }, {"name":"cancelCommand","parameterTypes":["java.util.function.Consumer"] }, {"name":"cancelCommand","parameterTypes":["software.amazon.awssdk.services.ssm.model.CancelCommandRequest"] }, {"name":"cancelMaintenanceWindowExecution","parameterTypes":["java.util.function.Consumer"] }, {"name":"cancelMaintenanceWindowExecution","parameterTypes":["software.amazon.awssdk.services.ssm.model.CancelMaintenanceWindowExecutionRequest"] }, {"name":"createActivation","parameterTypes":["java.util.function.Consumer"] }, {"name":"createActivation","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateActivationRequest"] }, {"name":"createAssociation","parameterTypes":["java.util.function.Consumer"] }, {"name":"createAssociation","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateAssociationRequest"] }, {"name":"createAssociationBatch","parameterTypes":["java.util.function.Consumer"] }, {"name":"createAssociationBatch","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateAssociationBatchRequest"] }, {"name":"createDocument","parameterTypes":["java.util.function.Consumer"] }, {"name":"createDocument","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateDocumentRequest"] }, {"name":"createMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"createMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateMaintenanceWindowRequest"] }, {"name":"createOpsItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"createOpsItem","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateOpsItemRequest"] }, {"name":"createOpsMetadata","parameterTypes":["java.util.function.Consumer"] }, {"name":"createOpsMetadata","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateOpsMetadataRequest"] }, {"name":"createPatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"createPatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreatePatchBaselineRequest"] }, {"name":"createResourceDataSync","parameterTypes":["java.util.function.Consumer"] }, {"name":"createResourceDataSync","parameterTypes":["software.amazon.awssdk.services.ssm.model.CreateResourceDataSyncRequest"] }, {"name":"deleteActivation","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteActivation","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteActivationRequest"] }, {"name":"deleteAssociation","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteAssociation","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteAssociationRequest"] }, {"name":"deleteDocument","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteDocument","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteDocumentRequest"] }, {"name":"deleteInventory","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteInventory","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteInventoryRequest"] }, {"name":"deleteMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteMaintenanceWindowRequest"] }, {"name":"deleteOpsItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteOpsItem","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteOpsItemRequest"] }, {"name":"deleteOpsMetadata","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteOpsMetadata","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteOpsMetadataRequest"] }, {"name":"deleteParameter","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteParameter","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteParameterRequest"] }, {"name":"deleteParameters","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteParameters","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteParametersRequest"] }, {"name":"deletePatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"deletePatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeletePatchBaselineRequest"] }, {"name":"deleteResourceDataSync","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteResourceDataSync","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteResourceDataSyncRequest"] }, {"name":"deleteResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"deleteResourcePolicy","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeleteResourcePolicyRequest"] }, {"name":"deregisterManagedInstance","parameterTypes":["java.util.function.Consumer"] }, {"name":"deregisterManagedInstance","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeregisterManagedInstanceRequest"] }, {"name":"deregisterPatchBaselineForPatchGroup","parameterTypes":["java.util.function.Consumer"] }, {"name":"deregisterPatchBaselineForPatchGroup","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeregisterPatchBaselineForPatchGroupRequest"] }, {"name":"deregisterTargetFromMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"deregisterTargetFromMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeregisterTargetFromMaintenanceWindowRequest"] }, {"name":"deregisterTaskFromMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"deregisterTaskFromMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.DeregisterTaskFromMaintenanceWindowRequest"] }, {"name":"describeActivations","parameterTypes":[] }, {"name":"describeActivations","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeActivations","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeActivationsRequest"] }, {"name":"describeActivationsPaginator","parameterTypes":[] }, {"name":"describeActivationsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeActivationsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeActivationsRequest"] }, {"name":"describeAssociation","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAssociation","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAssociationRequest"] }, {"name":"describeAssociationExecutionTargets","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAssociationExecutionTargets","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAssociationExecutionTargetsRequest"] }, {"name":"describeAssociationExecutionTargetsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAssociationExecutionTargetsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAssociationExecutionTargetsRequest"] }, {"name":"describeAssociationExecutions","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAssociationExecutions","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAssociationExecutionsRequest"] }, {"name":"describeAssociationExecutionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAssociationExecutionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAssociationExecutionsRequest"] }, {"name":"describeAutomationExecutions","parameterTypes":[] }, {"name":"describeAutomationExecutions","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAutomationExecutions","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAutomationExecutionsRequest"] }, {"name":"describeAutomationExecutionsPaginator","parameterTypes":[] }, {"name":"describeAutomationExecutionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAutomationExecutionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAutomationExecutionsRequest"] }, {"name":"describeAutomationStepExecutions","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAutomationStepExecutions","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAutomationStepExecutionsRequest"] }, {"name":"describeAutomationStepExecutionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAutomationStepExecutionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAutomationStepExecutionsRequest"] }, {"name":"describeAvailablePatches","parameterTypes":[] }, {"name":"describeAvailablePatches","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAvailablePatches","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAvailablePatchesRequest"] }, {"name":"describeAvailablePatchesPaginator","parameterTypes":[] }, {"name":"describeAvailablePatchesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeAvailablePatchesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeAvailablePatchesRequest"] }, {"name":"describeDocument","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeDocument","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeDocumentRequest"] }, {"name":"describeDocumentPermission","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeDocumentPermission","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeDocumentPermissionRequest"] }, {"name":"describeEffectiveInstanceAssociations","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeEffectiveInstanceAssociations","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeEffectiveInstanceAssociationsRequest"] }, {"name":"describeEffectiveInstanceAssociationsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeEffectiveInstanceAssociationsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeEffectiveInstanceAssociationsRequest"] }, {"name":"describeEffectivePatchesForPatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeEffectivePatchesForPatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeEffectivePatchesForPatchBaselineRequest"] }, {"name":"describeEffectivePatchesForPatchBaselinePaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeEffectivePatchesForPatchBaselinePaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeEffectivePatchesForPatchBaselineRequest"] }, {"name":"describeInstanceAssociationsStatus","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstanceAssociationsStatus","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstanceAssociationsStatusRequest"] }, {"name":"describeInstanceAssociationsStatusPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstanceAssociationsStatusPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstanceAssociationsStatusRequest"] }, {"name":"describeInstanceInformation","parameterTypes":[] }, {"name":"describeInstanceInformation","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstanceInformation","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstanceInformationRequest"] }, {"name":"describeInstanceInformationPaginator","parameterTypes":[] }, {"name":"describeInstanceInformationPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstanceInformationPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstanceInformationRequest"] }, {"name":"describeInstancePatchStates","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePatchStates","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePatchStatesRequest"] }, {"name":"describeInstancePatchStatesForPatchGroup","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePatchStatesForPatchGroup","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePatchStatesForPatchGroupRequest"] }, {"name":"describeInstancePatchStatesForPatchGroupPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePatchStatesForPatchGroupPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePatchStatesForPatchGroupRequest"] }, {"name":"describeInstancePatchStatesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePatchStatesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePatchStatesRequest"] }, {"name":"describeInstancePatches","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePatches","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePatchesRequest"] }, {"name":"describeInstancePatchesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePatchesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePatchesRequest"] }, {"name":"describeInstanceProperties","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstanceProperties","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePropertiesRequest"] }, {"name":"describeInstancePropertiesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInstancePropertiesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInstancePropertiesRequest"] }, {"name":"describeInventoryDeletions","parameterTypes":[] }, {"name":"describeInventoryDeletions","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInventoryDeletions","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInventoryDeletionsRequest"] }, {"name":"describeInventoryDeletionsPaginator","parameterTypes":[] }, {"name":"describeInventoryDeletionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeInventoryDeletionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeInventoryDeletionsRequest"] }, {"name":"describeMaintenanceWindowExecutionTaskInvocations","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowExecutionTaskInvocations","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowExecutionTaskInvocationsRequest"] }, {"name":"describeMaintenanceWindowExecutionTaskInvocationsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowExecutionTaskInvocationsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowExecutionTaskInvocationsRequest"] }, {"name":"describeMaintenanceWindowExecutionTasks","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowExecutionTasks","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowExecutionTasksRequest"] }, {"name":"describeMaintenanceWindowExecutionTasksPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowExecutionTasksPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowExecutionTasksRequest"] }, {"name":"describeMaintenanceWindowExecutions","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowExecutions","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowExecutionsRequest"] }, {"name":"describeMaintenanceWindowExecutionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowExecutionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowExecutionsRequest"] }, {"name":"describeMaintenanceWindowSchedule","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowSchedule","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowScheduleRequest"] }, {"name":"describeMaintenanceWindowSchedulePaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowSchedulePaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowScheduleRequest"] }, {"name":"describeMaintenanceWindowTargets","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowTargets","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowTargetsRequest"] }, {"name":"describeMaintenanceWindowTargetsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowTargetsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowTargetsRequest"] }, {"name":"describeMaintenanceWindowTasks","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowTasks","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowTasksRequest"] }, {"name":"describeMaintenanceWindowTasksPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowTasksPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowTasksRequest"] }, {"name":"describeMaintenanceWindows","parameterTypes":[] }, {"name":"describeMaintenanceWindows","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindows","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowsRequest"] }, {"name":"describeMaintenanceWindowsForTarget","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowsForTarget","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowsForTargetRequest"] }, {"name":"describeMaintenanceWindowsForTargetPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowsForTargetPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowsForTargetRequest"] }, {"name":"describeMaintenanceWindowsPaginator","parameterTypes":[] }, {"name":"describeMaintenanceWindowsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeMaintenanceWindowsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeMaintenanceWindowsRequest"] }, {"name":"describeOpsItems","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeOpsItems","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeOpsItemsRequest"] }, {"name":"describeOpsItemsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeOpsItemsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeOpsItemsRequest"] }, {"name":"describeParameters","parameterTypes":[] }, {"name":"describeParameters","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeParameters","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeParametersRequest"] }, {"name":"describeParametersPaginator","parameterTypes":[] }, {"name":"describeParametersPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeParametersPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeParametersRequest"] }, {"name":"describePatchBaselines","parameterTypes":[] }, {"name":"describePatchBaselines","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchBaselines","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchBaselinesRequest"] }, {"name":"describePatchBaselinesPaginator","parameterTypes":[] }, {"name":"describePatchBaselinesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchBaselinesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchBaselinesRequest"] }, {"name":"describePatchGroupState","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchGroupState","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchGroupStateRequest"] }, {"name":"describePatchGroups","parameterTypes":[] }, {"name":"describePatchGroups","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchGroups","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchGroupsRequest"] }, {"name":"describePatchGroupsPaginator","parameterTypes":[] }, {"name":"describePatchGroupsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchGroupsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchGroupsRequest"] }, {"name":"describePatchProperties","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchProperties","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchPropertiesRequest"] }, {"name":"describePatchPropertiesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describePatchPropertiesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribePatchPropertiesRequest"] }, {"name":"describeSessions","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeSessions","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeSessionsRequest"] }, {"name":"describeSessionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"describeSessionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.DescribeSessionsRequest"] }, {"name":"disassociateOpsItemRelatedItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"disassociateOpsItemRelatedItem","parameterTypes":["software.amazon.awssdk.services.ssm.model.DisassociateOpsItemRelatedItemRequest"] }, {"name":"getAutomationExecution","parameterTypes":["java.util.function.Consumer"] }, {"name":"getAutomationExecution","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetAutomationExecutionRequest"] }, {"name":"getCalendarState","parameterTypes":["java.util.function.Consumer"] }, {"name":"getCalendarState","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetCalendarStateRequest"] }, {"name":"getCommandInvocation","parameterTypes":["java.util.function.Consumer"] }, {"name":"getCommandInvocation","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetCommandInvocationRequest"] }, {"name":"getConnectionStatus","parameterTypes":["java.util.function.Consumer"] }, {"name":"getConnectionStatus","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetConnectionStatusRequest"] }, {"name":"getDefaultPatchBaseline","parameterTypes":[] }, {"name":"getDefaultPatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"getDefaultPatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetDefaultPatchBaselineRequest"] }, {"name":"getDeployablePatchSnapshotForInstance","parameterTypes":["java.util.function.Consumer"] }, {"name":"getDeployablePatchSnapshotForInstance","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetDeployablePatchSnapshotForInstanceRequest"] }, {"name":"getDocument","parameterTypes":["java.util.function.Consumer"] }, {"name":"getDocument","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetDocumentRequest"] }, {"name":"getInventory","parameterTypes":[] }, {"name":"getInventory","parameterTypes":["java.util.function.Consumer"] }, {"name":"getInventory","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetInventoryRequest"] }, {"name":"getInventoryPaginator","parameterTypes":[] }, {"name":"getInventoryPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"getInventoryPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetInventoryRequest"] }, {"name":"getInventorySchema","parameterTypes":[] }, {"name":"getInventorySchema","parameterTypes":["java.util.function.Consumer"] }, {"name":"getInventorySchema","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetInventorySchemaRequest"] }, {"name":"getInventorySchemaPaginator","parameterTypes":[] }, {"name":"getInventorySchemaPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"getInventorySchemaPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetInventorySchemaRequest"] }, {"name":"getMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"getMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetMaintenanceWindowRequest"] }, {"name":"getMaintenanceWindowExecution","parameterTypes":["java.util.function.Consumer"] }, {"name":"getMaintenanceWindowExecution","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetMaintenanceWindowExecutionRequest"] }, {"name":"getMaintenanceWindowExecutionTask","parameterTypes":["java.util.function.Consumer"] }, {"name":"getMaintenanceWindowExecutionTask","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetMaintenanceWindowExecutionTaskRequest"] }, {"name":"getMaintenanceWindowExecutionTaskInvocation","parameterTypes":["java.util.function.Consumer"] }, {"name":"getMaintenanceWindowExecutionTaskInvocation","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetMaintenanceWindowExecutionTaskInvocationRequest"] }, {"name":"getMaintenanceWindowTask","parameterTypes":["java.util.function.Consumer"] }, {"name":"getMaintenanceWindowTask","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetMaintenanceWindowTaskRequest"] }, {"name":"getOpsItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"getOpsItem","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetOpsItemRequest"] }, {"name":"getOpsMetadata","parameterTypes":["java.util.function.Consumer"] }, {"name":"getOpsMetadata","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetOpsMetadataRequest"] }, {"name":"getOpsSummary","parameterTypes":["java.util.function.Consumer"] }, {"name":"getOpsSummary","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetOpsSummaryRequest"] }, {"name":"getOpsSummaryPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"getOpsSummaryPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetOpsSummaryRequest"] }, {"name":"getParameter","parameterTypes":["java.util.function.Consumer"] }, {"name":"getParameter","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetParameterRequest"] }, {"name":"getParameterHistory","parameterTypes":["java.util.function.Consumer"] }, {"name":"getParameterHistory","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetParameterHistoryRequest"] }, {"name":"getParameterHistoryPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"getParameterHistoryPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetParameterHistoryRequest"] }, {"name":"getParameters","parameterTypes":["java.util.function.Consumer"] }, {"name":"getParameters","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetParametersRequest"] }, {"name":"getParametersByPath","parameterTypes":["java.util.function.Consumer"] }, {"name":"getParametersByPath","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest"] }, {"name":"getParametersByPathPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"getParametersByPathPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest"] }, {"name":"getPatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"getPatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetPatchBaselineRequest"] }, {"name":"getPatchBaselineForPatchGroup","parameterTypes":["java.util.function.Consumer"] }, {"name":"getPatchBaselineForPatchGroup","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetPatchBaselineForPatchGroupRequest"] }, {"name":"getResourcePolicies","parameterTypes":["java.util.function.Consumer"] }, {"name":"getResourcePolicies","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetResourcePoliciesRequest"] }, {"name":"getResourcePoliciesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"getResourcePoliciesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetResourcePoliciesRequest"] }, {"name":"getServiceSetting","parameterTypes":["java.util.function.Consumer"] }, {"name":"getServiceSetting","parameterTypes":["software.amazon.awssdk.services.ssm.model.GetServiceSettingRequest"] }, {"name":"labelParameterVersion","parameterTypes":["java.util.function.Consumer"] }, {"name":"labelParameterVersion","parameterTypes":["software.amazon.awssdk.services.ssm.model.LabelParameterVersionRequest"] }, {"name":"listAssociationVersions","parameterTypes":["java.util.function.Consumer"] }, {"name":"listAssociationVersions","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListAssociationVersionsRequest"] }, {"name":"listAssociationVersionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listAssociationVersionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListAssociationVersionsRequest"] }, {"name":"listAssociations","parameterTypes":[] }, {"name":"listAssociations","parameterTypes":["java.util.function.Consumer"] }, {"name":"listAssociations","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListAssociationsRequest"] }, {"name":"listAssociationsPaginator","parameterTypes":[] }, {"name":"listAssociationsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listAssociationsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListAssociationsRequest"] }, {"name":"listCommandInvocations","parameterTypes":[] }, {"name":"listCommandInvocations","parameterTypes":["java.util.function.Consumer"] }, {"name":"listCommandInvocations","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListCommandInvocationsRequest"] }, {"name":"listCommandInvocationsPaginator","parameterTypes":[] }, {"name":"listCommandInvocationsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listCommandInvocationsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListCommandInvocationsRequest"] }, {"name":"listCommands","parameterTypes":[] }, {"name":"listCommands","parameterTypes":["java.util.function.Consumer"] }, {"name":"listCommands","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListCommandsRequest"] }, {"name":"listCommandsPaginator","parameterTypes":[] }, {"name":"listCommandsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listCommandsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListCommandsRequest"] }, {"name":"listComplianceItems","parameterTypes":["java.util.function.Consumer"] }, {"name":"listComplianceItems","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListComplianceItemsRequest"] }, {"name":"listComplianceItemsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listComplianceItemsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListComplianceItemsRequest"] }, {"name":"listComplianceSummaries","parameterTypes":[] }, {"name":"listComplianceSummaries","parameterTypes":["java.util.function.Consumer"] }, {"name":"listComplianceSummaries","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListComplianceSummariesRequest"] }, {"name":"listComplianceSummariesPaginator","parameterTypes":[] }, {"name":"listComplianceSummariesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listComplianceSummariesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListComplianceSummariesRequest"] }, {"name":"listDocumentMetadataHistory","parameterTypes":["java.util.function.Consumer"] }, {"name":"listDocumentMetadataHistory","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListDocumentMetadataHistoryRequest"] }, {"name":"listDocumentVersions","parameterTypes":["java.util.function.Consumer"] }, {"name":"listDocumentVersions","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListDocumentVersionsRequest"] }, {"name":"listDocumentVersionsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listDocumentVersionsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListDocumentVersionsRequest"] }, {"name":"listDocuments","parameterTypes":[] }, {"name":"listDocuments","parameterTypes":["java.util.function.Consumer"] }, {"name":"listDocuments","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListDocumentsRequest"] }, {"name":"listDocumentsPaginator","parameterTypes":[] }, {"name":"listDocumentsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listDocumentsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListDocumentsRequest"] }, {"name":"listInventoryEntries","parameterTypes":["java.util.function.Consumer"] }, {"name":"listInventoryEntries","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListInventoryEntriesRequest"] }, {"name":"listOpsItemEvents","parameterTypes":["java.util.function.Consumer"] }, {"name":"listOpsItemEvents","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListOpsItemEventsRequest"] }, {"name":"listOpsItemEventsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listOpsItemEventsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListOpsItemEventsRequest"] }, {"name":"listOpsItemRelatedItems","parameterTypes":["java.util.function.Consumer"] }, {"name":"listOpsItemRelatedItems","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListOpsItemRelatedItemsRequest"] }, {"name":"listOpsItemRelatedItemsPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listOpsItemRelatedItemsPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListOpsItemRelatedItemsRequest"] }, {"name":"listOpsMetadata","parameterTypes":["java.util.function.Consumer"] }, {"name":"listOpsMetadata","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListOpsMetadataRequest"] }, {"name":"listOpsMetadataPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listOpsMetadataPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListOpsMetadataRequest"] }, {"name":"listResourceComplianceSummaries","parameterTypes":[] }, {"name":"listResourceComplianceSummaries","parameterTypes":["java.util.function.Consumer"] }, {"name":"listResourceComplianceSummaries","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListResourceComplianceSummariesRequest"] }, {"name":"listResourceComplianceSummariesPaginator","parameterTypes":[] }, {"name":"listResourceComplianceSummariesPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listResourceComplianceSummariesPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListResourceComplianceSummariesRequest"] }, {"name":"listResourceDataSync","parameterTypes":[] }, {"name":"listResourceDataSync","parameterTypes":["java.util.function.Consumer"] }, {"name":"listResourceDataSync","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListResourceDataSyncRequest"] }, {"name":"listResourceDataSyncPaginator","parameterTypes":[] }, {"name":"listResourceDataSyncPaginator","parameterTypes":["java.util.function.Consumer"] }, {"name":"listResourceDataSyncPaginator","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListResourceDataSyncRequest"] }, {"name":"listTagsForResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"listTagsForResource","parameterTypes":["software.amazon.awssdk.services.ssm.model.ListTagsForResourceRequest"] }, {"name":"modifyDocumentPermission","parameterTypes":["java.util.function.Consumer"] }, {"name":"modifyDocumentPermission","parameterTypes":["software.amazon.awssdk.services.ssm.model.ModifyDocumentPermissionRequest"] }, {"name":"putComplianceItems","parameterTypes":["java.util.function.Consumer"] }, {"name":"putComplianceItems","parameterTypes":["software.amazon.awssdk.services.ssm.model.PutComplianceItemsRequest"] }, {"name":"putInventory","parameterTypes":["java.util.function.Consumer"] }, {"name":"putInventory","parameterTypes":["software.amazon.awssdk.services.ssm.model.PutInventoryRequest"] }, {"name":"putParameter","parameterTypes":["java.util.function.Consumer"] }, {"name":"putParameter","parameterTypes":["software.amazon.awssdk.services.ssm.model.PutParameterRequest"] }, {"name":"putResourcePolicy","parameterTypes":["java.util.function.Consumer"] }, {"name":"putResourcePolicy","parameterTypes":["software.amazon.awssdk.services.ssm.model.PutResourcePolicyRequest"] }, {"name":"registerDefaultPatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"registerDefaultPatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.RegisterDefaultPatchBaselineRequest"] }, {"name":"registerPatchBaselineForPatchGroup","parameterTypes":["java.util.function.Consumer"] }, {"name":"registerPatchBaselineForPatchGroup","parameterTypes":["software.amazon.awssdk.services.ssm.model.RegisterPatchBaselineForPatchGroupRequest"] }, {"name":"registerTargetWithMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"registerTargetWithMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.RegisterTargetWithMaintenanceWindowRequest"] }, {"name":"registerTaskWithMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"registerTaskWithMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.RegisterTaskWithMaintenanceWindowRequest"] }, {"name":"removeTagsFromResource","parameterTypes":["java.util.function.Consumer"] }, {"name":"removeTagsFromResource","parameterTypes":["software.amazon.awssdk.services.ssm.model.RemoveTagsFromResourceRequest"] }, {"name":"resetServiceSetting","parameterTypes":["java.util.function.Consumer"] }, {"name":"resetServiceSetting","parameterTypes":["software.amazon.awssdk.services.ssm.model.ResetServiceSettingRequest"] }, {"name":"resumeSession","parameterTypes":["java.util.function.Consumer"] }, {"name":"resumeSession","parameterTypes":["software.amazon.awssdk.services.ssm.model.ResumeSessionRequest"] }, {"name":"sendAutomationSignal","parameterTypes":["java.util.function.Consumer"] }, {"name":"sendAutomationSignal","parameterTypes":["software.amazon.awssdk.services.ssm.model.SendAutomationSignalRequest"] }, {"name":"sendCommand","parameterTypes":["java.util.function.Consumer"] }, {"name":"sendCommand","parameterTypes":["software.amazon.awssdk.services.ssm.model.SendCommandRequest"] }, {"name":"serviceClientConfiguration","parameterTypes":[] }, {"name":"startAssociationsOnce","parameterTypes":["java.util.function.Consumer"] }, {"name":"startAssociationsOnce","parameterTypes":["software.amazon.awssdk.services.ssm.model.StartAssociationsOnceRequest"] }, {"name":"startAutomationExecution","parameterTypes":["java.util.function.Consumer"] }, {"name":"startAutomationExecution","parameterTypes":["software.amazon.awssdk.services.ssm.model.StartAutomationExecutionRequest"] }, {"name":"startChangeRequestExecution","parameterTypes":["java.util.function.Consumer"] }, {"name":"startChangeRequestExecution","parameterTypes":["software.amazon.awssdk.services.ssm.model.StartChangeRequestExecutionRequest"] }, {"name":"startSession","parameterTypes":["java.util.function.Consumer"] }, {"name":"startSession","parameterTypes":["software.amazon.awssdk.services.ssm.model.StartSessionRequest"] }, {"name":"stopAutomationExecution","parameterTypes":["java.util.function.Consumer"] }, {"name":"stopAutomationExecution","parameterTypes":["software.amazon.awssdk.services.ssm.model.StopAutomationExecutionRequest"] }, {"name":"terminateSession","parameterTypes":["java.util.function.Consumer"] }, {"name":"terminateSession","parameterTypes":["software.amazon.awssdk.services.ssm.model.TerminateSessionRequest"] }, {"name":"unlabelParameterVersion","parameterTypes":["java.util.function.Consumer"] }, {"name":"unlabelParameterVersion","parameterTypes":["software.amazon.awssdk.services.ssm.model.UnlabelParameterVersionRequest"] }, {"name":"updateAssociation","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateAssociation","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateAssociationRequest"] }, {"name":"updateAssociationStatus","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateAssociationStatus","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateAssociationStatusRequest"] }, {"name":"updateDocument","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateDocument","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateDocumentRequest"] }, {"name":"updateDocumentDefaultVersion","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateDocumentDefaultVersion","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateDocumentDefaultVersionRequest"] }, {"name":"updateDocumentMetadata","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateDocumentMetadata","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateDocumentMetadataRequest"] }, {"name":"updateMaintenanceWindow","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateMaintenanceWindow","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateMaintenanceWindowRequest"] }, {"name":"updateMaintenanceWindowTarget","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateMaintenanceWindowTarget","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateMaintenanceWindowTargetRequest"] }, {"name":"updateMaintenanceWindowTask","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateMaintenanceWindowTask","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateMaintenanceWindowTaskRequest"] }, {"name":"updateManagedInstanceRole","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateManagedInstanceRole","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateManagedInstanceRoleRequest"] }, {"name":"updateOpsItem","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateOpsItem","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateOpsItemRequest"] }, {"name":"updateOpsMetadata","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateOpsMetadata","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateOpsMetadataRequest"] }, {"name":"updatePatchBaseline","parameterTypes":["java.util.function.Consumer"] }, {"name":"updatePatchBaseline","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdatePatchBaselineRequest"] }, {"name":"updateResourceDataSync","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateResourceDataSync","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateResourceDataSyncRequest"] }, {"name":"updateServiceSetting","parameterTypes":["java.util.function.Consumer"] }, {"name":"updateServiceSetting","parameterTypes":["software.amazon.awssdk.services.ssm.model.UpdateServiceSettingRequest"] }, {"name":"waiter","parameterTypes":[] }] +}, +{ + "name":"software.amazon.awssdk.utils.SdkAutoCloseable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"close","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.transform.Base64Transformer", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.transform.BasicTransformer", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.transform.JsonTransformer", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.parameters.transform.ObjectToDeserialize", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setBar","parameterTypes":["int"] }, {"name":"setBaz","parameterTypes":["long"] }, {"name":"setFoo","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"sun.reflect.ReflectionFactory", + "methods":[{"name":"getReflectionFactory","parameterTypes":[] }, {"name":"newConstructorForSerialization","parameterTypes":["java.lang.Class","java.lang.reflect.Constructor"] }] +} +] diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SSMProviderTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SSMProviderTest.java deleted file mode 100644 index 761979e00..000000000 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SSMProviderTest.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. - * 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 - * http://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. - * - */ -package software.amazon.lambda.powertools.parameters; - -import org.assertj.core.data.MapEntry; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.ssm.model.*; -import software.amazon.lambda.powertools.parameters.cache.CacheManager; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; -import static org.mockito.MockitoAnnotations.openMocks; - -public class SSMProviderTest { - - @Mock - SsmClient client; - - @Captor - ArgumentCaptor<GetParameterRequest> paramCaptor; - - @Captor - ArgumentCaptor<GetParametersByPathRequest> paramByPathCaptor; - - CacheManager cacheManager; - - SSMProvider provider; - - @BeforeEach - public void init() { - openMocks(this); - cacheManager = new CacheManager(); - provider = new SSMProvider(cacheManager, client); - } - - @Test - public void getValue() { - String key = "Key1"; - String expectedValue = "Value1"; - initMock(expectedValue); - - String value = provider.getValue(key); - - assertThat(value).isEqualTo(expectedValue); - assertThat(paramCaptor.getValue().name()).isEqualTo(key); - assertThat(paramCaptor.getValue().withDecryption()).isFalse(); - } - - @Test - public void getValueDecrypted() { - String key = "Key2"; - String expectedValue = "Value2"; - initMock(expectedValue); - - String value = provider.withDecryption().getValue(key); - - assertThat(value).isEqualTo(expectedValue); - assertThat(paramCaptor.getValue().name()).isEqualTo(key); - assertThat(paramCaptor.getValue().withDecryption()).isTrue(); - } - - @Test - public void getMultiple() { - List<Parameter> parameters = new ArrayList<>(); - parameters.add(Parameter.builder().name("/prod/app1/key1").value("foo1").build()); - parameters.add(Parameter.builder().name("/prod/app1/key2").value("foo2").build()); - parameters.add(Parameter.builder().name("/prod/app1/key3").value("foo3").build()); - GetParametersByPathResponse response = GetParametersByPathResponse.builder().parameters(parameters).build(); - when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response); - - Map<String, String> params = provider.getMultiple("/prod/app1"); - assertThat(params).contains( - MapEntry.entry("key1", "foo1"), - MapEntry.entry("key2", "foo2"), - MapEntry.entry("key3", "foo3")); - assertThat(provider.get("/prod/app1/key1")).isEqualTo("foo1"); - assertThat(provider.get("/prod/app1/key2")).isEqualTo("foo2"); - assertThat(provider.get("/prod/app1/key3")).isEqualTo("foo3"); - - assertThat(paramByPathCaptor.getValue().path()).isEqualTo("/prod/app1"); - assertThat(paramByPathCaptor.getValue().withDecryption()).isFalse(); - assertThat(paramByPathCaptor.getValue().recursive()).isFalse(); - } - - @Test - public void getMultiple_cached_shouldNotCallSSM() { - List<Parameter> parameters = new ArrayList<>(); - parameters.add(Parameter.builder().name("/prod/app1/key1").value("foo1").build()); - parameters.add(Parameter.builder().name("/prod/app1/key2").value("foo2").build()); - parameters.add(Parameter.builder().name("/prod/app1/key3").value("foo3").build()); - GetParametersByPathResponse response = GetParametersByPathResponse.builder().parameters(parameters).build(); - when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response); - - provider.getMultiple("/prod/app1"); - - // should get the following from cache - provider.getMultiple("/prod/app1"); - provider.get("/prod/app1/key1"); - provider.get("/prod/app1/key2"); - provider.get("/prod/app1/key3"); - - verify(client, times(1)).getParametersByPath(any(GetParametersByPathRequest.class)); - - } - - @Test - public void getMultipleWithNextToken() { - List<Parameter> parameters1 = new ArrayList<>(); - parameters1.add(Parameter.builder().name("/prod/app1/key1").value("foo1").build()); - parameters1.add(Parameter.builder().name("/prod/app1/key2").value("foo2").build()); - GetParametersByPathResponse response1 = GetParametersByPathResponse.builder().parameters(parameters1).nextToken("123abc").build(); - - List<Parameter> parameters2 = new ArrayList<>(); - parameters2.add(Parameter.builder().name("/prod/app1/key3").value("foo3").build()); - GetParametersByPathResponse response2 = GetParametersByPathResponse.builder().parameters(parameters2).build(); - - when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response1, response2); - - Map<String, String> params = provider.getMultiple("/prod/app1"); - - assertThat(params).contains( - MapEntry.entry("key1", "foo1"), - MapEntry.entry("key2", "foo2"), - MapEntry.entry("key3", "foo3")); - - List<GetParametersByPathRequest> requestParams = paramByPathCaptor.getAllValues(); - GetParametersByPathRequest request1 = requestParams.get(0); - GetParametersByPathRequest request2 = requestParams.get(1); - - assertThat(asList(request1, request2)).allSatisfy(req -> { - assertThat(req.path()).isEqualTo("/prod/app1"); - assertThat(req.withDecryption()).isFalse(); - assertThat(req.recursive()).isFalse(); - }); - - assertThat(request1.nextToken()).isNull(); - assertThat(request2.nextToken()).isEqualTo("123abc"); - } - - private void initMock(String expectedValue) { - Parameter parameter = Parameter.builder().value(expectedValue).build(); - GetParameterResponse result = GetParameterResponse.builder().parameter(parameter).build(); - when(client.getParameter(paramCaptor.capture())).thenReturn(result); - } - -} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/cache/CacheManagerTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/cache/CacheManagerTest.java deleted file mode 100644 index 2464b4278..000000000 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/cache/CacheManagerTest.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. - * 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 - * http://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. - * - */ -package software.amazon.lambda.powertools.parameters.cache; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.time.Clock; -import java.util.Optional; - -import static java.time.Clock.offset; -import static java.time.Duration.of; -import static java.time.temporal.ChronoUnit.SECONDS; -import static org.assertj.core.api.Assertions.assertThat; - -public class CacheManagerTest { - - CacheManager manager; - - Clock clock; - - @BeforeEach - public void setup() { - clock = Clock.systemDefaultZone(); - manager = new CacheManager(); - } - - @Test - public void getIfNotExpired_notExpired_shouldReturnValue() { - manager.putInCache("key", "value"); - - Optional<String> value = manager.getIfNotExpired("key", clock.instant()); - - assertThat(value).isPresent().contains("value"); - } - - @Test - public void getIfNotExpired_expired_shouldReturnNothing() { - manager.putInCache("key", "value"); - - Optional<String> value = manager.getIfNotExpired("key", offset(clock, of(6, SECONDS)).instant()); - - assertThat(value).isNotPresent(); - } - - @Test - public void getIfNotExpired_withCustomExpirationTime_notExpired_shouldReturnValue() { - manager.setExpirationTime(of(42, SECONDS)); - manager.putInCache("key", "value"); - - Optional<String> value = manager.getIfNotExpired("key", offset(clock, of(40, SECONDS)).instant()); - - assertThat(value).isPresent().contains("value"); - } - - @Test - public void getIfNotExpired_withCustomDefaultExpirationTime_notExpired_shouldReturnValue() { - manager.setDefaultExpirationTime(of(42, SECONDS)); - manager.putInCache("key", "value"); - - - Optional<String> value = manager.getIfNotExpired("key", offset(clock, of(40, SECONDS)).instant()); - - assertThat(value).isPresent().contains("value"); - } - - @Test - public void getIfNotExpired_customDefaultExpirationTime_customExpirationTime_shouldUseExpirationTime() { - manager.setDefaultExpirationTime(of(42, SECONDS)); - manager.setExpirationTime(of(2, SECONDS)); - manager.putInCache("key", "value"); - - Optional<String> value = manager.getIfNotExpired("key", offset(clock, of(40, SECONDS)).instant()); - - assertThat(value).isNotPresent(); - } - - @Test - public void getIfNotExpired_resetExpirationTime_shouldUseDefaultExpirationTime() { - manager.setDefaultExpirationTime(of(42, SECONDS)); - manager.setExpirationTime(of(2, SECONDS)); - manager.putInCache("key", "value"); - manager.resetExpirationTime(); - manager.putInCache("key2", "value2"); - - Optional<String> value = manager.getIfNotExpired("key", offset(clock, of(40, SECONDS)).instant()); - Optional<String> value2 = manager.getIfNotExpired("key2", offset(clock, of(40, SECONDS)).instant()); - - assertThat(value).isNotPresent(); - assertThat(value2).isPresent().contains("value2"); - } - -} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java deleted file mode 100644 index b58ad7b3d..000000000 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java +++ /dev/null @@ -1,25 +0,0 @@ -package software.amazon.lambda.powertools.parameters.internal; - -public class AnotherObject { - - public AnotherObject() {} - - private String another; - private int object; - - public String getAnother() { - return another; - } - - public void setAnother(String another) { - this.another = another; - } - - public int getObject() { - return object; - } - - public void setObject(int object) { - this.object = object; - } -} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java deleted file mode 100644 index 2edbc8b24..000000000 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package software.amazon.lambda.powertools.parameters.internal; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import software.amazon.lambda.powertools.parameters.Param; -import software.amazon.lambda.powertools.parameters.ParamManager; -import software.amazon.lambda.powertools.parameters.SSMProvider; -import software.amazon.lambda.powertools.parameters.exception.TransformationException; -import software.amazon.lambda.powertools.parameters.transform.Base64Transformer; -import software.amazon.lambda.powertools.parameters.transform.JsonTransformer; -import software.amazon.lambda.powertools.parameters.transform.ObjectToDeserialize; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.*; -import static org.mockito.MockitoAnnotations.openMocks; - -public class LambdaParametersAspectTest { - - @Mock - private SSMProvider defaultProvider; - - @Param(key = "/default") - private String defaultValue; - - @Param(key = "/simple", provider = CustomProvider.class) - private String param; - - @Param(key = "/base64", provider = CustomProvider.class, transformer = Base64Transformer.class) - private String basicTransform; - - @Param(key = "/json", provider = CustomProvider.class, transformer = JsonTransformer.class) - private ObjectToDeserialize complexTransform; - - @Param(key = "/json", provider = CustomProvider.class, transformer = JsonTransformer.class) - private AnotherObject wrongTransform; - - @BeforeEach - public void init() { - openMocks(this); - } - - @Test - public void testDefault_ShouldUseSSMProvider() { - try (MockedStatic<ParamManager> mocked = mockStatic(ParamManager.class)) { - mocked.when(() -> ParamManager.getProvider(SSMProvider.class)).thenReturn(defaultProvider); - when(defaultProvider.get("/default")).thenReturn("value"); - - assertThat(defaultValue).isEqualTo("value"); - mocked.verify(() -> ParamManager.getProvider(SSMProvider.class), times(1)); - verify(defaultProvider, times(1)).get("/default"); - - mocked.reset(); - } - } - - @Test - public void testSimple() { - assertThat(param).isEqualTo("value"); - } - - @Test - public void testWithBasicTransform() { - assertThat(basicTransform).isEqualTo("value"); - } - - @Test - public void testWithComplexTransform() { - assertThat(complexTransform) - .isInstanceOf(ObjectToDeserialize.class) - .matches( - o -> o.getFoo().equals("Foo") && - o.getBar() == 42 && - o.getBaz() == 123456789); - } - - @Test - public void testWithComplexTransformWrongTargetClass_ShouldThrowException() { - assertThatExceptionOfType(TransformationException.class) - .isThrownBy(() -> {AnotherObject obj = wrongTransform; }); - } - -} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java deleted file mode 100644 index 0fcfa8c51..000000000 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. - * 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 - * http://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. - * - */ -package software.amazon.lambda.powertools.parameters.transform; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Base64; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; -import static software.amazon.lambda.powertools.parameters.transform.Transformer.json; - -public class TransformationManagerTest { - - TransformationManager manager; - - @BeforeEach - public void setup() { - manager = new TransformationManager(); - } - - @Test - public void setTransformer_shouldTransform() { - manager.setTransformer(json); - - assertThat(manager.shouldTransform()).isTrue(); - } - - @Test - public void notSetTransformer_shouldNotTransform() { - assertThat(manager.shouldTransform()).isFalse(); - } - - @Test - public void performBasicTransformation_noTransformer_shouldThrowException() { - assertThatIllegalStateException() - .isThrownBy(() -> manager.performBasicTransformation("value")); - } - - @Test - public void performBasicTransformation_notBasicTransformer_shouldThrowException() { - manager.setTransformer(json); - - assertThatIllegalStateException() - .isThrownBy(() -> manager.performBasicTransformation("value")); - } - - @Test - public void performBasicTransformation_shouldPerformTransformation() { - manager.setTransformer(base64); - - String expectedValue = "bar"; - String value = manager.performBasicTransformation(Base64.getEncoder().encodeToString(expectedValue.getBytes())); - - assertThat(value).isEqualTo(expectedValue); - } - - @Test - public void performComplexTransformation_noTransformer_shouldThrowException() { - assertThatIllegalStateException() - .isThrownBy(() -> manager.performComplexTransformation("value", ObjectToDeserialize.class)); - } - - @Test - public void performComplexTransformation_shouldPerformTransformation() { - manager.setTransformer(json); - - ObjectToDeserialize object = manager.performComplexTransformation("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}", ObjectToDeserialize.class); - - assertThat(object).isNotNull(); - } -} diff --git a/powertools-serialization/pom.xml b/powertools-serialization/pom.xml new file mode 100644 index 000000000..81603cd4f --- /dev/null +++ b/powertools-serialization/pom.xml @@ -0,0 +1,155 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>powertools-parent</artifactId> + <groupId>software.amazon.lambda</groupId> + <version>2.9.0</version> + </parent> + + <artifactId>powertools-serialization</artifactId> + <packaging>jar</packaging> + + <name>Powertools for AWS Lambda (Java) - Serialization Utilities</name> + <description>Utilities for JSON serialization used across the project.</description> + + <dependencies> + <dependency> + <groupId>io.burt</groupId> + <artifactId>jmespath-jackson</artifactId> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-events</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.datatype</groupId> + <artifactId>jackson-datatype-joda</artifactId> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-tests</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <resources> + <!-- GraalVM Native Image Configuration Files --> + <resource> + <directory>src/main/resources</directory> + </resource> + </resources> + <plugins> + <plugin> + <groupId>dev.aspectj</groupId> + <artifactId>aspectj-maven-plugin</artifactId> + <version>${aspectj-maven-plugin.version}</version> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>generate-graalvm-files</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization,experimental-class-define-support</argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-serialization</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + +</project> diff --git a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializationException.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializationException.java new file mode 100644 index 000000000..ae97232b0 --- /dev/null +++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializationException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.utilities; + +public class EventDeserializationException extends RuntimeException { + private static final long serialVersionUID = -5003158148870110442L; + + public EventDeserializationException(String msg, Exception e) { + super(msg, e); + } + + public EventDeserializationException(String msg) { + super(msg); + } +} diff --git a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializer.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializer.java new file mode 100644 index 000000000..1b3f158be --- /dev/null +++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializer.java @@ -0,0 +1,272 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.utilities; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static software.amazon.lambda.powertools.utilities.jmespath.Base64Function.decode; +import static software.amazon.lambda.powertools.utilities.jmespath.Base64GZipFunction.decompress; + +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.ActiveMQEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent; +import com.amazonaws.services.lambda.runtime.events.KafkaEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectReader; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class that can be used to extract the meaningful part of an event and deserialize it into a Java object.<br/> + * For example, extract the body of an API Gateway event, or messages from an SQS event. + */ +public class EventDeserializer { + + private static final Logger LOG = LoggerFactory.getLogger(EventDeserializer.class); + + /** + * Extract the meaningful part of a Lambda Event object. Main events are built-in: + * <ul> + * <li>{@link APIGatewayProxyRequestEvent} -> body</li> + * <li>{@link APIGatewayV2HTTPEvent} -> body</li> + * <li>{@link SNSEvent} -> Records[0].Sns.Message</li> + * <li>{@link SQSEvent} -> Records[*].body <i>(list)</i></li> + * <li>{@link ScheduledEvent} -> detail</li> + * <li>{@link ApplicationLoadBalancerRequestEvent} -> body</li> + * <li>{@link CloudWatchLogsEvent} -> powertools_base64_gzip(data)</li> + * <li>{@link CloudFormationCustomResourceEvent} -> resourceProperties</li> + * <li>{@link KinesisEvent} -> Records[*].kinesis.powertools_base64(data) <i>(list)</i></li> + * <li>{@link KinesisFirehoseEvent} -> Records[*].powertools_base64(data) <i>(list)</i></li> + * <li>{@link KafkaEvent} -> records[*].values[*].powertools_base64(value) <i>(list)</i></li> + * <li>{@link ActiveMQEvent} -> messages[*].powertools_base64(data) <i>(list)</i></li> + * <li>{@link RabbitMQEvent} -> rmqMessagesByQueue[*].values[*].powertools_base64(data) <i>(list)</i></li> + * <li>{@link KinesisAnalyticsFirehoseInputPreprocessingEvent} -> Records[*].kinesis.powertools_base64(data) <i>(list)</i></li> + * <li>{@link KinesisAnalyticsStreamsInputPreprocessingEvent} > Records[*].kinesis.powertools_base64(data) <i>(list)</i></li> + * <li>{@link String}</li> + * <li>{@link Map}</li> + * </ul> + * To be used in conjunction with {@link EventPart#as(Class)} or {@link EventPart#asListOf(Class)} + * for the deserialization. + * + * @param object the event of your Lambda function handler method + * @return the part of the event which is meaningful (ex: body of the API Gateway).<br/> + */ + public static EventPart extractDataFrom(Object object) { + if (object instanceof String) { + return new EventPart((String) object); + } else if (object instanceof Map) { + return new EventPart((Map<String, Object>) object); + } else if (object instanceof APIGatewayProxyRequestEvent) { + APIGatewayProxyRequestEvent event = (APIGatewayProxyRequestEvent) object; + return new EventPart(event.getBody()); + } else if (object instanceof APIGatewayV2HTTPEvent) { + APIGatewayV2HTTPEvent event = (APIGatewayV2HTTPEvent) object; + return new EventPart(event.getBody()); + } else if (object instanceof SNSEvent) { + SNSEvent event = (SNSEvent) object; + return new EventPart(event.getRecords().get(0).getSNS().getMessage()); + } else if (object instanceof SQSEvent) { + SQSEvent event = (SQSEvent) object; + return new EventPart(event.getRecords().stream() + .map(SQSEvent.SQSMessage::getBody) + .collect(Collectors.toList())); + } else if (object instanceof SQSEvent.SQSMessage) { + return new EventPart(((SQSEvent.SQSMessage) object).getBody()); + } else if (object instanceof ScheduledEvent) { + ScheduledEvent event = (ScheduledEvent) object; + return new EventPart(event.getDetail()); + } else if (object instanceof ApplicationLoadBalancerRequestEvent) { + ApplicationLoadBalancerRequestEvent event = (ApplicationLoadBalancerRequestEvent) object; + return new EventPart(event.getBody()); + } else if (object instanceof CloudWatchLogsEvent) { + CloudWatchLogsEvent event = (CloudWatchLogsEvent) object; + return new EventPart(decompress(decode(event.getAwsLogs().getData().getBytes(UTF_8)))); + } else if (object instanceof CloudFormationCustomResourceEvent) { + CloudFormationCustomResourceEvent event = (CloudFormationCustomResourceEvent) object; + return new EventPart(event.getResourceProperties()); + } else if (object instanceof KinesisEvent) { + KinesisEvent event = (KinesisEvent) object; + return new EventPart(event.getRecords().stream() + .map(r -> decode(r.getKinesis().getData())) + .collect(Collectors.toList())); + } else if (object instanceof KinesisEvent.KinesisEventRecord) { + return new EventPart(decode(((KinesisEvent.KinesisEventRecord)object).getKinesis().getData())); + } else if (object instanceof KinesisFirehoseEvent) { + KinesisFirehoseEvent event = (KinesisFirehoseEvent) object; + return new EventPart(event.getRecords().stream() + .map(r -> decode(r.getData())) + .collect(Collectors.toList())); + } else if (object instanceof KafkaEvent) { + KafkaEvent event = (KafkaEvent) object; + return new EventPart(event.getRecords().values().stream() + .flatMap(List::stream) + .map(r -> decode(r.getValue())) + .collect(Collectors.toList())); + } else if (object instanceof ActiveMQEvent) { + ActiveMQEvent event = (ActiveMQEvent) object; + return new EventPart(event.getMessages().stream() + .map(m -> decode(m.getData())) + .collect(Collectors.toList())); + } else if (object instanceof RabbitMQEvent) { + RabbitMQEvent event = (RabbitMQEvent) object; + return new EventPart(event.getRmqMessagesByQueue().values().stream() + .flatMap(List::stream) + .map(r -> decode(r.getData())) + .collect(Collectors.toList())); + } else if (object instanceof KinesisAnalyticsFirehoseInputPreprocessingEvent) { + KinesisAnalyticsFirehoseInputPreprocessingEvent event = + (KinesisAnalyticsFirehoseInputPreprocessingEvent) object; + return new EventPart(event.getRecords().stream() + .map(r -> decode(r.getData())) + .collect(Collectors.toList())); + } else if (object instanceof KinesisAnalyticsStreamsInputPreprocessingEvent) { + KinesisAnalyticsStreamsInputPreprocessingEvent event = + (KinesisAnalyticsStreamsInputPreprocessingEvent) object; + return new EventPart(event.getRecords().stream() + .map(r -> decode(r.getData())) + .collect(Collectors.toList())); + } else { + // does not really make sense to use this EventDeserializer when you already have a typed object + // just not to throw an exception + LOG.warn("Consider using your object directly instead of using EventDeserializer"); + return new EventPart(object); + } + } + + /** + * Meaningful part of a Lambda event.<br/> + * Use {@link #extractDataFrom(Object)} to retrieve an instance of this class. + */ + public static class EventPart { + private Map<String, Object> contentMap; + private String content; + private List<String> contentList; + private Object contentObject; + + private EventPart(List<String> contentList) { + this.contentList = contentList; + } + + private EventPart(String content) { + this.content = content; + } + + private EventPart(Map<String, Object> contentMap) { + this.contentMap = contentMap; + } + + private EventPart(Object content) { + this.contentObject = content; + } + + /** + * Deserialize this part of event from JSON to an object of type T + * + * @param clazz the target type for deserialization + * @param <T> type of object to return + * @return an Object of type T (deserialized from the content) + */ + public <T> T as(Class<T> clazz) { + try { + if (content != null) { + if (content.getClass().equals(clazz)) { + // do not read json when returning String, just return the String + return (T) content; + } + return JsonConfig.get().getObjectMapper().reader().readValue(content, clazz); + } + if (contentMap != null) { + return JsonConfig.get().getObjectMapper().convertValue(contentMap, clazz); + } + if (contentObject != null) { + return (T) contentObject; + } + if (contentList != null) { + throw new EventDeserializationException( + "The content of this event is a list, consider using 'asListOf' instead"); + } + // should not occur, except if the event is malformed (missing fields) + throw new IllegalStateException("Event content is null: the event may be malformed (missing fields)"); + } catch (IOException e) { + throw new EventDeserializationException("Cannot load the event as " + clazz.getSimpleName(), e); + } + } + + public <M> M as() { + TypeReference<M> typeRef = new TypeReference<M>() {}; + + try { + JsonParser parser = JsonConfig.get().getObjectMapper().createParser(content); + return JsonConfig.get().getObjectMapper().reader().readValue(parser, typeRef); + } catch (IOException e) { + throw new EventDeserializationException("Cannot load the event as " + typeRef, e); + } + }; + + /** + * Deserialize this part of event from JSON to a list of objects of type T + * + * @param clazz the target type for deserialization + * @param <T> type of object to return + * @return a list of objects of type T (deserialized from the content) + */ + public <T> List<T> asListOf(Class<T> clazz) { + if (contentList == null && content == null) { + if (contentMap != null || contentObject != null) { + throw new EventDeserializationException( + "The content of this event is not a list, consider using 'as' instead"); + } + // should not occur, except if the event is really malformed + throw new IllegalStateException("Event content is null: the event may be malformed (missing fields)"); + } + if (content != null) { + ObjectReader reader = JsonConfig.get().getObjectMapper().readerForListOf(clazz); + try { + return reader.readValue(content); + } catch (JsonProcessingException e) { + throw new EventDeserializationException( + "Cannot load the event as a list of " + clazz.getSimpleName() + + ", consider using 'as' instead", e); + } + } else { + return contentList.stream().map(s -> + { + try { + return s == null ? null : JsonConfig.get().getObjectMapper().reader().readValue(s, clazz); + } catch (IOException e) { + throw new EventDeserializationException( + "Cannot load the event as a list of " + clazz.getSimpleName(), e); + } + }).collect(Collectors.toList()); + } + } + } +} diff --git a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java new file mode 100644 index 000000000..ac10412fa --- /dev/null +++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java @@ -0,0 +1,132 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.utilities; + +import java.lang.reflect.Type; +import java.util.function.Supplier; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.joda.JodaModule; + +import io.burt.jmespath.JmesPath; +import io.burt.jmespath.RuntimeConfiguration; +import io.burt.jmespath.function.BaseFunction; +import io.burt.jmespath.function.FunctionRegistry; +import io.burt.jmespath.jackson.JacksonRuntime; +import software.amazon.lambda.powertools.utilities.jmespath.Base64Function; +import software.amazon.lambda.powertools.utilities.jmespath.Base64GZipFunction; +import software.amazon.lambda.powertools.utilities.jmespath.JsonFunction; + +public final class JsonConfig { + + private static final Supplier<ObjectMapper> objectMapperSupplier = () -> JsonMapper.builder() + // Don't throw an exception when json has extra fields you are not serializing on. + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + // Ignore null values when writing json. + .defaultPropertyInclusion( + JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.USE_DEFAULTS)) + // Write times as a String instead of a Long so its human-readable. + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + // Sort fields in alphabetical order + .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) + .addModule(new JodaModule()) + .build(); + + private static final ThreadLocal<ObjectMapper> om = ThreadLocal.withInitial(objectMapperSupplier); + + private final FunctionRegistry defaultFunctions = FunctionRegistry.defaultRegistry(); + + private final FunctionRegistry customFunctions = defaultFunctions.extend( + new Base64Function(), + new Base64GZipFunction(), + new JsonFunction()); + + private final RuntimeConfiguration configuration = new RuntimeConfiguration.Builder() + .withSilentTypeErrors(true) + .withFunctionRegistry(customFunctions) + .build(); + + private JmesPath<JsonNode> jmesPath = new JacksonRuntime(configuration, getObjectMapper()); + + private JsonConfig() { + } + + public static JsonConfig get() { + return ConfigHolder.instance; + } + + /** + * Return an Object Mapper. Use this to customize (de)serialization config. + * + * @return the {@link ObjectMapper} to serialize / deserialize JSON + */ + public ObjectMapper getObjectMapper() { + return om.get(); + } + + /** + * Creates a TypeReference from a Class for use with Jackson deserialization. + * This is useful when you need to convert a Class to a TypeReference for generic type handling. + * + * @param clazz the class to convert to TypeReference + * @param <T> the type parameter + * @return a TypeReference wrapping the provided class + */ + public static <T> TypeReference<T> toTypeReference(Class<T> clazz) { + return new TypeReference<T>() { + @Override + public Type getType() { + return clazz; + } + }; + } + + /** + * Return the JmesPath used to select sub node of Json + * + * @return the {@link JmesPath} + */ + public JmesPath<JsonNode> getJmesPath() { + return jmesPath; + } + + /** + * Add a custom {@link io.burt.jmespath.function.Function} to JMESPath + * {@link Base64Function} and {@link Base64GZipFunction} are already built-in. + * + * @param function the function to add + * @param <T> Must extends {@link BaseFunction} + */ + public <T extends BaseFunction> void addFunction(T function) { + FunctionRegistry functionRegistryWithExtendedFunctions = configuration.functionRegistry().extend(function); + + RuntimeConfiguration updatedConfig = new RuntimeConfiguration.Builder() + .withFunctionRegistry(functionRegistryWithExtendedFunctions) + .build(); + + jmesPath = new JacksonRuntime(updatedConfig, getObjectMapper()); + } + + private static final class ConfigHolder { + private static final JsonConfig instance = new JsonConfig(); + } +} diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64Function.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/Base64Function.java similarity index 94% rename from powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64Function.java rename to powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/Base64Function.java index c5693f8a7..26b655fbd 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64Function.java +++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/Base64Function.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,19 +11,19 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.validation.jmespath; -import java.nio.ByteBuffer; -import java.util.Base64; -import java.util.List; +package software.amazon.lambda.powertools.utilities.jmespath; + +import static java.nio.charset.StandardCharsets.UTF_8; import io.burt.jmespath.Adapter; import io.burt.jmespath.JmesPathType; import io.burt.jmespath.function.ArgumentConstraints; import io.burt.jmespath.function.BaseFunction; import io.burt.jmespath.function.FunctionArgument; - -import static java.nio.charset.StandardCharsets.UTF_8; +import java.nio.ByteBuffer; +import java.util.Base64; +import java.util.List; /** * Function used by JMESPath to decode a Base64 encoded String into a decoded String @@ -34,16 +34,6 @@ public Base64Function() { super("powertools_base64", ArgumentConstraints.typeOf(JmesPathType.STRING)); } - @Override - protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) { - T value = arguments.get(0).value(); - String encodedString = runtime.toString(value); - - String decodedString = decode(encodedString); - - return runtime.createString(decodedString); - } - public static String decode(String encodedString) { return new String(decode(encodedString.getBytes(UTF_8)), UTF_8); } @@ -55,4 +45,14 @@ public static String decode(ByteBuffer byteBuffer) { public static byte[] decode(byte[] encoded) { return Base64.getDecoder().decode(encoded); } + + @Override + protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) { + T value = arguments.get(0).value(); + String encodedString = runtime.toString(value); + + String decodedString = decode(encodedString); + + return runtime.createString(decodedString); + } } diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64GZipFunction.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunction.java similarity index 70% rename from powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64GZipFunction.java rename to powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunction.java index bd4b338c4..f5d5beeb1 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64GZipFunction.java +++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,24 +11,23 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.validation.jmespath; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.Arrays; -import java.util.List; -import java.util.zip.GZIPInputStream; +package software.amazon.lambda.powertools.utilities.jmespath; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static software.amazon.lambda.powertools.utilities.jmespath.Base64Function.decode; import io.burt.jmespath.Adapter; import io.burt.jmespath.JmesPathType; import io.burt.jmespath.function.ArgumentConstraints; import io.burt.jmespath.function.BaseFunction; import io.burt.jmespath.function.FunctionArgument; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static software.amazon.lambda.powertools.validation.jmespath.Base64Function.decode; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; +import java.util.zip.GZIPInputStream; /** * Function used by JMESPath to decode a Base64 encoded GZipped String into a decoded String @@ -39,32 +38,22 @@ public Base64GZipFunction() { super("powertools_base64_gzip", ArgumentConstraints.typeOf(JmesPathType.STRING)); } - @Override - protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) { - T value = arguments.get(0).value(); - String encodedString = runtime.toString(value); - - String decompressString = decompress(decode(encodedString.getBytes(UTF_8))); - - return runtime.createString(decompressString); - } - public static String decompress(byte[] compressed) { - if ((compressed == null) || (compressed.length == 0)) { - return ""; + if (compressed == null || compressed.length == 0) { + return null; + } + if (!isCompressed(compressed)) { + return new String(compressed, UTF_8); } try { StringBuilder out = new StringBuilder(); - if (isCompressed(compressed)) { - GZIPInputStream gzipStream = new GZIPInputStream(new ByteArrayInputStream(compressed)); - BufferedReader bf = new BufferedReader(new InputStreamReader(gzipStream, UTF_8)); - String line; - while ((line = bf.readLine()) != null) { - out.append(line); - } - } else { - out.append(Arrays.toString(compressed)); + GZIPInputStream gzipStream = new GZIPInputStream(new ByteArrayInputStream(compressed)); + BufferedReader bf = new BufferedReader(new InputStreamReader(gzipStream, UTF_8)); + + String line; + while ((line = bf.readLine()) != null) { + out.append(line); } return out.toString(); } catch (IOException e) { @@ -73,6 +62,21 @@ public static String decompress(byte[] compressed) { } public static boolean isCompressed(final byte[] compressed) { - return (compressed[0] == (byte) (GZIPInputStream.GZIP_MAGIC)) && (compressed[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8)); + return (compressed[0] == (byte) (GZIPInputStream.GZIP_MAGIC)) && + (compressed[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8)); + } + + @Override + protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) { + T value = arguments.get(0).value(); + String encodedString = runtime.toString(value); + + String decompressString = decompress(decode(encodedString.getBytes(UTF_8))); + + if (decompressString == null) { + return runtime.createNull(); + } + + return runtime.createString(decompressString); } } diff --git a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/JsonFunction.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/JsonFunction.java new file mode 100644 index 000000000..b7661b5af --- /dev/null +++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/JsonFunction.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.utilities.jmespath; + +import io.burt.jmespath.Adapter; +import io.burt.jmespath.JmesPathType; +import io.burt.jmespath.function.ArgumentConstraints; +import io.burt.jmespath.function.BaseFunction; +import io.burt.jmespath.function.FunctionArgument; +import java.util.List; + +public class JsonFunction extends BaseFunction { + + public JsonFunction() { + super("powertools_json", ArgumentConstraints.typeOf(JmesPathType.STRING)); + } + + @Override + protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) { + T value = arguments.get(0).value(); + String jsonString = runtime.toString(value); + return runtime.parseString(jsonString); + } +} diff --git a/powertools-serialization/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization/reflect-config.json b/powertools-serialization/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization/reflect-config.json new file mode 100644 index 000000000..e20bd748f --- /dev/null +++ b/powertools-serialization/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization/reflect-config.json @@ -0,0 +1,427 @@ +[ +{ + "name":"[B" +}, +{ + "name":"[Lcom.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers;" +}, +{ + "name":"[Lcom.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.KeyDeserializers;" +}, +{ + "name":"[Lcom.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiators;" +}, +{ + "name":"[Lcom.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers;" +}, +{ + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setBody","parameterTypes":["java.lang.String"] }, {"name":"setHeaders","parameterTypes":["java.util.Map"] }, {"name":"setHttpMethod","parameterTypes":["java.lang.String"] }, {"name":"setIsBase64Encoded","parameterTypes":["java.lang.Boolean"] }, {"name":"setPath","parameterTypes":["java.lang.String"] }, {"name":"setPathParameters","parameterTypes":["java.util.Map"] }, {"name":"setQueryStringParameters","parameterTypes":["java.util.Map"] }, {"name":"setRequestContext","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext"] }, {"name":"setResource","parameterTypes":["java.lang.String"] }, {"name":"setStageVariables","parameterTypes":["java.util.Map"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setAccountId","parameterTypes":["java.lang.String"] }, {"name":"setApiId","parameterTypes":["java.lang.String"] }, {"name":"setHttpMethod","parameterTypes":["java.lang.String"] }, {"name":"setIdentity","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity"] }, {"name":"setPath","parameterTypes":["java.lang.String"] }, {"name":"setProtocol","parameterTypes":["java.lang.String"] }, {"name":"setRequestId","parameterTypes":["java.lang.String"] }, {"name":"setRequestTime","parameterTypes":["java.lang.String"] }, {"name":"setRequestTimeEpoch","parameterTypes":["java.lang.Long"] }, {"name":"setResourceId","parameterTypes":["java.lang.String"] }, {"name":"setResourcePath","parameterTypes":["java.lang.String"] }, {"name":"setStage","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setAccessKey","parameterTypes":["java.lang.String"] }, {"name":"setAccountId","parameterTypes":["java.lang.String"] }, {"name":"setCaller","parameterTypes":["java.lang.String"] }, {"name":"setCognitoAuthenticationProvider","parameterTypes":["java.lang.String"] }, {"name":"setCognitoAuthenticationType","parameterTypes":["java.lang.String"] }, {"name":"setCognitoIdentityId","parameterTypes":["java.lang.String"] }, {"name":"setCognitoIdentityPoolId","parameterTypes":["java.lang.String"] }, {"name":"setSourceIp","parameterTypes":["java.lang.String"] }, {"name":"setUser","parameterTypes":["java.lang.String"] }, {"name":"setUserAgent","parameterTypes":["java.lang.String"] }, {"name":"setUserArn","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setBody","parameterTypes":["java.lang.String"] }, {"name":"setCookies","parameterTypes":["java.util.List"] }, {"name":"setHeaders","parameterTypes":["java.util.Map"] }, {"name":"setIsBase64Encoded","parameterTypes":["boolean"] }, {"name":"setPathParameters","parameterTypes":["java.util.Map"] }, {"name":"setQueryStringParameters","parameterTypes":["java.util.Map"] }, {"name":"setRawPath","parameterTypes":["java.lang.String"] }, {"name":"setRawQueryString","parameterTypes":["java.lang.String"] }, {"name":"setRequestContext","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext"] }, {"name":"setRouteKey","parameterTypes":["java.lang.String"] }, {"name":"setStageVariables","parameterTypes":["java.util.Map"] }, {"name":"setVersion","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setAccountId","parameterTypes":["java.lang.String"] }, {"name":"setApiId","parameterTypes":["java.lang.String"] }, {"name":"setDomainName","parameterTypes":["java.lang.String"] }, {"name":"setDomainPrefix","parameterTypes":["java.lang.String"] }, {"name":"setHttp","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext$Http"] }, {"name":"setRouteKey","parameterTypes":["java.lang.String"] }, {"name":"setStage","parameterTypes":["java.lang.String"] }, {"name":"setTime","parameterTypes":["java.lang.String"] }, {"name":"setTimeEpoch","parameterTypes":["long"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext$Authorizer", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext$Authorizer$JWT", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext$CognitoIdentity", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext$Http", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setMethod","parameterTypes":["java.lang.String"] }, {"name":"setPath","parameterTypes":["java.lang.String"] }, {"name":"setProtocol","parameterTypes":["java.lang.String"] }, {"name":"setSourceIp","parameterTypes":["java.lang.String"] }, {"name":"setUserAgent","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent$RequestContext$IAM", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.ActiveMQEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setEventSource","parameterTypes":["java.lang.String"] }, {"name":"setEventSourceArn","parameterTypes":["java.lang.String"] }, {"name":"setMessages","parameterTypes":["java.util.List"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.ActiveMQEvent$ActiveMQMessage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setBrokerInTime","parameterTypes":["long"] }, {"name":"setBrokerOutTime","parameterTypes":["long"] }, {"name":"setData","parameterTypes":["java.lang.String"] }, {"name":"setDeliveryMode","parameterTypes":["int"] }, {"name":"setDestination","parameterTypes":["com.amazonaws.services.lambda.runtime.events.ActiveMQEvent$Destination"] }, {"name":"setExpiration","parameterTypes":["long"] }, {"name":"setMessageID","parameterTypes":["java.lang.String"] }, {"name":"setMessageType","parameterTypes":["java.lang.String"] }, {"name":"setPriority","parameterTypes":["int"] }, {"name":"setProperties","parameterTypes":["java.util.Map"] }, {"name":"setRedelivered","parameterTypes":["boolean"] }, {"name":"setReplyTo","parameterTypes":["java.lang.String"] }, {"name":"setTimestamp","parameterTypes":["long"] }, {"name":"setType","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.ActiveMQEvent$Destination", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setPhysicalName","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setBody","parameterTypes":["java.lang.String"] }, {"name":"setHeaders","parameterTypes":["java.util.Map"] }, {"name":"setHttpMethod","parameterTypes":["java.lang.String"] }, {"name":"setIsBase64Encoded","parameterTypes":["boolean"] }, {"name":"setPath","parameterTypes":["java.lang.String"] }, {"name":"setQueryStringParameters","parameterTypes":["java.util.Map"] }, {"name":"setRequestContext","parameterTypes":["com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent$RequestContext"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent$Elb", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setTargetGroupArn","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent$RequestContext", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setElb","parameterTypes":["com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent$Elb"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setLogicalResourceId","parameterTypes":["java.lang.String"] }, {"name":"setOldResourceProperties","parameterTypes":["java.util.Map"] }, {"name":"setRequestId","parameterTypes":["java.lang.String"] }, {"name":"setRequestType","parameterTypes":["java.lang.String"] }, {"name":"setResourceProperties","parameterTypes":["java.util.Map"] }, {"name":"setResourceType","parameterTypes":["java.lang.String"] }, {"name":"setServiceToken","parameterTypes":["java.lang.String"] }, {"name":"setStackId","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setAwsLogs","parameterTypes":["com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent$AWSLogs"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent$AWSLogs", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setData","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KafkaEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setBootstrapServers","parameterTypes":["java.lang.String"] }, {"name":"setEventSource","parameterTypes":["java.lang.String"] }, {"name":"setEventSourceArn","parameterTypes":["java.lang.String"] }, {"name":"setRecords","parameterTypes":["java.util.Map"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KafkaEvent$KafkaEventRecord", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setOffset","parameterTypes":["long"] }, {"name":"setPartition","parameterTypes":["int"] }, {"name":"setTimestamp","parameterTypes":["long"] }, {"name":"setTimestampType","parameterTypes":["java.lang.String"] }, {"name":"setTopic","parameterTypes":["java.lang.String"] }, {"name":"setValue","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setApplicationArn","parameterTypes":["java.lang.String"] }, {"name":"setInvocationId","parameterTypes":["java.lang.String"] }, {"name":"setRecords","parameterTypes":["java.util.List"] }, {"name":"setStreamArn","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent$Record", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setData","parameterTypes":["java.nio.ByteBuffer"] }, {"name":"setKinesisFirehoseRecordMetadata","parameterTypes":["com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent$Record$KinesisFirehoseRecordMetadata"] }, {"name":"setRecordId","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent$Record$KinesisFirehoseRecordMetadata", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setApproximateArrivalTimestamp","parameterTypes":["java.lang.Long"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setApplicationArn","parameterTypes":["java.lang.String"] }, {"name":"setInvocationId","parameterTypes":["java.lang.String"] }, {"name":"setRecords","parameterTypes":["java.util.List"] }, {"name":"setStreamArn","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent$Record", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setData","parameterTypes":["java.nio.ByteBuffer"] }, {"name":"setKinesisStreamRecordMetadata","parameterTypes":["com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent$Record$KinesisStreamRecordMetadata"] }, {"name":"setRecordId","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent$Record$KinesisStreamRecordMetadata", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setApproximateArrivalTimestamp","parameterTypes":["java.lang.Long"] }, {"name":"setPartitionKey","parameterTypes":["java.lang.String"] }, {"name":"setSequenceNumber","parameterTypes":["java.lang.String"] }, {"name":"setShardId","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KinesisEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setRecords","parameterTypes":["java.util.List"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KinesisEvent$KinesisEventRecord", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setAwsRegion","parameterTypes":["java.lang.String"] }, {"name":"setEventID","parameterTypes":["java.lang.String"] }, {"name":"setEventName","parameterTypes":["java.lang.String"] }, {"name":"setEventSource","parameterTypes":["java.lang.String"] }, {"name":"setEventSourceARN","parameterTypes":["java.lang.String"] }, {"name":"setEventVersion","parameterTypes":["java.lang.String"] }, {"name":"setInvokeIdentityArn","parameterTypes":["java.lang.String"] }, {"name":"setKinesis","parameterTypes":["com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setKinesisSchemaVersion","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setDeliveryStreamArn","parameterTypes":["java.lang.String"] }, {"name":"setInvocationId","parameterTypes":["java.lang.String"] }, {"name":"setRecords","parameterTypes":["java.util.List"] }, {"name":"setRegion","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent$Record", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setApproximateArrivalTimestamp","parameterTypes":["java.lang.Long"] }, {"name":"setData","parameterTypes":["java.nio.ByteBuffer"] }, {"name":"setRecordId","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.RabbitMQEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setEventSource","parameterTypes":["java.lang.String"] }, {"name":"setEventSourceArn","parameterTypes":["java.lang.String"] }, {"name":"setRmqMessagesByQueue","parameterTypes":["java.util.Map"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.RabbitMQEvent$BasicProperties", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setAppId","parameterTypes":["java.lang.String"] }, {"name":"setBodySize","parameterTypes":["int"] }, {"name":"setClusterId","parameterTypes":["java.lang.String"] }, {"name":"setContentEncoding","parameterTypes":["java.lang.String"] }, {"name":"setContentType","parameterTypes":["java.lang.String"] }, {"name":"setCorrelationId","parameterTypes":["java.lang.String"] }, {"name":"setDeliveryMode","parameterTypes":["int"] }, {"name":"setExpiration","parameterTypes":["int"] }, {"name":"setHeaders","parameterTypes":["java.util.Map"] }, {"name":"setMessageId","parameterTypes":["java.lang.String"] }, {"name":"setPriority","parameterTypes":["int"] }, {"name":"setReplyTo","parameterTypes":["java.lang.String"] }, {"name":"setTimestamp","parameterTypes":["java.lang.String"] }, {"name":"setType","parameterTypes":["java.lang.String"] }, {"name":"setUserId","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.RabbitMQEvent$RabbitMessage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setBasicProperties","parameterTypes":["com.amazonaws.services.lambda.runtime.events.RabbitMQEvent$BasicProperties"] }, {"name":"setData","parameterTypes":["java.lang.String"] }, {"name":"setRedelivered","parameterTypes":["boolean"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.SNSEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setRecords","parameterTypes":["java.util.List"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.SNSEvent$MessageAttribute", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setType","parameterTypes":["java.lang.String"] }, {"name":"setValue","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.SNSEvent$SNS", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setMessage","parameterTypes":["java.lang.String"] }, {"name":"setMessageAttributes","parameterTypes":["java.util.Map"] }, {"name":"setMessageId","parameterTypes":["java.lang.String"] }, {"name":"setSignature","parameterTypes":["java.lang.String"] }, {"name":"setSignatureVersion","parameterTypes":["java.lang.String"] }, {"name":"setSigningCertUrl","parameterTypes":["java.lang.String"] }, {"name":"setSubject","parameterTypes":["java.lang.String"] }, {"name":"setTimestamp","parameterTypes":["org.joda.time.DateTime"] }, {"name":"setTopicArn","parameterTypes":["java.lang.String"] }, {"name":"setType","parameterTypes":["java.lang.String"] }, {"name":"setUnsubscribeUrl","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setEventSource","parameterTypes":["java.lang.String"] }, {"name":"setEventSubscriptionArn","parameterTypes":["java.lang.String"] }, {"name":"setEventVersion","parameterTypes":["java.lang.String"] }, {"name":"setSns","parameterTypes":["com.amazonaws.services.lambda.runtime.events.SNSEvent$SNS"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.SQSEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setRecords","parameterTypes":["java.util.List"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.SQSEvent$MessageAttribute", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setAttributes","parameterTypes":["java.util.Map"] }, {"name":"setAwsRegion","parameterTypes":["java.lang.String"] }, {"name":"setBody","parameterTypes":["java.lang.String"] }, {"name":"setEventSource","parameterTypes":["java.lang.String"] }, {"name":"setEventSourceArn","parameterTypes":["java.lang.String"] }, {"name":"setMd5OfBody","parameterTypes":["java.lang.String"] }, {"name":"setMd5OfMessageAttributes","parameterTypes":["java.lang.String"] },{"name":"setMessageAttributes","parameterTypes":["java.util.Map"] }, {"name":"setMessageId","parameterTypes":["java.lang.String"] }, {"name":"setReceiptHandle","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.ScheduledEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setAccount","parameterTypes":["java.lang.String"] }, {"name":"setDetail","parameterTypes":["java.util.Map"] }, {"name":"setDetailType","parameterTypes":["java.lang.String"] }, {"name":"setId","parameterTypes":["java.lang.String"] }, {"name":"setRegion","parameterTypes":["java.lang.String"] }, {"name":"setResources","parameterTypes":["java.util.List"] }, {"name":"setSource","parameterTypes":["java.lang.String"] }, {"name":"setTime","parameterTypes":["org.joda.time.DateTime"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.events.models.kinesis.Record", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "methods":[{"name":"setApproximateArrivalTimestamp","parameterTypes":["java.util.Date"] }, {"name":"setData","parameterTypes":["java.nio.ByteBuffer"] }, {"name":"setEncryptionType","parameterTypes":["java.lang.String"] }, {"name":"setPartitionKey","parameterTypes":["java.lang.String"] }, {"name":"setSequenceNumber","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.serialization.events.mixins.CloudFormationCustomResourceEventMixin", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.serialization.events.mixins.CloudWatchLogsEventMixin", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.serialization.events.mixins.KinesisEventMixin", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.serialization.events.mixins.KinesisEventMixin$RecordMixin", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.serialization.events.mixins.SNSEventMixin", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.serialization.events.mixins.SNSEventMixin$SNSRecordMixin", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.serialization.events.mixins.SQSEventMixin", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.serialization.events.mixins.SQSEventMixin$SQSMessageMixin", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.serialization.events.mixins.ScheduledEventMixin", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.services.lambda.runtime.tests.EventArgumentsProvider", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.services.lambda.runtime.tests.annotations.Event", + "queryAllPublicMethods":true +}, +{ + "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"java.io.Serializable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Cloneable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Object", + "allDeclaredFields":true +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.function.Consumer", + "queryAllPublicMethods":true +}, +{ + "name":"org.apiguardian.api.API", + "queryAllPublicMethods":true +}, +{ + "name":"org.joda.time.DateTime", + "methods":[{"name":"parse","parameterTypes":["java.lang.String"] }] +} +] diff --git a/powertools-serialization/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization/resource-config.json b/powertools-serialization/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization/resource-config.json new file mode 100644 index 000000000..58b01cd07 --- /dev/null +++ b/powertools-serialization/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization/resource-config.json @@ -0,0 +1,57 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\Qcom/amazonaws/lambda/thirdparty/org/joda/time/tz/data/Europe/Amsterdam\\E" + }, { + "pattern":"\\Qcom/amazonaws/lambda/thirdparty/org/joda/time/tz/data/ZoneInfoMap\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/alb_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/amq_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/apigw_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/apigw_event_no_body.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/apigwv2_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/cfcr_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/custom_event_map.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/cwl_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/kafip_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/kafka_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/kasip_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/kf_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/kinesis_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/rabbitmq_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/scheduled_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/sns_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/sqs_event.json\\E" + }, { + "pattern":"\\Qcom/amazonaws/services/lambda/runtime/serialization/factories/sqs_event_no_body.json\\E" + }, { + "pattern":"\\Qorg/joda/time/tz/data/Europe/Amsterdam\\E" + }, { + "pattern":"\\Qorg/joda/time/tz/data/ZoneInfoMap\\E" + }]}, + "bundles":[] +} diff --git a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/EventDeserializerTest.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/EventDeserializerTest.java new file mode 100644 index 000000000..14fc32231 --- /dev/null +++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/EventDeserializerTest.java @@ -0,0 +1,395 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.utilities; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; + +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.ActiveMQEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent; +import com.amazonaws.services.lambda.runtime.events.KafkaEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; +import com.amazonaws.services.lambda.runtime.tests.annotations.Event; +import com.fasterxml.jackson.databind.JsonNode; + +import software.amazon.lambda.powertools.utilities.model.Order; +import software.amazon.lambda.powertools.utilities.model.Product; + +class EventDeserializerTest { + + @Test + void testDeserializeStringAsString_shouldReturnString() { + String stringEvent = "Hello World"; + String result = extractDataFrom(stringEvent).as(String.class); + assertThat(result).isEqualTo(stringEvent); + } + + @Test + void testDeserializeStringAsObject_shouldReturnObject() { + String productStr = "{\"id\":1234, \"name\":\"product\", \"price\":42}"; + Product product = extractDataFrom(productStr).as(Product.class); + assertProduct(product); + } + + @Test + void testDeserializeStringArrayAsList_shouldReturnList() { + String productStr = "[{\"id\":1234, \"name\":\"product\", \"price\":42}, {\"id\":2345, \"name\":\"product2\", \"price\":43}]"; + List<Product> products = extractDataFrom(productStr).asListOf(Product.class); + assertThat(products).hasSize(2); + assertProduct(products.get(0)); + } + + @Test + void testDeserializeStringAsList_shouldThrowException() { + String productStr = "{\"id\":1234, \"name\":\"product\", \"price\":42}"; + assertThatThrownBy(() -> extractDataFrom(productStr).asListOf(Product.class)) + .isInstanceOf(EventDeserializationException.class) + .hasMessage("Cannot load the event as a list of Product, consider using 'as' instead"); + } + + @Test + void testDeserializeMapAsObject_shouldReturnObject() { + Map<String, Object> map = new HashMap<>(); + map.put("id", 1234); + map.put("name", "product"); + map.put("price", 42); + Product product = extractDataFrom(map).as(Product.class); + assertProduct(product); + } + + @ParameterizedTest + @Event(value = "apigw_event.json", type = APIGatewayProxyRequestEvent.class) + void testDeserializeAPIGWEventBodyAsObject_shouldReturnObject(APIGatewayProxyRequestEvent event) { + Product product = extractDataFrom(event).as(Product.class); + assertProduct(product); + } + + @ParameterizedTest + @Event(value = "sns_event.json", type = SNSEvent.class) + void testDeserializeSNSEventMessageAsObject_shouldReturnObject(SNSEvent event) { + Product product = extractDataFrom(event).as(Product.class); + assertProduct(product); + } + + @ParameterizedTest + @Event(value = "sqs_event.json", type = SQSEvent.class) + void testDeserializeSQSEventMessageAsList_shouldReturnList(SQSEvent event) { + List<Product> products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(2); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "kinesis_event.json", type = KinesisEvent.class) + void testDeserializeKinesisEventMessageAsList_shouldReturnList(KinesisEvent event) { + List<Product> products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(2); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "kafka_event.json", type = KafkaEvent.class) + void testDeserializeKafkaEventMessageAsList_shouldReturnList(KafkaEvent event) { + List<Product> products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(2); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "sqs_event.json", type = SQSEvent.class) + void testDeserializeSQSEventMessageAsObject_shouldThrowException(SQSEvent event) { + assertThatThrownBy(() -> extractDataFrom(event).as(Product.class)) + .isInstanceOf(EventDeserializationException.class) + .hasMessageContaining("consider using 'asListOf' instead"); + } + + @ParameterizedTest + @Event(value = "apigw_event.json", type = APIGatewayProxyRequestEvent.class) + void testDeserializeAPIGatewayEventAsList_shouldThrowException(APIGatewayProxyRequestEvent event) { + assertThatThrownBy(() -> extractDataFrom(event).asListOf(Product.class)) + .isInstanceOf(EventDeserializationException.class) + .hasMessageContaining("consider using 'as' instead") + .hasMessageContaining("Cannot load the event as a list of"); + } + + @ParameterizedTest + @Event(value = "custom_event_map.json", type = HashMap.class) + void testDeserializeAPIGatewayMapEventAsList_shouldThrowException(Map<String, Order> event) { + assertThatThrownBy(() -> extractDataFrom(event).asListOf(Order.class)) + .isInstanceOf(EventDeserializationException.class) + .hasMessage("The content of this event is not a list, consider using 'as' instead"); + } + + @Test + void testDeserializeEmptyEventAsList_shouldThrowException() { + assertThatThrownBy(() -> extractDataFrom(null).asListOf(Product.class)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Event content is null: the event may be malformed (missing fields)"); + } + + @ParameterizedTest + @Event(value = "apigw_event_no_body.json", type = APIGatewayProxyRequestEvent.class) + void testDeserializeAPIGatewayNoBody_shouldThrowException(APIGatewayProxyRequestEvent event) { + assertThatThrownBy(() -> extractDataFrom(event).as(Product.class)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Event content is null: the event may be malformed (missing fields)"); + } + + @Test + void testDeserializeAPIGatewayNoBodyAsList_shouldThrowException() { + assertThatThrownBy(() -> extractDataFrom(new Object()).asListOf(Product.class)) + .isInstanceOf(EventDeserializationException.class) + .hasMessage("The content of this event is not a list, consider using 'as' instead"); + } + + @ParameterizedTest + @Event(value = "sqs_event_no_body.json", type = SQSEvent.class) + void testDeserializeSQSEventNoBody_shouldThrowException(SQSEvent event) { + List<Product> products = extractDataFrom(event).asListOf(Product.class); + assertThat(products.get(0)).isNull(); + } + + @Test + void testDeserializeProductAsProduct_shouldReturnProduct() { + Product myProduct = new Product(1234, "product", 42); + Product product = extractDataFrom(myProduct).as(Product.class); + assertProduct(product); + } + + private void assertProduct(Product product) { + assertThat(product) + .isEqualTo(new Product(1234, "product", 42)) + .usingRecursiveComparison(); + } + + @ParameterizedTest + @Event(value = "scheduled_event.json", type = ScheduledEvent.class) + void testDeserializeScheduledEventMessageAsObject_shouldReturnObject(ScheduledEvent event) { + Product product = extractDataFrom(event).as(Product.class); + assertProduct(product); + } + + @ParameterizedTest + @Event(value = "alb_event.json", type = ApplicationLoadBalancerRequestEvent.class) + void testDeserializeALBEventMessageAsObjectShouldReturnObject(ApplicationLoadBalancerRequestEvent event) { + Product product = extractDataFrom(event).as(Product.class); + assertProduct(product); + } + + @ParameterizedTest + @Event(value = "cwl_event.json", type = CloudWatchLogsEvent.class) + void testDeserializeCWLEventMessageAsObjectShouldReturnObject(CloudWatchLogsEvent event) { + Product product = extractDataFrom(event).as(Product.class); + assertProduct(product); + } + + @ParameterizedTest + @Event(value = "kf_event.json", type = KinesisFirehoseEvent.class) + void testDeserializeKFEventMessageAsListShouldReturnList(KinesisFirehoseEvent event) { + List<Product> products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(1); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "amq_event.json", type = ActiveMQEvent.class) + void testDeserializeAMQEventMessageAsListShouldReturnList(ActiveMQEvent event) { + List<Product> products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(1); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "rabbitmq_event.json", type = RabbitMQEvent.class) + void testDeserializeRabbitMQEventMessageAsListShouldReturnList(RabbitMQEvent event) { + List<Product> products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(1); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "kasip_event.json", type = KinesisAnalyticsStreamsInputPreprocessingEvent.class) + void testDeserializeKasipEventMessageAsListShouldReturnList( + KinesisAnalyticsStreamsInputPreprocessingEvent event) { + List<Product> products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(1); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "kafip_event.json", type = KinesisAnalyticsFirehoseInputPreprocessingEvent.class) + void testDeserializeKafipEventMessageAsListShouldReturnList( + KinesisAnalyticsFirehoseInputPreprocessingEvent event) { + List<Product> products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(1); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "apigwv2_event.json", type = APIGatewayV2HTTPEvent.class) + void testDeserializeApiGWV2EventMessageAsObjectShouldReturnObject(APIGatewayV2HTTPEvent event) { + Product product = extractDataFrom(event).as(Product.class); + assertProduct(product); + } + + @ParameterizedTest + @Event(value = "cfcr_event.json", type = CloudFormationCustomResourceEvent.class) + void testDeserializeCfcrEventMessageAsObjectShouldReturnObject(CloudFormationCustomResourceEvent event) { + Product product = extractDataFrom(event).as(Product.class); + assertProduct(product); + } + + @ParameterizedTest + @Event(value = "scheduled_event.json", type = ScheduledEvent.class) + void testSerializeScheduledEvent_shouldReturnValidJson(ScheduledEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("detail")).isTrue(); + } + + @ParameterizedTest + @Event(value = "apigw_event.json", type = APIGatewayProxyRequestEvent.class) + void testSerializeAPIGatewayEvent_shouldReturnValidJson(APIGatewayProxyRequestEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("body")).isTrue(); + } + + @ParameterizedTest + @Event(value = "sqs_event.json", type = SQSEvent.class) + void testSerializeSQSEvent_shouldReturnValidJson(SQSEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("records")).isTrue(); + } + + @ParameterizedTest + @Event(value = "sns_event.json", type = SNSEvent.class) + void testSerializeSNSEvent_shouldReturnValidJson(SNSEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("records")).isTrue(); + } + + @ParameterizedTest + @Event(value = "kinesis_event.json", type = KinesisEvent.class) + void testSerializeKinesisEvent_shouldReturnValidJson(KinesisEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("records")).isTrue(); + } + + @ParameterizedTest + @Event(value = "kafka_event.json", type = KafkaEvent.class) + void testSerializeKafkaEvent_shouldReturnValidJson(KafkaEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("records")).isTrue(); + } + + @ParameterizedTest + @Event(value = "alb_event.json", type = ApplicationLoadBalancerRequestEvent.class) + void testSerializeALBEvent_shouldReturnValidJson(ApplicationLoadBalancerRequestEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("body")).isTrue(); + } + + @ParameterizedTest + @Event(value = "cwl_event.json", type = CloudWatchLogsEvent.class) + void testSerializeCWLEvent_shouldReturnValidJson(CloudWatchLogsEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("awsLogs")).isTrue(); + } + + @ParameterizedTest + @Event(value = "kf_event.json", type = KinesisFirehoseEvent.class) + void testSerializeKFEvent_shouldReturnValidJson(KinesisFirehoseEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("records")).isTrue(); + } + + @ParameterizedTest + @Event(value = "amq_event.json", type = ActiveMQEvent.class) + void testSerializeAMQEvent_shouldReturnValidJson(ActiveMQEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("messages")).isTrue(); + } + + @ParameterizedTest + @Event(value = "rabbitmq_event.json", type = RabbitMQEvent.class) + void testSerializeRabbitMQEvent_shouldReturnValidJson(RabbitMQEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("rmqMessagesByQueue")).isTrue(); + } + + @ParameterizedTest + @Event(value = "kasip_event.json", type = KinesisAnalyticsStreamsInputPreprocessingEvent.class) + void testSerializeKasipEvent_shouldReturnValidJson(KinesisAnalyticsStreamsInputPreprocessingEvent event) + throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("records")).isTrue(); + } + + @ParameterizedTest + @Event(value = "kafip_event.json", type = KinesisAnalyticsFirehoseInputPreprocessingEvent.class) + void testSerializeKafipEvent_shouldReturnValidJson(KinesisAnalyticsFirehoseInputPreprocessingEvent event) + throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("records")).isTrue(); + } + + @ParameterizedTest + @Event(value = "apigwv2_event.json", type = APIGatewayV2HTTPEvent.class) + void testSerializeApiGWV2Event_shouldReturnValidJson(APIGatewayV2HTTPEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("body")).isTrue(); + } + + @ParameterizedTest + @Event(value = "cfcr_event.json", type = CloudFormationCustomResourceEvent.class) + void testSerializeCfcrEvent_shouldReturnValidJson(CloudFormationCustomResourceEvent event) throws Exception { + String json = JsonConfig.get().getObjectMapper().valueToTree(event).toString(); + JsonNode parsed = JsonConfig.get().getObjectMapper().readTree(json); + assertThat(parsed.has("resourceProperties")).isTrue(); + } + +} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64FunctionTest.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/Base64FunctionTest.java similarity index 68% rename from powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64FunctionTest.java rename to powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/Base64FunctionTest.java index b9bbd6f88..478d3477c 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64FunctionTest.java +++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/Base64FunctionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,23 +11,29 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.validation; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeType; -import io.burt.jmespath.Expression; -import org.junit.jupiter.api.Test; +package software.amazon.lambda.powertools.utilities.jmespath; + +import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeType; + +import io.burt.jmespath.Expression; +import software.amazon.lambda.powertools.utilities.JsonConfig; -public class Base64FunctionTest { +class Base64FunctionTest { @Test - public void testPowertoolsBase64() throws IOException { - JsonNode event = ValidationConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/custom_event.json")); - Expression<JsonNode> expression = ValidationConfig.get().getJmesPath().compile("basket.powertools_base64(hiddenProduct)"); + void testPowertoolsBase64() throws IOException { + JsonNode event = JsonConfig.get().getObjectMapper() + .readTree(this.getClass().getResourceAsStream("/custom_event.json")); + Expression<JsonNode> expression = JsonConfig.get().getJmesPath() + .compile("basket.powertools_base64(hiddenProduct)"); JsonNode result = expression.search(event); assertThat(result.getNodeType()).isEqualTo(JsonNodeType.STRING); assertThat(result.asText()).isEqualTo("{\n" + diff --git a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunctionTest.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunctionTest.java new file mode 100644 index 000000000..6f9f9a592 --- /dev/null +++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunctionTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.utilities.jmespath; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeType; + +import io.burt.jmespath.Expression; +import io.burt.jmespath.JmesPathType; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +class Base64GZipFunctionTest { + + @Test + void testConstructor() { + Base64GZipFunction base64GZipFunction = new Base64GZipFunction(); + assertThat(base64GZipFunction.name()).isEqualTo("powertools_base64_gzip"); + assertThat(base64GZipFunction.argumentConstraints().expectedType().toLowerCase()).isEqualTo( + JmesPathType.STRING.name().toLowerCase()); + assertThat(base64GZipFunction.argumentConstraints().minArity()).isEqualTo(1); + assertThat(base64GZipFunction.argumentConstraints().maxArity()).isEqualTo(1); + + } + + @Test + void testPowertoolsGzip() throws IOException { + JsonNode event = JsonConfig.get().getObjectMapper() + .readTree(this.getClass().getResourceAsStream("/custom_event_gzip.json")); + Expression<JsonNode> expression = JsonConfig.get().getJmesPath() + .compile("basket.powertools_base64_gzip(hiddenProduct)"); + JsonNode result = expression.search(event); + assertThat(result.getNodeType()).isEqualTo(JsonNodeType.STRING); + assertThat(result.asText()).isEqualTo("{ \"id\": 43242, \"name\": \"FooBar XY\", \"price\": 258}"); + } + + @Test + void testPowertoolsGzipEmptyJsonAttribute() throws IOException { + JsonNode event = JsonConfig.get().getObjectMapper() + .readTree(this.getClass().getResourceAsStream("/custom_event_gzip.json")); + Expression<JsonNode> expression = JsonConfig.get().getJmesPath().compile("basket.powertools_base64_gzip('')"); + JsonNode result = expression.search(event); + assertThat(result.getNodeType()).isEqualTo(JsonNodeType.NULL); + } + + @Test + void testPowertoolsGzipWrongArgumentType() throws IOException { + JsonNode event = JsonConfig.get().getObjectMapper() + .readTree(this.getClass().getResourceAsStream("/custom_event_gzip.json")); + Expression<JsonNode> expression = JsonConfig.get().getJmesPath().compile("basket.powertools_base64_gzip(null)"); + JsonNode result = expression.search(event); + + assertThat(result.getNodeType()).isEqualTo(JsonNodeType.NULL); + } + + @Test + void testBase64GzipDecompressNull() { + String result = Base64GZipFunction.decompress(null); + assertThat(result).isNull(); + } + + @Test + void testPowertoolsGzipNotCompressedJsonAttribute() throws IOException { + JsonNode event = JsonConfig.get().getObjectMapper() + .readTree(this.getClass().getResourceAsStream("/custom_event_gzip.json")); + Expression<JsonNode> expression = JsonConfig.get().getJmesPath() + .compile("basket.powertools_base64_gzip(encodedString)"); + JsonNode result = expression.search(event); + assertThat(result.getNodeType()).isEqualTo(JsonNodeType.STRING); + assertThat(result.asText()).isEqualTo("test"); + } + +} diff --git a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/JsonFunctionTest.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/JsonFunctionTest.java new file mode 100644 index 000000000..028dba9ea --- /dev/null +++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/JsonFunctionTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.utilities.jmespath; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeType; + +import io.burt.jmespath.Expression; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +class JsonFunctionTest { + + @Test + void testJsonFunction() throws IOException { + JsonNode event = JsonConfig.get().getObjectMapper() + .readTree(this.getClass().getResourceAsStream("/custom_event_json.json")); + Expression<JsonNode> expression = JsonConfig.get().getJmesPath().compile("powertools_json(body)"); + JsonNode result = expression.search(event); + assertThat(result.getNodeType()).isEqualTo(JsonNodeType.OBJECT); + assertThat(result.get("message").asText()).isEqualTo("Lambda rocks"); + assertThat(result.get("list").isArray()).isTrue(); + assertThat(result.get("list").size()).isEqualTo(2); + } + + @Test + void testJsonFunctionChild() throws IOException { + JsonNode event = JsonConfig.get().getObjectMapper() + .readTree(this.getClass().getResourceAsStream("/custom_event_json.json")); + Expression<JsonNode> expression = JsonConfig.get().getJmesPath().compile("powertools_json(body).list[0].item"); + JsonNode result = expression.search(event); + assertThat(result.getNodeType()).isEqualTo(JsonNodeType.STRING); + assertThat(result.asText()).isEqualTo("4gh345h"); + } +} diff --git a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Basket.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Basket.java new file mode 100644 index 000000000..4bf427a21 --- /dev/null +++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Basket.java @@ -0,0 +1,60 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.utilities.model; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class Basket { + private List<Product> products = new ArrayList<>(); + + public Basket() { + } + + public Basket(Product... p) { + products.addAll(Arrays.asList(p)); + } + + public List<Product> getProducts() { + return products; + } + + public void setProducts(List<Product> products) { + this.products = products; + } + + public void add(Product product) { + products.add(product); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Basket basket = (Basket) o; + return products.equals(basket.products); + } + + @Override + public int hashCode() { + return Objects.hash(products); + } +} diff --git a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Order.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Order.java new file mode 100644 index 000000000..6b48ccd1d --- /dev/null +++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Order.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.utilities.model; + +import java.util.HashMap; +import java.util.Map; + +public class Order { + private Map<String, Product> orders = new HashMap<>(); + + public Order() { + } + + public Map<String, Product> getProducts() { + return orders; + } + + public void setProducts(Map<String, Product> products) { + this.orders = products; + } + +} diff --git a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Product.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Product.java new file mode 100644 index 000000000..c90a4632e --- /dev/null +++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Product.java @@ -0,0 +1,75 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.utilities.model; + +import java.util.Objects; + +public class Product { + private long id; + + private String name; + + private double price; + + public Product() { + } + + public Product(long id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Product product = (Product) o; + return id == product.id && Double.compare(product.price, price) == 0 && Objects.equals(name, product.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, price); + } +} diff --git a/powertools-serialization/src/test/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization/reflect-config.json b/powertools-serialization/src/test/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization/reflect-config.json new file mode 100644 index 000000000..b781d2609 --- /dev/null +++ b/powertools-serialization/src/test/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization/reflect-config.json @@ -0,0 +1,45 @@ +[ +{ + "name":"software.amazon.lambda.powertools.utilities.EventDeserializerTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testDeserializeALBEventMessageAsObjectShouldReturnObject","parameterTypes":["com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent"] }, {"name":"testDeserializeAMQEventMessageAsListShouldReturnList","parameterTypes":["com.amazonaws.services.lambda.runtime.events.ActiveMQEvent"] }, {"name":"testDeserializeAPIGWEventBodyAsObject_shouldReturnObject","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent"] }, {"name":"testDeserializeAPIGatewayEventAsList_shouldThrowException","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent"] }, {"name":"testDeserializeAPIGatewayMapEventAsList_shouldThrowException","parameterTypes":["java.util.Map"] }, {"name":"testDeserializeAPIGatewayNoBodyAsList_shouldThrowException","parameterTypes":[] }, {"name":"testDeserializeAPIGatewayNoBody_shouldThrowException","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent"] }, {"name":"testDeserializeApiGWV2EventMessageAsObjectShouldReturnObject","parameterTypes":["com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent"] }, {"name":"testDeserializeCWLEventMessageAsObjectShouldReturnObject","parameterTypes":["com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent"] }, {"name":"testDeserializeCfcrEventMessageAsObjectShouldReturnObject","parameterTypes":["com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent"] }, {"name":"testDeserializeEmptyEventAsList_shouldThrowException","parameterTypes":[] }, {"name":"testDeserializeKFEventMessageAsListShouldReturnList","parameterTypes":["com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent"] }, {"name":"testDeserializeKafipEventMessageAsListShouldReturnList","parameterTypes":["com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent"] }, {"name":"testDeserializeKafkaEventMessageAsList_shouldReturnList","parameterTypes":["com.amazonaws.services.lambda.runtime.events.KafkaEvent"] }, {"name":"testDeserializeKasipEventMessageAsListShouldReturnList","parameterTypes":["com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent"] }, {"name":"testDeserializeKinesisEventMessageAsList_shouldReturnList","parameterTypes":["com.amazonaws.services.lambda.runtime.events.KinesisEvent"] }, {"name":"testDeserializeMapAsObject_shouldReturnObject","parameterTypes":[] }, {"name":"testDeserializeProductAsProduct_shouldReturnProduct","parameterTypes":[] }, {"name":"testDeserializeRabbitMQEventMessageAsListShouldReturnList","parameterTypes":["com.amazonaws.services.lambda.runtime.events.RabbitMQEvent"] }, {"name":"testDeserializeSNSEventMessageAsObject_shouldReturnObject","parameterTypes":["com.amazonaws.services.lambda.runtime.events.SNSEvent"] }, {"name":"testDeserializeSQSEventMessageAsList_shouldReturnList","parameterTypes":["com.amazonaws.services.lambda.runtime.events.SQSEvent"] }, {"name":"testDeserializeSQSEventMessageAsObject_shouldThrowException","parameterTypes":["com.amazonaws.services.lambda.runtime.events.SQSEvent"] }, {"name":"testDeserializeSQSEventNoBody_shouldThrowException","parameterTypes":["com.amazonaws.services.lambda.runtime.events.SQSEvent"] }, {"name":"testDeserializeScheduledEventMessageAsObject_shouldReturnObject","parameterTypes":["com.amazonaws.services.lambda.runtime.events.ScheduledEvent"] }, {"name":"testDeserializeStringArrayAsList_shouldReturnList","parameterTypes":[] }, {"name":"testDeserializeStringAsList_shouldThrowException","parameterTypes":[] }, {"name":"testDeserializeStringAsObject_shouldReturnObject","parameterTypes":[] }, {"name":"testDeserializeStringAsString_shouldReturnString","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.utilities.jmespath.Base64FunctionTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testPowertoolsBase64","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.utilities.jmespath.Base64GZipFunctionTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testBase64GzipDecompressNull","parameterTypes":[] }, {"name":"testConstructor","parameterTypes":[] }, {"name":"testPowertoolsGzip","parameterTypes":[] }, {"name":"testPowertoolsGzipEmptyJsonAttribute","parameterTypes":[] }, {"name":"testPowertoolsGzipNotCompressedJsonAttribute","parameterTypes":[] }, {"name":"testPowertoolsGzipWrongArgumentType","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.utilities.jmespath.JsonFunctionTest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testJsonFunction","parameterTypes":[] }, {"name":"testJsonFunctionChild","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.utilities.model.Product", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setId","parameterTypes":["long"] }, {"name":"setName","parameterTypes":["java.lang.String"] }, {"name":"setPrice","parameterTypes":["double"] }] +} +] diff --git a/powertools-serialization/src/test/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization/resource-config.json b/powertools-serialization/src/test/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization/resource-config.json new file mode 100644 index 000000000..986bf5b4a --- /dev/null +++ b/powertools-serialization/src/test/resources/META-INF/native-image/software.amazon.lambda/powertools-serialization/resource-config.json @@ -0,0 +1,51 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qalb_event.json\\E" + }, { + "pattern":"\\Qamq_event.json\\E" + }, { + "pattern":"\\Qapigw_event.json\\E" + }, { + "pattern":"\\Qapigw_event_no_body.json\\E" + }, { + "pattern":"\\Qapigwv2_event.json\\E" + }, { + "pattern":"\\Qcfcr_event.json\\E" + }, { + "pattern":"\\Qcustom_event.json\\E" + }, { + "pattern":"\\Qcustom_event_gzip.json\\E" + }, { + "pattern":"\\Qcustom_event_json.json\\E" + }, { + "pattern":"\\Qcustom_event_map.json\\E" + }, { + "pattern":"\\Qcwl_event.json\\E" + }, { + "pattern":"\\Qjunit-platform.properties\\E" + }, { + "pattern":"\\Qkafip_event.json\\E" + }, { + "pattern":"\\Qkafka_event.json\\E" + }, { + "pattern":"\\Qkasip_event.json\\E" + }, { + "pattern":"\\Qkf_event.json\\E" + }, { + "pattern":"\\Qkinesis_event.json\\E" + }, { + "pattern":"\\Qrabbitmq_event.json\\E" + }, { + "pattern":"\\Qscheduled_event.json\\E" + }, { + "pattern":"\\Qsimplelogger.properties\\E" + }, { + "pattern":"\\Qsns_event.json\\E" + }, { + "pattern":"\\Qsqs_event.json\\E" + }, { + "pattern":"\\Qsqs_event_no_body.json\\E" + }]}, + "bundles":[] +} diff --git a/powertools-serialization/src/test/resources/alb_event.json b/powertools-serialization/src/test/resources/alb_event.json new file mode 100644 index 000000000..d2b0d3cda --- /dev/null +++ b/powertools-serialization/src/test/resources/alb_event.json @@ -0,0 +1,28 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a" + } + }, + "httpMethod": "GET", + "path": "/lambda", + "queryStringParameters": { + "query": "1234ABCD" + }, + "headers": { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "accept-encoding": "gzip", + "accept-language": "en-US,en;q=0.9", + "connection": "keep-alive", + "host": "lambda-alb-123578498.us-east-1.elb.amazonaws.com", + "upgrade-insecure-requests": "1", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", + "x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476", + "x-forwarded-for": "72.12.164.125", + "x-forwarded-port": "80", + "x-forwarded-proto": "http", + "x-imforwards": "20" + }, + "body": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/amq_event.json b/powertools-serialization/src/test/resources/amq_event.json new file mode 100644 index 000000000..00923d5e5 --- /dev/null +++ b/powertools-serialization/src/test/resources/amq_event.json @@ -0,0 +1,29 @@ +{ + "eventSource": "aws:mq", + "eventSourceArn": "arn:aws:mq:us-west-2:111122223333:broker:test:b-9bcfa592-423a-4942-879d-eb284b418fc8", + "messages": [ + { + "messageID": "ID:b-9bcfa592-423a-4942-879d-eb284b418fc8-1.mq.us-west-2.amazonaws.com-37557-1234520418293-4:1:1:1:1", + "messageType": "jms/text-message", + "deliveryMode": 1, + "replyTo": null, + "type": null, + "expiration": "60000", + "priority": 1, + "correlationId": "myJMSCoID", + "redelivered": false, + "destination": { + "physicalName": "testQueue" + }, + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==", + "timestamp": 1598827811958, + "brokerInTime": 1598827811958, + "brokerOutTime": 1598827811959, + "properties": { + "index": "1", + "doAlarm": "false", + "myCustomProperty": "value" + } + } + ] +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/apigw_event.json b/powertools-serialization/src/test/resources/apigw_event.json new file mode 100644 index 000000000..7758cb0bb --- /dev/null +++ b/powertools-serialization/src/test/resources/apigw_event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/powertools-serialization/src/test/resources/apigw_event_no_body.json b/powertools-serialization/src/test/resources/apigw_event_no_body.json new file mode 100644 index 000000000..f534c91a3 --- /dev/null +++ b/powertools-serialization/src/test/resources/apigw_event_no_body.json @@ -0,0 +1,61 @@ +{ + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/powertools-serialization/src/test/resources/apigwv2_event.json b/powertools-serialization/src/test/resources/apigwv2_event.json new file mode 100644 index 000000000..db4fc0f95 --- /dev/null +++ b/powertools-serialization/src/test/resources/apigwv2_event.json @@ -0,0 +1,57 @@ +{ + "version": "V2", + "routeKey": "routeKey", + "rawPath": "rawPath", + "rawQueryString": "rawQueryString", + "cookies": + ["foo", "bar"] + , + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "body": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "isBase64Encoded": false, + "requestContext": { + "routeKey": "routeKey", + "accountId": "123456789012", + "stage": "prod", + "apiId": "1234567890", + "domainName": "domainName", + "domainPrefix": "domainPrefix", + "time": "09/Apr/2015:12:34:56 +0000", + "timeEpoch": 1428582896000, + "http": { + "method": "POST", + "path": "/path/to/resource", + "protocol": "HTTP/1.1", + "sourceIp": "1.1.1.1", + "userAgent": "Chrome" + } + } +} diff --git a/powertools-serialization/src/test/resources/cfcr_event.json b/powertools-serialization/src/test/resources/cfcr_event.json new file mode 100644 index 000000000..58d054c06 --- /dev/null +++ b/powertools-serialization/src/test/resources/cfcr_event.json @@ -0,0 +1,20 @@ +{ + "RequestType": "requestType", + "ServiceToken": "serviceToken", + "ResponseUrl": "responseUrl", + "StackId": "stackId", + "RequestId": "requestId", + "LogicalResourceId": "logicalResourceId", + "ResourceType": "resourceType", + "ResourceProperties": { + "id": 1234, + "name": "product", + "price": 42 + }, + "OldResourceProperties": { + "id": 1234, + "name": "product", + "price": 40 + } +} + diff --git a/powertools-serialization/src/test/resources/custom_event.json b/powertools-serialization/src/test/resources/custom_event.json new file mode 100644 index 000000000..918cad81f --- /dev/null +++ b/powertools-serialization/src/test/resources/custom_event.json @@ -0,0 +1,12 @@ +{ + "basket": { + "products": [ + { + "id": 43242, + "name": "FooBar XY", + "price": 258 + } + ], + "hiddenProduct": "ewogICJpZCI6IDQzMjQyLAogICJuYW1lIjogIkZvb0JhciBYWSIsCiAgInByaWNlIjogMjU4Cn0=" + } +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/custom_event_gzip.json b/powertools-serialization/src/test/resources/custom_event_gzip.json new file mode 100644 index 000000000..2cf088092 --- /dev/null +++ b/powertools-serialization/src/test/resources/custom_event_gzip.json @@ -0,0 +1,13 @@ +{ + "basket": { + "products": [ + { + "id": 43242, + "name": "FooBar XY", + "price": 258 + } + ], + "hiddenProduct": "H4sIAAAAAAAA/6vmUlBQykxRslIwMTYyMdIBcfMSc1OBAkpu+flOiUUKEZFKYOGCosxkkLiRqQVXLQDnWo6bOAAAAA==", + "encodedString": "dGVzdA==" + } +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/custom_event_json.json b/powertools-serialization/src/test/resources/custom_event_json.json new file mode 100644 index 000000000..edc8fa298 --- /dev/null +++ b/powertools-serialization/src/test/resources/custom_event_json.json @@ -0,0 +1,3 @@ +{ + "body": "{\"message\": \"Lambda rocks\", \"list\":[{\"item\":\"4gh345h\", \"price\":42}, {\"item\":\"45jk6h46\", \"price\":24}]}" +} diff --git a/powertools-serialization/src/test/resources/custom_event_map.json b/powertools-serialization/src/test/resources/custom_event_map.json new file mode 100644 index 000000000..7d3f076d7 --- /dev/null +++ b/powertools-serialization/src/test/resources/custom_event_map.json @@ -0,0 +1,9 @@ +{ + "products": { + "12345": { + "id": 43242, + "name": "FooBar XY", + "price": 258 + } + } +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/cwl_event.json b/powertools-serialization/src/test/resources/cwl_event.json new file mode 100644 index 000000000..911ab1b3a --- /dev/null +++ b/powertools-serialization/src/test/resources/cwl_event.json @@ -0,0 +1,5 @@ +{ + "awslogs": { + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==" + } +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/kafip_event.json b/powertools-serialization/src/test/resources/kafip_event.json new file mode 100644 index 000000000..01196256c --- /dev/null +++ b/powertools-serialization/src/test/resources/kafip_event.json @@ -0,0 +1,14 @@ +{ + "invocationId": "arn:aws:iam::EXAMPLE", + "applicationArn": "arn:aws:kinesis:EXAMPLE", + "streamArn": "arn:aws:kinesis:EXAMPLE", + "records": [ + { + "kinesisFirehoseRecordMetadata": { + "approximateArrivalTimestamp": 1428537600 + }, + "recordId": "record-id", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=" + } + ] +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/kafka_event.json b/powertools-serialization/src/test/resources/kafka_event.json new file mode 100644 index 000000000..cf1bad615 --- /dev/null +++ b/powertools-serialization/src/test/resources/kafka_event.json @@ -0,0 +1,27 @@ +{ + "eventSource": "aws:kafka", + "eventSourceArn": "arn:aws:kafka:us-east-1:123456789012:cluster/vpc-3432434/4834-3547-3455-9872-7929", + "bootstrapServers": "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092", + "records": { + "mytopic-01": [ + { + "topic": "mytopic1", + "partition": 0, + "offset": 15, + "timestamp": 1596480920837, + "timestampType": "CREATE_TIME", + "value": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=" + } + ], + "mytopic-02": [ + { + "topic": "mytopic2", + "partition": 0, + "offset": 15, + "timestamp": 1596480920838, + "timestampType": "CREATE_TIME", + "value": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ==" + } + ] + } +} diff --git a/powertools-serialization/src/test/resources/kasip_event.json b/powertools-serialization/src/test/resources/kasip_event.json new file mode 100644 index 000000000..78bc9a3fb --- /dev/null +++ b/powertools-serialization/src/test/resources/kasip_event.json @@ -0,0 +1,17 @@ +{ + "invocationId": "arn:aws:iam::EXAMPLE", + "applicationArn": "arn:aws:kinesis:EXAMPLE", + "streamArn": "arn:aws:kinesis:EXAMPLE", + "records": [ + { + "kinesisStreamRecordMetadata": { + "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", + "partitionKey": "partitionKey-03", + "shardId": "12", + "approximateArrivalTimestamp": 1428537600 + }, + "recordId": "record-id", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=" + } + ] +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/kf_event.json b/powertools-serialization/src/test/resources/kf_event.json new file mode 100644 index 000000000..e36bc4c3f --- /dev/null +++ b/powertools-serialization/src/test/resources/kf_event.json @@ -0,0 +1,12 @@ +{ + "invocationId": "invocationIdExample", + "deliveryStreamArn": "arn:aws:kinesis:EXAMPLE", + "region": "us-east-1", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "approximateArrivalTimestamp": 1495072949453, + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==" + } + ] +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/kinesis_event.json b/powertools-serialization/src/test/resources/kinesis_event.json new file mode 100644 index 000000000..5b95ddaf4 --- /dev/null +++ b/powertools-serialization/src/test/resources/kinesis_event.json @@ -0,0 +1,38 @@ +{ + "Records": [ + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ==", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/rabbitmq_event.json b/powertools-serialization/src/test/resources/rabbitmq_event.json new file mode 100644 index 000000000..698e37143 --- /dev/null +++ b/powertools-serialization/src/test/resources/rabbitmq_event.json @@ -0,0 +1,51 @@ +{ + "eventSource": "aws:rmq", + "eventSourceArn": "arn:aws:mq:us-west-2:111122223333:broker:pizzaBroker:b-9bcfa592-423a-4942-879d-eb284b418fc8", + "rmqMessagesByQueue": { + "pizzaQueue::/": [ + { + "basicProperties": { + "contentType": "text/plain", + "contentEncoding": null, + "headers": { + "header1": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 49 + ] + }, + "header2": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 50 + ] + }, + "numberInHeader": 10 + }, + "deliveryMode": 1, + "priority": 34, + "correlationId": null, + "replyTo": null, + "expiration": "60000", + "messageId": null, + "timestamp": "Jan 1, 1970, 12:33:41 AM", + "type": null, + "userId": "AIDACKCEVSQ6C2EXAMPLE", + "appId": null, + "clusterId": null, + "bodySize": 80 + }, + "redelivered": false, + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==" + } + ] + } +} diff --git a/powertools-serialization/src/test/resources/scheduled_event.json b/powertools-serialization/src/test/resources/scheduled_event.json new file mode 100644 index 000000000..9a65f4bd4 --- /dev/null +++ b/powertools-serialization/src/test/resources/scheduled_event.json @@ -0,0 +1,12 @@ +{ + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "detail-type": "Scheduled Event", + "source": "aws.events", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "eu-central-1", + "resources": [ + "arn:aws:events:eu-central-1:123456789012:rule/my-schedule" + ], + "detail": {"id":1234, "name":"product", "price":42} +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/sns_event.json b/powertools-serialization/src/test/resources/sns_event.json new file mode 100644 index 000000000..317a657d9 --- /dev/null +++ b/powertools-serialization/src/test/resources/sns_event.json @@ -0,0 +1,27 @@ +{ + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:eu-central-1:123456789012:TopicSendToMe:e3ddc7d5-2f86-40b8-a13d-3362f94fd8dd", + "Sns": { + "Type": "Notification", + "MessageId": "dc918f50-80c6-56a2-ba33-d8a9bbf013ab", + "TopicArn": "arn:aws:sns:eu-central-1:123456789012:TopicSendToMe", + "Subject": "Test sns message", + "Message": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "Timestamp": "2020-10-08T16:06:14.656Z", + "SignatureVersion": "1", + "Signature": "UWnPpkqPAphyr+6PXzUF9++4zJcw==", + "SigningCertUrl": "https://sns.eu-central-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem", + "UnsubscribeUrl": "https://sns.eu-central-1.amazonaws.com/?Action=Unsubscribe", + "MessageAttributes": { + "name": { + "Type": "String", + "Value": "Bob" + } + } + } + } + ] +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/sqs_event.json b/powertools-serialization/src/test/resources/sqs_event.json new file mode 100644 index 000000000..d33db4b53 --- /dev/null +++ b/powertools-serialization/src/test/resources/sqs_event.json @@ -0,0 +1,40 @@ +{ + "Records": [ + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 1234,\n \"name\": \"product\",\n \"price\": 42\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "body": "{\n \"id\": 12345,\n \"name\": \"product5\",\n \"price\": 45\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/sqs_event_no_body.json b/powertools-serialization/src/test/resources/sqs_event_no_body.json new file mode 100644 index 000000000..3a313dd6b --- /dev/null +++ b/powertools-serialization/src/test/resources/sqs_event_no_body.json @@ -0,0 +1,21 @@ +{ + "Records": [ + { + "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54", + "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975706495", + "SenderId": "AROAIFU437PVZ5L2J53F5", + "ApproximateFirstReceiveTimestamp": "1601975706499" + }, + "messageAttributes": { + + }, + "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/powertools-sqs/pom.xml b/powertools-sqs/pom.xml deleted file mode 100644 index 8f73de92c..000000000 --- a/powertools-sqs/pom.xml +++ /dev/null @@ -1,112 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - - <artifactId>powertools-sqs</artifactId> - <packaging>jar</packaging> - - <parent> - <artifactId>powertools-parent</artifactId> - <groupId>software.amazon.lambda</groupId> - <version>1.10.2</version> - </parent> - - <name>AWS Lambda Powertools Java library SQS</name> - <description> - A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. - </description> - <url>https://aws.amazon.com/lambda/</url> - <issueManagement> - <system>GitHub Issues</system> - <url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url> - </issueManagement> - <scm> - <url>https://github.com/awslabs/aws-lambda-powertools-java.git</url> - </scm> - <developers> - <developer> - <name>AWS Lambda Powertools team</name> - <organization>Amazon Web Services</organization> - <organizationUrl>https://aws.amazon.com/</organizationUrl> - </developer> - </developers> - - <distributionManagement> - <snapshotRepository> - <id>ossrh</id> - <url>https://aws.oss.sonatype.org/content/repositories/snapshots</url> - </snapshotRepository> - </distributionManagement> - - <dependencies> - <dependency> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-core</artifactId> - </dependency> - <dependency> - <groupId>com.amazonaws</groupId> - <artifactId>aws-lambda-java-core</artifactId> - </dependency> - <dependency> - <groupId>com.amazonaws</groupId> - <artifactId>aws-lambda-java-events</artifactId> - </dependency> - <dependency> - <groupId>software.amazon.awssdk</groupId> - <artifactId>sqs</artifactId> - </dependency> - <dependency> - <groupId>software.amazon.awssdk</groupId> - <artifactId>s3</artifactId> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-databind</artifactId> - </dependency> - - <dependency> - <groupId>org.aspectj</groupId> - <artifactId>aspectjrt</artifactId> - </dependency> - - <!-- Test dependencies --> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-api</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-engine</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-params</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.aspectj</groupId> - <artifactId>aspectjweaver</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.assertj</groupId> - <artifactId>assertj-core</artifactId> - <scope>test</scope> - </dependency> - </dependencies> - -</project> \ No newline at end of file diff --git a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SQSBatchProcessingException.java b/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SQSBatchProcessingException.java deleted file mode 100644 index 85231a003..000000000 --- a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SQSBatchProcessingException.java +++ /dev/null @@ -1,78 +0,0 @@ -package software.amazon.lambda.powertools.sqs; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import com.amazonaws.services.lambda.runtime.events.SQSEvent; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static java.util.Collections.*; -import static java.util.stream.Collectors.joining; - -/** - * <p> - * When one or more {@link SQSMessage} fails and if any exception is thrown from {@link SqsMessageHandler#process(SQSMessage)} - * during processing of a messages, this exception is with all the details of successful and failed messages. - * </p> - * - * <p> - * This exception can be thrown form: - * <ul> - * <li>{@link SqsBatch}</li> - * <li>{@link SqsUtils#batchProcessor(SQSEvent, Class)}</li> - * <li>{@link SqsUtils#batchProcessor(SQSEvent, boolean, Class)}</li> - * <li>{@link SqsUtils#batchProcessor(SQSEvent, SqsMessageHandler)}</li> - * <li>{@link SqsUtils#batchProcessor(SQSEvent, boolean, SqsMessageHandler)}</li> - * </ul> - * </p> - */ -public class SQSBatchProcessingException extends RuntimeException { - - private final List<Exception> exceptions; - private final List<SQSMessage> failures; - private final List<Object> returnValues; - - public <T> SQSBatchProcessingException(final List<Exception> exceptions, - final List<SQSMessage> failures, - final List<T> successReturns) { - super(exceptions.stream() - .map(Throwable::toString) - .collect(joining("\n"))); - - this.exceptions = new ArrayList<>(exceptions); - this.failures = new ArrayList<>(failures); - this.returnValues = new ArrayList<>(successReturns); - } - - /** - * Details for exceptions that occurred while processing messages in {@link SqsMessageHandler#process(SQSMessage)} - * @return List of exceptions that occurred while processing messages - */ - public List<Exception> getExceptions() { - return unmodifiableList(exceptions); - } - - /** - * List of returns from {@link SqsMessageHandler#process(SQSMessage)} that were successfully processed. - * @return List of returns from successfully processed messages - */ - public List<Object> successMessageReturnValues() { - return unmodifiableList(returnValues); - } - - /** - * Details of {@link SQSMessage} that failed in {@link SqsMessageHandler#process(SQSMessage)} - * @return List of failed messages - */ - public List<SQSMessage> getFailures() { - return unmodifiableList(failures); - } - - @Override - public void printStackTrace() { - for (Exception exception : exceptions) { - exception.printStackTrace(); - } - } -} diff --git a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SqsBatch.java b/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SqsBatch.java deleted file mode 100644 index cd529ff22..000000000 --- a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SqsBatch.java +++ /dev/null @@ -1,85 +0,0 @@ -package software.amazon.lambda.powertools.sqs; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.amazonaws.services.lambda.runtime.events.SQSEvent; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.*; - -/** - * {@link SqsBatch} is used to process batch messages in {@link SQSEvent} - * - * <p> - * When using the annotation, implementation of {@link SqsMessageHandler} is required. Annotation will take care of - * calling {@link SqsMessageHandler#process(SQSMessage)} method for each {@link SQSMessage} in the received {@link SQSEvent} - * </p> - * - * <p> - * If any exception is thrown from {@link SqsMessageHandler#process(SQSMessage)} during processing of a messages, Utility - * will take care of deleting all the successful messages from SQS. When one or more single message fails processing due - * to exception thrown from {@link SqsMessageHandler#process(SQSMessage)}, Lambda execution will fail - * with {@link SQSBatchProcessingException}. - * - * If all the messages are successfully processes, No SQS messages are deleted explicitly but is rather delegated to - * Lambda execution context for deletion. - * </p> - * - * <p> - * If you want to suppress the exception even if any message in batch fails, set - * {@link SqsBatch#suppressException()} to true. By default its value is false - * </p> - * - * <p> - * If you want certain exceptions to be treated as permanent failures, i.e. exceptions where the result of retrying will - * always be a failure and want these can be immediately moved to the dead letter queue associated to the source SQS queue, - * - * you can use {@link SqsBatch#nonRetryableExceptions()} to configure such exceptions. - * Make sure function execution role has sqs:GetQueueAttributes permission on source SQS queue and sqs:SendMessage, - * sqs:SendMessageBatch permission for configured DLQ. - * - * If you want such messages to be deleted instead, set {@link SqsBatch#deleteNonRetryableMessageFromQueue()} to true. - * By default its value is false. - * - * If there is no DLQ configured on source SQS queue and {@link SqsBatch#nonRetryableExceptions()} attribute is set, if - * nonRetryableExceptions occurs from {@link SqsMessageHandler}, such exceptions will still be treated as temporary - * exceptions and the message will be moved back to source SQS queue for reprocessing. The same behaviour will occur if - * for some reason the utility is unable to move the message to the DLQ. An example of this could be because the function - * is missing the correct permissions. - * </p> - * - * <pre> - * public class SqsMessageHandler implements RequestHandler<SQSEvent, String> { - * - * {@literal @}Override - * {@literal @}{@link SqsBatch (SqsMessageHandler)} - * public String handleRequest(SQSEvent sqsEvent, Context context) { - * - * return "ok"; - * } - * - * public class DummySqsMessageHandler implements SqsMessageHandler<Object>{ - * @Override - * public Object process(SQSEvent.SQSMessage message) { - * throw new UnsupportedOperationException(); - * } - * } - * - * ... - * </pre> - * @see <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html">Amazon SQS dead-letter queues</a> - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface SqsBatch { - - Class<? extends SqsMessageHandler<Object>> value(); - - boolean suppressException() default false; - - Class<? extends Exception>[] nonRetryableExceptions() default {}; - - boolean deleteNonRetryableMessageFromQueue() default false; -} diff --git a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SqsLargeMessage.java b/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SqsLargeMessage.java deleted file mode 100644 index d96245006..000000000 --- a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SqsLargeMessage.java +++ /dev/null @@ -1,68 +0,0 @@ -package software.amazon.lambda.powertools.sqs; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * {@code SqsLargeMessage} is used to signal that the annotated method - * should be extended to handle large SQS messages which have been offloaded - * to S3 - * - * <p>{@code SqsLargeMessage} automatically retrieves and deletes messages - * which have been offloaded to S3 using the {@code amazon-sqs-java-extended-client-lib} - * client library.</p> - * - * <p>This version of the {@code SqsLargeMessage} is compatible with version - * 1.1.0+ of {@code amazon-sqs-java-extended-client-lib}.</p> - * - * <pre> - * <dependency> - * <groupId>com.amazonaws</groupId> - * <artifactId>amazon-sqs-java-extended-client-lib</artifactId> - * <version>1.1.0</version> - * </dependency> - * </pre> - * - * <p>{@code SqsLargeMessage} should be used with the handleRequest method of a class - * which implements {@code com.amazonaws.services.lambda.runtime.RequestHandler} with - * {@code com.amazonaws.services.lambda.runtime.events.SQSEvent} as the first parameter.</p> - * - * <pre> - * public class SqsMessageHandler implements RequestHandler<SQSEvent, String> { - * - * {@literal @}Override - * {@literal @}SqsLargeMessage - * public String handleRequest(SQSEvent sqsEvent, Context context) { - * - * // process messages - * - * return "ok"; - * } - * - * ... - * </pre> - * - * <p>Using the default S3 Client {@code AmazonS3 amazonS3 = AmazonS3ClientBuilder.defaultClient();} - * each record received in the SQSEvent {@code SqsLargeMessage} will checked - * to see if it's body contains a payload which has been offloaded to S3. If it - * does then {@code getObject(bucket, key)} will be called and the payload - * retrieved.</p> - * - * <p><b>Note</b>: Retreiving payloads from S3 will increase the duration of the - * Lambda function.</p> - * - * <p>If the request handler method returns then each payload will be deleted - * from S3 using {@code deleteObject(bucket, key)}</p> - * - * <p>To disable deletion of payloads setting the following annotation parameter - * {@code @SqsLargeMessage(deletePayloads=false)}</p> - * - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface SqsLargeMessage { - - boolean deletePayloads() default true; -} diff --git a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SqsMessageHandler.java b/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SqsMessageHandler.java deleted file mode 100644 index 17e37797c..000000000 --- a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SqsMessageHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package software.amazon.lambda.powertools.sqs; - -import com.amazonaws.services.lambda.runtime.events.SQSEvent; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; - -/** - * <p> - * This interface should be implemented for processing {@link SQSMessage} inside {@link SQSEvent} received by lambda - * function. - * </p> - * - * <p> - * It is required by utilities: - * <ul> - * <li>{@link SqsBatch}</li> - * <li>{@link SqsUtils#batchProcessor(SQSEvent, Class)}</li> - * <li>{@link SqsUtils#batchProcessor(SQSEvent, boolean, Class)}</li> - * <li>{@link SqsUtils#batchProcessor(SQSEvent, SqsMessageHandler)}</li> - * <li>{@link SqsUtils#batchProcessor(SQSEvent, boolean, SqsMessageHandler)}</li> - * </ul> - * </p> - * @param <R> Return value type from {@link SqsMessageHandler#process(SQSMessage)} - */ -@FunctionalInterface -public interface SqsMessageHandler<R> { - - R process(SQSMessage message); -} diff --git a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SqsUtils.java b/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SqsUtils.java deleted file mode 100644 index e8bf9a7ae..000000000 --- a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/SqsUtils.java +++ /dev/null @@ -1,545 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. - * 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 - * http://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. - * - */ -package software.amazon.lambda.powertools.sqs; - -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.sqs.SqsClient; -import software.amazon.lambda.powertools.sqs.internal.BatchContext; -import software.amazon.payloadoffloading.PayloadS3Pointer; -import software.amazon.lambda.powertools.sqs.internal.SqsLargeMessageAspect; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static software.amazon.lambda.powertools.sqs.internal.SqsLargeMessageAspect.processMessages; - -/** - * A class of helper functions to add additional functionality to {@link SQSEvent} processing. - */ -public final class SqsUtils { - private static final Logger LOG = LoggerFactory.getLogger(SqsUtils.class); - - private static final ObjectMapper objectMapper = new ObjectMapper(); - private static SqsClient client; - private static S3Client s3Client; - - private SqsUtils() { - } - - /** - * This is a utility method when you want to avoid using {@code SqsLargeMessage} annotation. - * Gives you access to enriched messages from S3 in the SQS event produced via extended client lib. - * If all the large S3 payload are successfully retrieved, it will delete them from S3 post success. - * - * @param sqsEvent Event received from SQS Extended client library - * @param messageFunction Function to execute you business logic which provides access to enriched messages from S3 when needed. - * @return Return value from the function. - */ - public static <R> R enrichedMessageFromS3(final SQSEvent sqsEvent, - final Function<List<SQSMessage>, R> messageFunction) { - return enrichedMessageFromS3(sqsEvent, true, messageFunction); - } - - /** - * This is a utility method when you want to avoid using {@code SqsLargeMessage} annotation. - * Gives you access to enriched messages from S3 in the SQS event produced via extended client lib. - * if all the large S3 payload are successfully retrieved, Control if it will delete payload from S3 post success. - * - * @param sqsEvent Event received from SQS Extended client library - * @param messageFunction Function to execute you business logic which provides access to enriched messages from S3 when needed. - * @return Return value from the function. - */ - public static <R> R enrichedMessageFromS3(final SQSEvent sqsEvent, - final boolean deleteS3Payload, - final Function<List<SQSMessage>, R> messageFunction) { - - List<SQSMessage> sqsMessages = sqsEvent.getRecords().stream() - .map(SqsUtils::clonedMessage) - .collect(Collectors.toList()); - - List<PayloadS3Pointer> s3Pointers = processMessages(sqsMessages); - - R returnValue = messageFunction.apply(sqsMessages); - - if (deleteS3Payload) { - s3Pointers.forEach(SqsLargeMessageAspect::deleteMessage); - } - - return returnValue; - } - - /** - * Provides ability to set default {@link SqsClient} to be used by utility. - * If no default configuration is provided, client is instantiated via {@link SqsClient#create()} - * - * @param client {@link SqsClient} to be used by utility - */ - public static void overrideSqsClient(SqsClient client) { - SqsUtils.client = client; - } - - /** - * By default, the S3Client is instantiated via {@link S3Client#create()}. - * This method provides the ability to override the S3Client with your own custom version. - * - * @param s3Client {@link S3Client} to be used by utility - */ - public static void overrideS3Client(S3Client s3Client) { - SqsUtils.s3Client = s3Client; - } - - /** - * This utility method is used to process each {@link SQSMessage} inside the received {@link SQSEvent} - * - * <p> - * The Utility will call {@link SqsMessageHandler#process(SQSMessage)} method for each {@link SQSMessage} - * in the received {@link SQSEvent} - * </p> - * - * </p> - * If any exception is thrown from {@link SqsMessageHandler#process(SQSMessage)} during processing of a message, - * the utility will take care of deleting all the successful messages from SQS. When one or more single message fails - * processing due to exception thrown from {@link SqsMessageHandler#process(SQSMessage)} - * {@link SQSBatchProcessingException} is thrown with all the details of successful and failed messages. - * <p> - * If all the messages are successfully processes, No SQS messages are deleted explicitly but is rather delegated to - * Lambda execution context for deletion. - * </p> - * - * <p> - * If you dont want the utility to throw {@link SQSBatchProcessingException} in case of failures but rather suppress - * it, Refer {@link SqsUtils#batchProcessor(SQSEvent, boolean, Class)} - * </p> - * - * @param event {@link SQSEvent} received by lambda function. - * @param handler Class implementing {@link SqsMessageHandler} which will be called for each message in event. - * @return List of values returned by {@link SqsMessageHandler#process(SQSMessage)} while processing each message. - * @throws SQSBatchProcessingException if some messages fail during processing. - */ - public static <R> List<R> batchProcessor(final SQSEvent event, - final Class<? extends SqsMessageHandler<R>> handler) { - return batchProcessor(event, false, handler); - } - - /** - * This utility method is used to process each {@link SQSMessage} inside the received {@link SQSEvent} - * - * <p> - * The Utility will call {@link SqsMessageHandler#process(SQSMessage)} method for each {@link SQSMessage} - * in the received {@link SQSEvent} - * </p> - * <p> - * If any exception is thrown from {@link SqsMessageHandler#process(SQSMessage)} during processing of a message, - * the utility will take care of deleting all the successful messages from SQS. When one or more single message fails - * processing due to exception thrown from {@link SqsMessageHandler#process(SQSMessage)} - * {@link SQSBatchProcessingException} is thrown with all the details of successful and failed messages. - * - * </p> - * - * <p> - * If all the messages are successfully processes, No SQS messages are deleted explicitly but is rather delegated to - * Lambda execution context for deletion. - * </p> - * - * <p> - * If you dont want the utility to throw {@link SQSBatchProcessingException} in case of failures but rather suppress - * it, Refer {@link SqsUtils#batchProcessor(SQSEvent, boolean, Class)} - * </p> - * - * <p> - * If you want certain exceptions to be treated as permanent failures, i.e. exceptions where the result of retrying will - * always be a failure and want these can be immediately moved to the dead letter queue associated to the source SQS queue, - * you can use nonRetryableExceptions parameter to configure such exceptions. - * - * Make sure function execution role has sqs:GetQueueAttributes permission on source SQS queue and sqs:SendMessage, - * sqs:SendMessageBatch permission for configured DLQ. - * - * If there is no DLQ configured on source SQS queue and {@link SqsBatch#nonRetryableExceptions()} attribute is set, if - * nonRetryableExceptions occurs from {@link SqsMessageHandler}, such exceptions will still be treated as temporary - * exceptions and the message will be moved back to source SQS queue for reprocessing. The same behaviour will occur if - * for some reason the utility is unable to move the message to the DLQ. An example of this could be because the function - * is missing the correct permissions. - * </p> - * @see <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html">Amazon SQS dead-letter queues</a> - * @param event {@link SQSEvent} received by lambda function. - * @param handler Class implementing {@link SqsMessageHandler} which will be called for each message in event. - * @param nonRetryableExceptions exception classes that are to be treated as permanent exceptions and to be moved - * to DLQ. - * @return List of values returned by {@link SqsMessageHandler#process(SQSMessage)} while processing each message. - * @throws SQSBatchProcessingException if some messages fail during processing. - */ - @SafeVarargs - public static <R> List<R> batchProcessor(final SQSEvent event, - final Class<? extends SqsMessageHandler<R>> handler, - final Class<? extends Exception>... nonRetryableExceptions) { - return batchProcessor(event, false, handler, nonRetryableExceptions); - } - - /** - * This utility method is used to process each {@link SQSMessage} inside the received {@link SQSEvent} - * - * <p> - * The Utility will call {@link SqsMessageHandler#process(SQSMessage)} method for each {@link SQSMessage} - * in the received {@link SQSEvent} - * </p> - * </p> - * If any exception is thrown from {@link SqsMessageHandler#process(SQSMessage)} during processing of a message, - * the utility will take care of deleting all the successful messages from SQS. When one or more single message fails - * processing due to exception thrown from {@link SqsMessageHandler#process(SQSMessage)} - * {@link SQSBatchProcessingException} is thrown with all the details of successful and failed messages. - * <p> - * Exception can also be suppressed if desired. - * <p> - * If all the messages are successfully processes, No SQS messages are deleted explicitly but is rather delegated to - * Lambda execution context for deletion. - * </p> - * - * @param event {@link SQSEvent} received by lambda function. - * @param suppressException if this is set to true, No {@link SQSBatchProcessingException} is thrown even on failed - * messages. - * @param handler Class implementing {@link SqsMessageHandler} which will be called for each message in event. - * @return List of values returned by {@link SqsMessageHandler#process(SQSMessage)} while processing each message. - * @throws SQSBatchProcessingException if some messages fail during processing and no suppression enabled. - */ - public static <R> List<R> batchProcessor(final SQSEvent event, - final boolean suppressException, - final Class<? extends SqsMessageHandler<R>> handler) { - - SqsMessageHandler<R> handlerInstance = instantiatedHandler(handler); - return batchProcessor(event, suppressException, handlerInstance); - } - - /** - * This utility method is used to process each {@link SQSMessage} inside the received {@link SQSEvent} - * - * <p> - * The Utility will call {@link SqsMessageHandler#process(SQSMessage)} method for each {@link SQSMessage} - * in the received {@link SQSEvent} - * </p> - * <p> - * If any exception is thrown from {@link SqsMessageHandler#process(SQSMessage)} during processing of a message, - * the utility will take care of deleting all the successful messages from SQS. When one or more single message fails - * processing due to exception thrown from {@link SqsMessageHandler#process(SQSMessage)} - * {@link SQSBatchProcessingException} is thrown with all the details of successful and failed messages. - * - * </p> - * - * <p> - * If all the messages are successfully processes, No SQS messages are deleted explicitly but is rather delegated to - * Lambda execution context for deletion. - * </p> - * - * <p> - * If you dont want the utility to throw {@link SQSBatchProcessingException} in case of failures but rather suppress - * it, Refer {@link SqsUtils#batchProcessor(SQSEvent, boolean, Class)} - * </p> - * - * <p> - * If you want certain exceptions to be treated as permanent failures, i.e. exceptions where the result of retrying will - * always be a failure and want these can be immediately moved to the dead letter queue associated to the source SQS queue, - * you can use nonRetryableExceptions parameter to configure such exceptions. - * - * Make sure function execution role has sqs:GetQueueAttributes permission on source SQS queue and sqs:SendMessage, - * sqs:SendMessageBatch permission for configured DLQ. - * - * If there is no DLQ configured on source SQS queue and {@link SqsBatch#nonRetryableExceptions()} attribute is set, if - * nonRetryableExceptions occurs from {@link SqsMessageHandler}, such exceptions will still be treated as temporary - * exceptions and the message will be moved back to source SQS queue for reprocessing. The same behaviour will occur if - * for some reason the utility is unable to move the message to the DLQ. An example of this could be because the function - * is missing the correct permissions. - * </p> - * @see <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html">Amazon SQS dead-letter queues</a> - * - * @param event {@link SQSEvent} received by lambda function. - * @param suppressException if this is set to true, No {@link SQSBatchProcessingException} is thrown even on failed - * messages. - * @param handler Class implementing {@link SqsMessageHandler} which will be called for each message in event. - * @param nonRetryableExceptions exception classes that are to be treated as permanent exceptions and to be moved - * to DLQ. - * @return List of values returned by {@link SqsMessageHandler#process(SQSMessage)} while processing each message. - * @throws SQSBatchProcessingException if some messages fail during processing. - */ - @SafeVarargs - public static <R> List<R> batchProcessor(final SQSEvent event, - final boolean suppressException, - final Class<? extends SqsMessageHandler<R>> handler, - final Class<? extends Exception>... nonRetryableExceptions) { - - SqsMessageHandler<R> handlerInstance = instantiatedHandler(handler); - return batchProcessor(event, suppressException, handlerInstance, false, nonRetryableExceptions); - } - - /** - * This utility method is used to process each {@link SQSMessage} inside the received {@link SQSEvent} - * - * <p> - * The Utility will call {@link SqsMessageHandler#process(SQSMessage)} method for each {@link SQSMessage} - * in the received {@link SQSEvent} - * </p> - * - * <p> - * If any exception is thrown from {@link SqsMessageHandler#process(SQSMessage)} during processing of a message, - * the utility will take care of deleting all the successful messages from SQS. When one or more single message fails - * processing due to exception thrown from {@link SqsMessageHandler#process(SQSMessage)} - * {@link SQSBatchProcessingException} is thrown with all the details of successful and failed messages. - * - * </p> - * - * <p> - * If all the messages are successfully processes, No SQS messages are deleted explicitly but is rather delegated to - * Lambda execution context for deletion. - * </p> - * - * <p> - * If you dont want the utility to throw {@link SQSBatchProcessingException} in case of failures but rather suppress - * it, Refer {@link SqsUtils#batchProcessor(SQSEvent, boolean, Class)} - * </p> - * - * <p> - * If you want certain exceptions to be treated as permanent failures, i.e. exceptions where the result of retrying will - * always be a failure and want these can be immediately moved to the dead letter queue associated to the source SQS queue, - * you can use nonRetryableExceptions parameter to configure such exceptions. - * - * Make sure function execution role has sqs:GetQueueAttributes permission on source SQS queue and sqs:SendMessage, - * sqs:SendMessageBatch permission for configured DLQ. - * - * If you want such messages to be deleted instead, set deleteNonRetryableMessageFromQueue to true. - * - * If there is no DLQ configured on source SQS queue and {@link SqsBatch#nonRetryableExceptions()} attribute is set, if - * nonRetryableExceptions occurs from {@link SqsMessageHandler}, such exceptions will still be treated as temporary - * exceptions and the message will be moved back to source SQS queue for reprocessing. The same behaviour will occur if - * for some reason the utility is unable to move the message to the DLQ. An example of this could be because the function - * is missing the correct permissions. - * </p> - * @see <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html">Amazon SQS dead-letter queues</a> - * @param event {@link SQSEvent} received by lambda function. - * @param suppressException if this is set to true, No {@link SQSBatchProcessingException} is thrown even on failed - * messages. - * @param handler Class implementing {@link SqsMessageHandler} which will be called for each message in event. - * @param deleteNonRetryableMessageFromQueue If messages with nonRetryableExceptions are to be deleted from SQS queue. - * @param nonRetryableExceptions exception classes that are to be treated as permanent exceptions and to be moved - * to DLQ. - * @return List of values returned by {@link SqsMessageHandler#process(SQSMessage)} while processing each message. - * @throws SQSBatchProcessingException if some messages fail during processing. - */ - @SafeVarargs - public static <R> List<R> batchProcessor(final SQSEvent event, - final boolean suppressException, - final Class<? extends SqsMessageHandler<R>> handler, - final boolean deleteNonRetryableMessageFromQueue, - final Class<? extends Exception>... nonRetryableExceptions) { - - SqsMessageHandler<R> handlerInstance = instantiatedHandler(handler); - return batchProcessor(event, suppressException, handlerInstance, deleteNonRetryableMessageFromQueue, nonRetryableExceptions); - } - - /** - * This utility method is used to process each {@link SQSMessage} inside the received {@link SQSEvent} - * - * <p> - * The Utility will call {@link SqsMessageHandler#process(SQSMessage)} method for each {@link SQSMessage} - * in the received {@link SQSEvent} - * </p> - * - * </p> - * If any exception is thrown from {@link SqsMessageHandler#process(SQSMessage)} during processing of a messages, - * Utility will take care of deleting all the successful messages from SQS. When one or more single message fails - * processing due to exception thrown from {@link SqsMessageHandler#process(SQSMessage)} - * {@link SQSBatchProcessingException} is thrown with all the details of successful and failed messages. - * <p> - * If all the messages are successfully processes, No SQS messages are deleted explicitly but is rather delegated to - * Lambda execution context for deletion. - * </p> - * - * <p> - * If you dont want the utility to throw {@link SQSBatchProcessingException} in case of failures but rather suppress - * it, Refer {@link SqsUtils#batchProcessor(SQSEvent, boolean, SqsMessageHandler)} - * </p> - * - * @param event {@link SQSEvent} received by lambda function. - * @param handler Instance of class implementing {@link SqsMessageHandler} which will be called for each message in event. - * @return List of values returned by {@link SqsMessageHandler#process(SQSMessage)} while processing each message- - * @throws SQSBatchProcessingException if some messages fail during processing. - */ - public static <R> List<R> batchProcessor(final SQSEvent event, - final SqsMessageHandler<R> handler) { - return batchProcessor(event, false, handler); - } - - - /** - * This utility method is used to process each {@link SQSMessage} inside the received {@link SQSEvent} - * - * <p> - * The Utility will call {@link SqsMessageHandler#process(SQSMessage)} method for each {@link SQSMessage} - * in the received {@link SQSEvent} - * </p> - * - * <p> - * If any exception is thrown from {@link SqsMessageHandler#process(SQSMessage)} during processing of a message, - * the utility will take care of deleting all the successful messages from SQS. When one or more single message fails - * processing due to exception thrown from {@link SqsMessageHandler#process(SQSMessage)} - * {@link SQSBatchProcessingException} is thrown with all the details of successful and failed messages. - * - * </p> - * - * <p> - * If all the messages are successfully processes, No SQS messages are deleted explicitly but is rather delegated to - * Lambda execution context for deletion. - * </p> - * - * <p> - * If you dont want the utility to throw {@link SQSBatchProcessingException} in case of failures but rather suppress - * it, Refer {@link SqsUtils#batchProcessor(SQSEvent, boolean, Class)} - * </p> - * - * <p> - * If you want certain exceptions to be treated as permanent failures, i.e. exceptions where the result of retrying will - * always be a failure and want these can be immediately moved to the dead letter queue associated to the source SQS queue, - * you can use nonRetryableExceptions parameter to configure such exceptions. - * - * Make sure function execution role has sqs:GetQueueAttributes permission on source SQS queue and sqs:SendMessage, - * sqs:SendMessageBatch permission for configured DLQ. - * - * If there is no DLQ configured on source SQS queue and {@link SqsBatch#nonRetryableExceptions()} attribute is set, if - * nonRetryableExceptions occurs from {@link SqsMessageHandler}, such exceptions will still be treated as temporary - * exceptions and the message will be moved back to source SQS queue for reprocessing.The same behaviour will occur if - * for some reason the utility is unable to moved the message to the DLQ. An example of this could be because the function - * is missing the correct permissions. - * </p> - * @see <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html">Amazon SQS dead-letter queues</a> - * @param event {@link SQSEvent} received by lambda function. - * @param handler Instance of class implementing {@link SqsMessageHandler} which will be called for each message in event. - * @param nonRetryableExceptions exception classes that are to be treated as permanent exceptions and to be moved - * to DLQ. - * @return List of values returned by {@link SqsMessageHandler#process(SQSMessage)} while processing each message. - * @throws SQSBatchProcessingException if some messages fail during processing. - */ - @SafeVarargs - public static <R> List<R> batchProcessor(final SQSEvent event, - final SqsMessageHandler<R> handler, - final Class<? extends Exception>... nonRetryableExceptions) { - return batchProcessor(event, false, handler, false, nonRetryableExceptions); - } - - - /** - * This utility method is used to process each {@link SQSMessage} inside the received {@link SQSEvent} - * - * <p> - * The Utility will call {@link SqsMessageHandler#process(SQSMessage)} method for each {@link SQSMessage} - * in the received {@link SQSEvent} - * </p> - * - * </p> - * If any exception is thrown from {@link SqsMessageHandler#process(SQSMessage)} during processing of a messages, - * the utility will take care of deleting all the successful messages from SQS. When one or more single message fails - * processing due to exception thrown from {@link SqsMessageHandler#process(SQSMessage)} - * {@link SQSBatchProcessingException} is thrown with all the details of successful and failed messages. - * <p> - * Exception can also be suppressed if desired. - * <p> - * If all the messages are successfully processes, No SQS messages are deleted explicitly but is rather delegated to - * Lambda execution context for deletion. - * </p> - * - * @param event {@link SQSEvent} received by lambda function. - * @param suppressException if this is set to true, No {@link SQSBatchProcessingException} is thrown even on failed - * messages. - * @param handler Instance of class implementing {@link SqsMessageHandler} which will be called for each message in event. - * @return List of values returned by {@link SqsMessageHandler#process(SQSMessage)} while processing each message. - * @throws SQSBatchProcessingException if some messages fail during processing and no suppression enabled. - */ - public static <R> List<R> batchProcessor(final SQSEvent event, - final boolean suppressException, - final SqsMessageHandler<R> handler) { - return batchProcessor(event, suppressException, handler, false); - - } - - @SafeVarargs - public static <R> List<R> batchProcessor(final SQSEvent event, - final boolean suppressException, - final SqsMessageHandler<R> handler, - final boolean deleteNonRetryableMessageFromQueue, - final Class<? extends Exception>... nonRetryableExceptions) { - final List<R> handlerReturn = new ArrayList<>(); - - if(client == null) { - client = SqsClient.create(); - } - - BatchContext batchContext = new BatchContext(client); - - for (SQSMessage message : event.getRecords()) { - try { - handlerReturn.add(handler.process(message)); - batchContext.addSuccess(message); - } catch (Exception e) { - batchContext.addFailure(message, e); - } - } - - batchContext.processSuccessAndHandleFailed(handlerReturn, suppressException, deleteNonRetryableMessageFromQueue, nonRetryableExceptions); - - return handlerReturn; - } - - private static <R> SqsMessageHandler<R> instantiatedHandler(final Class<? extends SqsMessageHandler<R>> handler) { - - try { - if (null == handler.getDeclaringClass()) { - return handler.getDeclaredConstructor().newInstance(); - } - - final Constructor<? extends SqsMessageHandler<R>> constructor = handler.getDeclaredConstructor(handler.getDeclaringClass()); - constructor.setAccessible(true); - return constructor.newInstance(handler.getDeclaringClass().getDeclaredConstructor().newInstance()); - } catch (Exception e) { - LOG.error("Failed creating handler instance", e); - throw new RuntimeException("Unexpected error occurred. Please raise issue at " + - "https://github.com/awslabs/aws-lambda-powertools-java/issues", e); - } - } - - private static SQSMessage clonedMessage(final SQSMessage sqsMessage) { - try { - return objectMapper - .readValue(objectMapper.writeValueAsString(sqsMessage), SQSMessage.class); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - - public static ObjectMapper objectMapper() { - return objectMapper; - } - - public static S3Client s3Client() { - if(null == s3Client) { - SqsUtils.s3Client = S3Client.create(); - } - - return s3Client; - } -} diff --git a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/internal/BatchContext.java b/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/internal/BatchContext.java deleted file mode 100644 index ff9add984..000000000 --- a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/internal/BatchContext.java +++ /dev/null @@ -1,240 +0,0 @@ -package software.amazon.lambda.powertools.sqs.internal; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.stream.IntStream; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.sqs.SqsClient; -import software.amazon.awssdk.services.sqs.model.DeleteMessageBatchRequest; -import software.amazon.awssdk.services.sqs.model.DeleteMessageBatchRequestEntry; -import software.amazon.awssdk.services.sqs.model.DeleteMessageBatchResponse; -import software.amazon.awssdk.services.sqs.model.GetQueueAttributesRequest; -import software.amazon.awssdk.services.sqs.model.GetQueueAttributesResponse; -import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; -import software.amazon.awssdk.services.sqs.model.QueueAttributeName; -import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequest; -import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequestEntry; -import software.amazon.awssdk.services.sqs.model.SendMessageBatchResponse; -import software.amazon.lambda.powertools.sqs.SQSBatchProcessingException; -import software.amazon.lambda.powertools.sqs.SqsUtils; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static java.lang.String.format; -import static java.util.Optional.ofNullable; -import static java.util.stream.Collectors.toList; - -public final class BatchContext { - private static final Logger LOG = LoggerFactory.getLogger(BatchContext.class); - private static final Map<String, String> QUEUE_ARN_TO_DLQ_URL_MAPPING = new HashMap<>(); - - private final Map<SQSMessage, Exception> messageToException = new HashMap<>(); - private final List<SQSMessage> success = new ArrayList<>(); - - private final SqsClient client; - - public BatchContext(SqsClient client) { - this.client = client; - } - - public void addSuccess(SQSMessage event) { - success.add(event); - } - - public void addFailure(SQSMessage event, Exception e) { - messageToException.put(event, e); - } - - @SafeVarargs - public final <T> void processSuccessAndHandleFailed(final List<T> successReturns, - final boolean suppressException, - final boolean deleteNonRetryableMessageFromQueue, - final Class<? extends Exception>... nonRetryableExceptions) { - if (hasFailures()) { - - List<Exception> exceptions = new ArrayList<>(); - List<SQSMessage> failedMessages = new ArrayList<>(); - Map<SQSMessage, Exception> nonRetryableMessageToException = new HashMap<>(); - - if (nonRetryableExceptions.length == 0) { - exceptions.addAll(messageToException.values()); - failedMessages.addAll(messageToException.keySet()); - } else { - messageToException.forEach((sqsMessage, exception) -> { - boolean nonRetryableException = isNonRetryableException(exception, nonRetryableExceptions); - - if (nonRetryableException) { - nonRetryableMessageToException.put(sqsMessage, exception); - } else { - exceptions.add(exception); - failedMessages.add(sqsMessage); - } - }); - } - - List<SQSMessage> messagesToBeDeleted = new ArrayList<>(success); - - if (!nonRetryableMessageToException.isEmpty() && deleteNonRetryableMessageFromQueue) { - messagesToBeDeleted.addAll(nonRetryableMessageToException.keySet()); - } else if (!nonRetryableMessageToException.isEmpty()) { - - boolean isMovedToDlq = moveNonRetryableMessagesToDlqIfConfigured(nonRetryableMessageToException); - - if (!isMovedToDlq) { - exceptions.addAll(nonRetryableMessageToException.values()); - failedMessages.addAll(nonRetryableMessageToException.keySet()); - } - } - - deleteMessagesFromQueue(messagesToBeDeleted); - - processFailedMessages(successReturns, suppressException, exceptions, failedMessages); - } - } - - private <T> void processFailedMessages(List<T> successReturns, - boolean suppressException, - List<Exception> exceptions, - List<SQSMessage> failedMessages) { - if (failedMessages.isEmpty()) { - return; - } - - if (suppressException) { - List<String> messageIds = failedMessages.stream(). - map(SQSMessage::getMessageId) - .collect(toList()); - - LOG.debug(format("[%s] records failed processing, but exceptions are suppressed. " + - "Failed messages %s", failedMessages.size(), messageIds)); - } else { - throw new SQSBatchProcessingException(exceptions, failedMessages, successReturns); - } - } - - private boolean isNonRetryableException(Exception exception, Class<? extends Exception>[] nonRetryableExceptions) { - return Arrays.stream(nonRetryableExceptions) - .anyMatch(aClass -> aClass.isInstance(exception)); - } - - private boolean moveNonRetryableMessagesToDlqIfConfigured(Map<SQSMessage, Exception> nonRetryableMessageToException) { - Optional<String> dlqUrl = fetchDlqUrl(nonRetryableMessageToException); - - if (!dlqUrl.isPresent()) { - return false; - } - - List<SendMessageBatchRequestEntry> dlqMessages = nonRetryableMessageToException.keySet().stream() - .map(sqsMessage -> { - Map<String, MessageAttributeValue> messageAttributesMap = new HashMap<>(); - - sqsMessage.getMessageAttributes().forEach((s, messageAttribute) -> { - MessageAttributeValue.Builder builder = MessageAttributeValue.builder(); - - builder - .dataType(messageAttribute.getDataType()) - .stringValue(messageAttribute.getStringValue()); - - if (null != messageAttribute.getBinaryValue()) { - builder.binaryValue(SdkBytes.fromByteBuffer(messageAttribute.getBinaryValue())); - } - - messageAttributesMap.put(s, builder.build()); - }); - - return SendMessageBatchRequestEntry.builder() - .messageBody(sqsMessage.getBody()) - .id(sqsMessage.getMessageId()) - .messageAttributes(messageAttributesMap) - .build(); - }) - .collect(toList()); - - batchRequest(dlqMessages, 10, entriesToSend -> { - - SendMessageBatchResponse sendMessageBatchResponse = client.sendMessageBatch(SendMessageBatchRequest.builder() - .entries(entriesToSend) - .queueUrl(dlqUrl.get()) - .build()); - - LOG.debug("Response from send batch message to DLQ request {}", sendMessageBatchResponse); - }); - - return true; - } - - - private Optional<String> fetchDlqUrl(Map<SQSMessage, Exception> nonRetryableMessageToException) { - return nonRetryableMessageToException.keySet().stream() - .findFirst() - .map(sqsMessage -> QUEUE_ARN_TO_DLQ_URL_MAPPING.computeIfAbsent(sqsMessage.getEventSourceArn(), sourceArn -> { - String queueUrl = url(sourceArn); - - GetQueueAttributesResponse queueAttributes = client.getQueueAttributes(GetQueueAttributesRequest.builder() - .attributeNames(QueueAttributeName.REDRIVE_POLICY) - .queueUrl(queueUrl) - .build()); - - return ofNullable(queueAttributes.attributes().get(QueueAttributeName.REDRIVE_POLICY)) - .map(policy -> { - try { - return SqsUtils.objectMapper().readTree(policy); - } catch (JsonProcessingException e) { - LOG.debug("Unable to parse Re drive policy for queue {}. Even if DLQ exists, failed messages will be send back to main queue.", queueUrl, e); - return null; - } - }) - .map(node -> node.get("deadLetterTargetArn")) - .map(JsonNode::asText) - .map(this::url) - .orElse(null); - })); - } - - private boolean hasFailures() { - return !messageToException.isEmpty(); - } - - private void deleteMessagesFromQueue(final List<SQSMessage> messages) { - if (!messages.isEmpty()) { - - List<DeleteMessageBatchRequestEntry> entries = messages.stream().map(m -> DeleteMessageBatchRequestEntry.builder() - .id(m.getMessageId()) - .receiptHandle(m.getReceiptHandle()) - .build()).collect(toList()); - - batchRequest(entries, 10, entriesToDelete -> { - DeleteMessageBatchRequest request = DeleteMessageBatchRequest.builder() - .queueUrl(url(messages.get(0).getEventSourceArn())) - .entries(entriesToDelete) - .build(); - - DeleteMessageBatchResponse deleteMessageBatchResponse = client.deleteMessageBatch(request); - - LOG.debug("Response from delete request {}", deleteMessageBatchResponse); - }); - } - } - - private <T> void batchRequest(final List<T> listOFEntries, - final int size, - final Consumer<List<T>> batchLogic) { - IntStream.range(0, listOFEntries.size()) - .filter(index -> index % size == 0) - .mapToObj(index -> listOFEntries.subList(index, Math.min(index + size, listOFEntries.size()))) - .forEach(batchLogic); - } - - private String url(String queueArn) { - String[] arnArray = queueArn.split(":"); - return String.format("https://sqs.%s.amazonaws.com/%s/%s", arnArray[3], arnArray[4], arnArray[5]); - } -} diff --git a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/internal/SqsLargeMessageAspect.java b/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/internal/SqsLargeMessageAspect.java deleted file mode 100644 index 072d903d0..000000000 --- a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/internal/SqsLargeMessageAspect.java +++ /dev/null @@ -1,145 +0,0 @@ -package software.amazon.lambda.powertools.sqs.internal; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import software.amazon.awssdk.core.ResponseInputStream; -import software.amazon.awssdk.core.exception.SdkClientException; -import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectResponse; -import software.amazon.awssdk.services.s3.model.S3Exception; -import software.amazon.awssdk.utils.IoUtils; -import software.amazon.lambda.powertools.sqs.SqsLargeMessage; -import software.amazon.payloadoffloading.PayloadS3Pointer; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static java.lang.String.format; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isHandlerMethod; -import static software.amazon.lambda.powertools.sqs.SqsUtils.s3Client; - -@Aspect -public class SqsLargeMessageAspect { - - private static final Logger LOG = LoggerFactory.getLogger(SqsLargeMessageAspect.class); - - @SuppressWarnings({"EmptyMethod"}) - @Pointcut("@annotation(sqsLargeMessage)") - public void callAt(SqsLargeMessage sqsLargeMessage) { - } - - @Around(value = "callAt(sqsLargeMessage) && execution(@SqsLargeMessage * *.*(..))", argNames = "pjp,sqsLargeMessage") - public Object around(ProceedingJoinPoint pjp, - SqsLargeMessage sqsLargeMessage) throws Throwable { - Object[] proceedArgs = pjp.getArgs(); - - if (isHandlerMethod(pjp) - && placedOnSqsEventRequestHandler(pjp)) { - List<PayloadS3Pointer> pointersToDelete = rewriteMessages((SQSEvent) proceedArgs[0]); - - Object proceed = pjp.proceed(proceedArgs); - - if (sqsLargeMessage.deletePayloads()) { - pointersToDelete.forEach(SqsLargeMessageAspect::deleteMessage); - } - return proceed; - } - - return pjp.proceed(proceedArgs); - } - - private List<PayloadS3Pointer> rewriteMessages(SQSEvent sqsEvent) { - List<SQSMessage> records = sqsEvent.getRecords(); - return processMessages(records); - } - - public static List<PayloadS3Pointer> processMessages(final List<SQSMessage> records) { - List<PayloadS3Pointer> s3Pointers = new ArrayList<>(); - for (SQSMessage sqsMessage : records) { - if (isBodyLargeMessagePointer(sqsMessage.getBody())) { - - PayloadS3Pointer s3Pointer = PayloadS3Pointer.fromJson(sqsMessage.getBody()) - .orElseThrow(() -> new FailedProcessingLargePayloadException(format("Failed processing SQS body to extract S3 details. [ %s ].", sqsMessage.getBody()))); - - ResponseInputStream<GetObjectResponse> s3Object = callS3Gracefully(s3Pointer, pointer -> { - ResponseInputStream<GetObjectResponse> response = s3Client().getObject(GetObjectRequest.builder() - .bucket(pointer.getS3BucketName()) - .key(pointer.getS3Key()) - .build()); - - LOG.debug("Object downloaded with key: " + s3Pointer.getS3Key()); - return response; - }); - - sqsMessage.setBody(readStringFromS3Object(s3Object, s3Pointer)); - s3Pointers.add(s3Pointer); - } - } - - return s3Pointers; - } - - private static boolean isBodyLargeMessagePointer(String record) { - return record.startsWith("[\"software.amazon.payloadoffloading.PayloadS3Pointer\""); - } - - private static String readStringFromS3Object(ResponseInputStream<GetObjectResponse> response, - PayloadS3Pointer s3Pointer) { - try (ResponseInputStream<GetObjectResponse> content = response) { - return IoUtils.toUtf8String(content); - } catch (IOException e) { - LOG.error("Error converting S3 object to String", e); - throw new FailedProcessingLargePayloadException(format("Failed processing S3 record with [Bucket Name: %s Bucket Key: %s]", s3Pointer.getS3BucketName(), s3Pointer.getS3Key()), e); - } - } - - public static void deleteMessage(PayloadS3Pointer s3Pointer) { - callS3Gracefully(s3Pointer, pointer -> { - s3Client().deleteObject(DeleteObjectRequest.builder() - .bucket(pointer.getS3BucketName()) - .key(pointer.getS3Key()) - .build()); - LOG.info("Message deleted from S3: " + s3Pointer.toJson()); - return null; - }); - } - - private static <R> R callS3Gracefully(final PayloadS3Pointer pointer, - final Function<PayloadS3Pointer, R> function) { - try { - return function.apply(pointer); - } catch (S3Exception e) { - LOG.error("A service exception", e); - throw new FailedProcessingLargePayloadException(format("Failed processing S3 record with [Bucket Name: %s Bucket Key: %s]", pointer.getS3BucketName(), pointer.getS3Key()), e); - } catch (SdkClientException e) { - LOG.error("Some sort of client exception", e); - throw new FailedProcessingLargePayloadException(format("Failed processing S3 record with [Bucket Name: %s Bucket Key: %s]", pointer.getS3BucketName(), pointer.getS3Key()), e); - } - } - - public static boolean placedOnSqsEventRequestHandler(ProceedingJoinPoint pjp) { - return pjp.getArgs().length == 2 - && pjp.getArgs()[0] instanceof SQSEvent - && pjp.getArgs()[1] instanceof Context; - } - - public static class FailedProcessingLargePayloadException extends RuntimeException { - public FailedProcessingLargePayloadException(String message, Throwable cause) { - super(message, cause); - } - - public FailedProcessingLargePayloadException(String message) { - super(message); - } - } -} diff --git a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/internal/SqsMessageBatchProcessorAspect.java b/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/internal/SqsMessageBatchProcessorAspect.java deleted file mode 100644 index 73e91c3a7..000000000 --- a/powertools-sqs/src/main/java/software/amazon/lambda/powertools/sqs/internal/SqsMessageBatchProcessorAspect.java +++ /dev/null @@ -1,41 +0,0 @@ -package software.amazon.lambda.powertools.sqs.internal; - -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import software.amazon.lambda.powertools.sqs.SqsBatch; - -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isHandlerMethod; -import static software.amazon.lambda.powertools.sqs.SqsUtils.batchProcessor; -import static software.amazon.lambda.powertools.sqs.internal.SqsLargeMessageAspect.placedOnSqsEventRequestHandler; - -@Aspect -public class SqsMessageBatchProcessorAspect { - - @SuppressWarnings({"EmptyMethod"}) - @Pointcut("@annotation(sqsBatch)") - public void callAt(SqsBatch sqsBatch) { - } - - @Around(value = "callAt(sqsBatch) && execution(@SqsBatch * *.*(..))", argNames = "pjp,sqsBatch") - public Object around(ProceedingJoinPoint pjp, - SqsBatch sqsBatch) throws Throwable { - Object[] proceedArgs = pjp.getArgs(); - - if (isHandlerMethod(pjp) - && placedOnSqsEventRequestHandler(pjp)) { - - SQSEvent sqsEvent = (SQSEvent) proceedArgs[0]; - - batchProcessor(sqsEvent, - sqsBatch.suppressException(), - sqsBatch.value(), - sqsBatch.deleteNonRetryableMessageFromQueue(), - sqsBatch.nonRetryableExceptions()); - } - - return pjp.proceed(proceedArgs); - } -} diff --git a/powertools-sqs/src/main/java/software/amazon/payloadoffloading/PayloadS3Pointer.java b/powertools-sqs/src/main/java/software/amazon/payloadoffloading/PayloadS3Pointer.java deleted file mode 100644 index 078b9a773..000000000 --- a/powertools-sqs/src/main/java/software/amazon/payloadoffloading/PayloadS3Pointer.java +++ /dev/null @@ -1,59 +0,0 @@ -package software.amazon.payloadoffloading; - -import java.util.Optional; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.databind.SerializationFeature; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static java.util.Optional.empty; -import static java.util.Optional.ofNullable; - -public class PayloadS3Pointer { - private static final Logger LOG = LoggerFactory.getLogger(PayloadS3Pointer.class); - private static final ObjectMapper objectMapper = new ObjectMapper(); - - private String s3BucketName; - private String s3Key; - - static { - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); - objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); - } - - private PayloadS3Pointer() { - - } - - public String getS3BucketName() { - return this.s3BucketName; - } - - public String getS3Key() { - return this.s3Key; - } - - public static Optional<PayloadS3Pointer> fromJson(String s3PointerJson) { - try { - return ofNullable(objectMapper.readValue(s3PointerJson, PayloadS3Pointer.class)); - } catch (Exception e) { - LOG.error("Failed to read the S3 object pointer from given string.", e); - return empty(); - } - } - - public Optional<String> toJson() { - try { - ObjectWriter objectWriter = objectMapper.writer(); - return ofNullable(objectWriter.writeValueAsString(this)); - - } catch (Exception e) { - LOG.error("Failed to convert S3 object pointer to text.", e); - return empty(); - } - } -} diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SampleSqsHandler.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SampleSqsHandler.java deleted file mode 100644 index d48cded5f..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SampleSqsHandler.java +++ /dev/null @@ -1,12 +0,0 @@ -package software.amazon.lambda.powertools.sqs; - -import com.amazonaws.services.lambda.runtime.events.SQSEvent; - -public class SampleSqsHandler implements SqsMessageHandler<Object> { - private int counter; - - @Override - public String process(SQSEvent.SQSMessage message) { - return String.valueOf(counter++); - } -} diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SqsUtilsBatchProcessorTest.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SqsUtilsBatchProcessorTest.java deleted file mode 100644 index 0d1c9c35a..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SqsUtilsBatchProcessorTest.java +++ /dev/null @@ -1,361 +0,0 @@ -package software.amazon.lambda.powertools.sqs; - -import java.io.IOException; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Function; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.ArgumentCaptor; -import software.amazon.awssdk.services.sqs.SqsClient; -import software.amazon.awssdk.services.sqs.model.DeleteMessageBatchRequest; -import software.amazon.awssdk.services.sqs.model.GetQueueAttributesRequest; -import software.amazon.awssdk.services.sqs.model.GetQueueAttributesResponse; -import software.amazon.awssdk.services.sqs.model.QueueAttributeName; -import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequest; -import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequestEntry; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; -import static software.amazon.lambda.powertools.sqs.SqsUtils.batchProcessor; -import static software.amazon.lambda.powertools.sqs.SqsUtils.overrideSqsClient; - -class SqsUtilsBatchProcessorTest { - - private static final SqsClient sqsClient = mock(SqsClient.class); - private static final SqsClient interactionClient = mock(SqsClient.class); - private static final ObjectMapper MAPPER = new ObjectMapper(); - private SQSEvent event; - - @BeforeEach - void setUp() throws IOException { - reset(sqsClient, interactionClient); - event = MAPPER.readValue(this.getClass().getResource("/sampleSqsBatchEvent.json"), SQSEvent.class); - overrideSqsClient(sqsClient); - } - - @Test - void shouldBatchProcessAndNotDeleteMessagesWhenAllSuccess() { - List<String> returnValues = batchProcessor(event, false, (message) -> { - interactionClient.listQueues(); - return "Success"; - }); - - assertThat(returnValues) - .hasSize(2) - .containsExactly("Success", "Success"); - - verify(interactionClient, times(2)).listQueues(); - verifyNoInteractions(sqsClient); - } - - @ParameterizedTest - @ValueSource(classes = {SampleInnerSqsHandler.class, SampleSqsHandler.class}) - void shouldBatchProcessViaClassAndNotDeleteMessagesWhenAllSuccess(Class<? extends SqsMessageHandler<String>> handler) { - List<String> returnValues = batchProcessor(event, handler); - - assertThat(returnValues) - .hasSize(2) - .containsExactly("0", "1"); - - verifyNoInteractions(sqsClient); - } - - @Test - void shouldBatchProcessAndDeleteSuccessMessageOnPartialFailures() { - String failedId = "2e1424d4-f796-459a-8184-9c92662be6da"; - - SqsMessageHandler<String> failedHandler = (message) -> { - if (failedId.equals(message.getMessageId())) { - throw new RuntimeException("Failed processing"); - } - - interactionClient.listQueues(); - return "Success"; - }; - - assertThatExceptionOfType(SQSBatchProcessingException.class) - .isThrownBy(() -> batchProcessor(event, failedHandler)) - .satisfies(e -> { - - assertThat(e.successMessageReturnValues()) - .hasSize(1) - .contains("Success"); - - assertThat(e.getFailures()) - .hasSize(1) - .extracting("messageId") - .contains(failedId); - - assertThat(e.getExceptions()) - .hasSize(1) - .extracting("detailMessage") - .contains("Failed processing"); - }); - - verify(interactionClient).listQueues(); - - ArgumentCaptor<DeleteMessageBatchRequest> captor = ArgumentCaptor.forClass(DeleteMessageBatchRequest.class); - verify(sqsClient).deleteMessageBatch(captor.capture()); - - assertThat(captor.getValue()) - .hasFieldOrPropertyWithValue("queueUrl", "https://sqs.us-east-2.amazonaws.com/123456789012/my-queue"); - } - - @Test - void shouldBatchProcessAndFullFailuresInBatch() { - SqsMessageHandler<String> failedHandler = (message) -> { - throw new RuntimeException(message.getMessageId()); - }; - - assertThatExceptionOfType(SQSBatchProcessingException.class) - .isThrownBy(() -> batchProcessor(event, failedHandler)) - .satisfies(e -> { - - assertThat(e.successMessageReturnValues()) - .isEmpty(); - - assertThat(e.getFailures()) - .hasSize(2) - .extracting("messageId") - .containsExactly("059f36b4-87a3-44ab-83d2-661975830a7d", - "2e1424d4-f796-459a-8184-9c92662be6da"); - - assertThat(e.getExceptions()) - .hasSize(2) - .extracting("detailMessage") - .containsExactly("059f36b4-87a3-44ab-83d2-661975830a7d", - "2e1424d4-f796-459a-8184-9c92662be6da"); - }); - - verifyNoInteractions(sqsClient); - } - - @Test - void shouldBatchProcessViaClassAndDeleteSuccessMessageOnPartialFailures() { - assertThatExceptionOfType(SQSBatchProcessingException.class) - .isThrownBy(() -> batchProcessor(event, FailureSampleInnerSqsHandler.class)) - .satisfies(e -> { - - assertThat(e.successMessageReturnValues()) - .hasSize(1) - .contains("Success"); - - assertThat(e.getFailures()) - .hasSize(1) - .extracting("messageId") - .contains("2e1424d4-f796-459a-8184-9c92662be6da"); - - assertThat(e.getExceptions()) - .hasSize(1) - .extracting("detailMessage") - .contains("Failed processing"); - }); - - verify(sqsClient).deleteMessageBatch(any(DeleteMessageBatchRequest.class)); - } - - - @Test - void shouldBatchProcessAndSuppressExceptions() { - String failedId = "2e1424d4-f796-459a-8184-9c92662be6da"; - - SqsMessageHandler<String> failedHandler = (message) -> { - if (failedId.equals(message.getMessageId())) { - throw new RuntimeException("Failed processing"); - } - - interactionClient.listQueues(); - return "Success"; - }; - - List<String> returnValues = batchProcessor(event, true, failedHandler); - - assertThat(returnValues) - .hasSize(1) - .contains("Success"); - - verify(interactionClient).listQueues(); - verify(sqsClient).deleteMessageBatch(any(DeleteMessageBatchRequest.class)); - } - - @Test - void shouldBatchProcessViaClassAndSuppressExceptions() { - List<String> returnValues = batchProcessor(event, true, FailureSampleInnerSqsHandler.class); - - assertThat(returnValues) - .hasSize(1) - .contains("Success"); - - verify(interactionClient).listQueues(); - verify(sqsClient).deleteMessageBatch(any(DeleteMessageBatchRequest.class)); - } - - public class SampleInnerSqsHandler implements SqsMessageHandler<String> { - private int counter; - - @Override - public String process(SQSMessage message) { - interactionClient.listQueues(); - return String.valueOf(counter++); - } - } - - @Test - void shouldBatchProcessAndMoveNonRetryableExceptionToDlq() { - String failedId = "2e1424d4-f796-459a-8184-9c92662be6da"; - HashMap<QueueAttributeName, String> attributes = new HashMap<>(); - - attributes.put(QueueAttributeName.REDRIVE_POLICY, "{\n" + - " \"deadLetterTargetArn\": \"arn:aws:sqs:us-east-2:123456789012:retry-queue\",\n" + - " \"maxReceiveCount\": 2\n" + - "}"); - - when(sqsClient.getQueueAttributes(any(GetQueueAttributesRequest.class))).thenReturn(GetQueueAttributesResponse.builder() - .attributes(attributes) - .build()); - - List<String> batchProcessor = batchProcessor(event, (message) -> { - if (failedId.equals(message.getMessageId())) { - throw new IllegalStateException("Failed processing"); - } - - interactionClient.listQueues(); - return "Success"; - }, IllegalStateException.class, IllegalArgumentException.class); - - assertThat(batchProcessor) - .hasSize(1); - - verify(sqsClient).sendMessageBatch(any(SendMessageBatchRequest.class)); - } - - @Test - void shouldBatchProcessAndDeleteNonRetryableException() { - String failedId = "2e1424d4-f796-459a-8184-9c92662be6da"; - HashMap<QueueAttributeName, String> attributes = new HashMap<>(); - - attributes.put(QueueAttributeName.REDRIVE_POLICY, "{\n" + - " \"deadLetterTargetArn\": \"arn:aws:sqs:us-east-2:123456789012:retry-queue\",\n" + - " \"maxReceiveCount\": 2\n" + - "}"); - - when(sqsClient.getQueueAttributes(any(GetQueueAttributesRequest.class))).thenReturn(GetQueueAttributesResponse.builder() - .attributes(attributes) - .build()); - - List<String> batchProcessor = batchProcessor(event, false, (message) -> { - if (failedId.equals(message.getMessageId())) { - throw new IllegalStateException("Failed processing"); - } - - interactionClient.listQueues(); - return "Success"; - }, true, IllegalStateException.class, IllegalArgumentException.class); - - assertThat(batchProcessor) - .hasSize(1); - - verify(sqsClient, times(0)).sendMessageBatch(any(SendMessageBatchRequest.class)); - verify(sqsClient).deleteMessageBatch(any(DeleteMessageBatchRequest.class)); - } - - @Test - void shouldDeleteSuccessfulMessageInBatchesOfT10orLess() throws IOException { - SQSEvent batch25Message = MAPPER.readValue(this.getClass().getResource("/sampleSqsBatchEventBatchSize25.json"), SQSEvent.class); - - assertThatExceptionOfType(SQSBatchProcessingException.class) - .isThrownBy(() -> batchProcessor(batch25Message, FailureSampleInnerSqsHandler.class)) - .satisfies(e -> { - - assertThat(e.successMessageReturnValues()) - .hasSize(24) - .contains("Success"); - - assertThat(e.getFailures()) - .hasSize(1) - .extracting("messageId") - .contains("2e1424d4-f796-459a-8184-9c92662be6da"); - - assertThat(e.getExceptions()) - .hasSize(1) - .extracting("detailMessage") - .contains("Failed processing"); - }); - - ArgumentCaptor<DeleteMessageBatchRequest> captor = ArgumentCaptor.forClass(DeleteMessageBatchRequest.class); - - verify(sqsClient, times(3)).deleteMessageBatch(captor.capture()); - - assertThat(captor.getAllValues()) - .hasSize(3) - .flatMap(DeleteMessageBatchRequest::entries) - .hasSize(24); - } - - @Test - void shouldBatchProcessAndMoveNonRetryableExceptionToDlqInBatchesOfT10orLess() throws IOException { - SQSEvent batch25Message = MAPPER.readValue(this.getClass().getResource("/sampleSqsBatchEventBatchSize25.json"), SQSEvent.class); - - HashMap<QueueAttributeName, String> attributes = new HashMap<>(); - - attributes.put(QueueAttributeName.REDRIVE_POLICY, "{\n" + - " \"deadLetterTargetArn\": \"arn:aws:sqs:us-east-2:123456789012:retry-queue\",\n" + - " \"maxReceiveCount\": 2\n" + - "}"); - - when(sqsClient.getQueueAttributes(any(GetQueueAttributesRequest.class))).thenReturn(GetQueueAttributesResponse.builder() - .attributes(attributes) - .build()); - - List<String> batchProcessor = batchProcessor(batch25Message, (message) -> { - if ("2e1424d4-f796-459a-8184-9c92662be6da".equals(message.getMessageId())) { - interactionClient.listQueues(); - return "Success"; - } - - throw new IllegalStateException("Failed processing"); - }, IllegalStateException.class, IllegalArgumentException.class); - - assertThat(batchProcessor) - .hasSize(1); - - ArgumentCaptor<SendMessageBatchRequest> captor = ArgumentCaptor.forClass(SendMessageBatchRequest.class); - - - verify(sqsClient, times(3)).sendMessageBatch(captor.capture()); - - assertThat(captor.getAllValues()) - .hasSize(3) - .flatMap(SendMessageBatchRequest::entries) - .hasSize(24); - } - - public class FailureSampleInnerSqsHandler implements SqsMessageHandler<String> { - @Override - public String process(SQSEvent.SQSMessage message) { - if ("2e1424d4-f796-459a-8184-9c92662be6da".equals(message.getMessageId())) { - throw new RuntimeException("Failed processing"); - } - - interactionClient.listQueues(); - return "Success"; - } - } -} \ No newline at end of file diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SqsUtilsLargeMessageTest.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SqsUtilsLargeMessageTest.java deleted file mode 100644 index 48de3e6a9..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SqsUtilsLargeMessageTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package software.amazon.lambda.powertools.sqs; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; -import java.util.stream.Stream; - -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import software.amazon.awssdk.core.ResponseInputStream; -import software.amazon.awssdk.core.exception.SdkClientException; -import software.amazon.awssdk.http.AbortableInputStream; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectResponse; -import software.amazon.awssdk.services.s3.model.S3Exception; -import software.amazon.awssdk.utils.StringInputStream; -import software.amazon.lambda.powertools.sqs.internal.SqsLargeMessageAspect; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; - -class SqsUtilsLargeMessageTest { - - @Mock - private S3Client s3Client; - private static final String BUCKET_NAME = "ms-extended-sqs-client"; - private static final String BUCKET_KEY = "c71eb2ae-37e0-4265-8909-32f4153faddf"; - - @BeforeEach - void setUp() { - openMocks(this); - SqsUtils.overrideS3Client(s3Client); - } - - @Test - public void testLargeMessage() { - ResponseInputStream<GetObjectResponse> s3Response = new ResponseInputStream<>(GetObjectResponse.builder().build(), AbortableInputStream.create(new ByteArrayInputStream("A big message".getBytes()))); - when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3Response); - - SQSEvent sqsEvent = messageWithBody("[\"software.amazon.payloadoffloading.PayloadS3Pointer\",{\"s3BucketName\":\"" + BUCKET_NAME + "\",\"s3Key\":\"" + BUCKET_KEY + "\"}]"); - - Map<String, String> sqsMessage = SqsUtils.enrichedMessageFromS3(sqsEvent, sqsMessages -> { - Map<String, String> someBusinessLogic = new HashMap<>(); - someBusinessLogic.put("Message", sqsMessages.get(0).getBody()); - return someBusinessLogic; - }); - - assertThat(sqsMessage) - .hasSize(1) - .containsEntry("Message", "A big message"); - - ArgumentCaptor<DeleteObjectRequest> delete = ArgumentCaptor.forClass(DeleteObjectRequest.class); - - verify(s3Client).deleteObject(delete.capture()); - - Assertions.assertThat(delete.getValue()) - .satisfies((Consumer<DeleteObjectRequest>) deleteObjectRequest -> { - assertThat(deleteObjectRequest.bucket()) - .isEqualTo(BUCKET_NAME); - - assertThat(deleteObjectRequest.key()) - .isEqualTo(BUCKET_KEY); - }); - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testLargeMessageDeleteFromS3Toggle(boolean deleteS3Payload) { - ResponseInputStream<GetObjectResponse> s3Response = new ResponseInputStream<>(GetObjectResponse.builder().build(), AbortableInputStream.create(new ByteArrayInputStream("A big message".getBytes()))); - - when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3Response); - - SQSEvent sqsEvent = messageWithBody("[\"software.amazon.payloadoffloading.PayloadS3Pointer\",{\"s3BucketName\":\"" + BUCKET_NAME + "\",\"s3Key\":\"" + BUCKET_KEY + "\"}]"); - - Map<String, String> sqsMessage = SqsUtils.enrichedMessageFromS3(sqsEvent, deleteS3Payload, sqsMessages -> { - Map<String, String> someBusinessLogic = new HashMap<>(); - someBusinessLogic.put("Message", sqsMessages.get(0).getBody()); - return someBusinessLogic; - }); - - assertThat(sqsMessage) - .hasSize(1) - .containsEntry("Message", "A big message"); - if (deleteS3Payload) { - ArgumentCaptor<DeleteObjectRequest> delete = ArgumentCaptor.forClass(DeleteObjectRequest.class); - - verify(s3Client).deleteObject(delete.capture()); - - Assertions.assertThat(delete.getValue()) - .satisfies((Consumer<DeleteObjectRequest>) deleteObjectRequest -> { - assertThat(deleteObjectRequest.bucket()) - .isEqualTo(BUCKET_NAME); - - assertThat(deleteObjectRequest.key()) - .isEqualTo(BUCKET_KEY); - }); - } else { - verify(s3Client, never()).deleteObject(any(DeleteObjectRequest.class)); - } - } - - @Test - public void shouldNotProcessSmallMessageBody() { - ResponseInputStream<GetObjectResponse> s3Response = new ResponseInputStream<>(GetObjectResponse.builder().build(), AbortableInputStream.create(new ByteArrayInputStream("A big message".getBytes()))); - - when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3Response); - - SQSEvent sqsEvent = messageWithBody("This is small message"); - - Map<String, String> sqsMessage = SqsUtils.enrichedMessageFromS3(sqsEvent, sqsMessages -> { - Map<String, String> someBusinessLogic = new HashMap<>(); - someBusinessLogic.put("Message", sqsMessages.get(0).getBody()); - return someBusinessLogic; - }); - - assertThat(sqsMessage) - .containsEntry("Message", "This is small message"); - - verifyNoInteractions(s3Client); - } - - @ParameterizedTest - @MethodSource("exception") - public void shouldFailEntireBatchIfFailedDownloadingFromS3(RuntimeException exception) { - when(s3Client.getObject(any(GetObjectRequest.class))).thenThrow(exception); - - String messageBody = "[\"software.amazon.payloadoffloading.PayloadS3Pointer\",{\"s3BucketName\":\"" + BUCKET_NAME + "\",\"s3Key\":\"" + BUCKET_KEY + "\"}]"; - SQSEvent sqsEvent = messageWithBody(messageBody); - - assertThatExceptionOfType(SqsLargeMessageAspect.FailedProcessingLargePayloadException.class) - .isThrownBy(() -> SqsUtils.enrichedMessageFromS3(sqsEvent, sqsMessages -> sqsMessages.get(0).getBody())) - .withCause(exception); - - verify(s3Client, never()).deleteObject(any(DeleteObjectRequest.class)); - } - - @Test - public void shouldFailEntireBatchIfFailedProcessingDownloadMessageFromS3() { - ResponseInputStream<GetObjectResponse> s3Response = new ResponseInputStream<>(GetObjectResponse.builder().build(), AbortableInputStream.create(new StringInputStream("test") { - @Override - public void close() throws IOException { - throw new IOException("Failed"); - } - })); - - when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3Response); - - String messageBody = "[\"software.amazon.payloadoffloading.PayloadS3Pointer\",{\"s3BucketName\":\"" + BUCKET_NAME + "\",\"s3Key\":\"" + BUCKET_KEY + "\"}]"; - SQSEvent sqsEvent = messageWithBody(messageBody); - - assertThatExceptionOfType(SqsLargeMessageAspect.FailedProcessingLargePayloadException.class) - .isThrownBy(() -> SqsUtils.enrichedMessageFromS3(sqsEvent, sqsMessages -> sqsMessages.get(0).getBody())) - .withCauseInstanceOf(IOException.class); - - verify(s3Client, never()).deleteObject(any(DeleteObjectRequest.class)); - } - - private static Stream<Arguments> exception() { - return Stream.of(Arguments.of(S3Exception.builder() - .message("Service Exception") - .build()), - Arguments.of(SdkClientException.builder() - .message("Client Exception") - .build())); - } - - private SQSEvent messageWithBody(String messageBody) { - SQSMessage sqsMessage = new SQSMessage(); - sqsMessage.setBody(messageBody); - SQSEvent sqsEvent = new SQSEvent(); - sqsEvent.setRecords(singletonList(sqsMessage)); - return sqsEvent; - } -} \ No newline at end of file diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/LambdaHandlerApiGateway.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/LambdaHandlerApiGateway.java deleted file mode 100644 index b0d8177ac..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/LambdaHandlerApiGateway.java +++ /dev/null @@ -1,18 +0,0 @@ -package software.amazon.lambda.powertools.sqs.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import software.amazon.lambda.powertools.sqs.SqsLargeMessage; -import software.amazon.lambda.powertools.sqs.SampleSqsHandler; -import software.amazon.lambda.powertools.sqs.SqsBatch; - -public class LambdaHandlerApiGateway implements RequestHandler<APIGatewayProxyRequestEvent, String> { - - @Override - @SqsLargeMessage - @SqsBatch(value = SampleSqsHandler.class) - public String handleRequest(APIGatewayProxyRequestEvent sqsEvent, Context context) { - return sqsEvent.getBody(); - } -} diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/PartialBatchFailureSuppressedHandler.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/PartialBatchFailureSuppressedHandler.java deleted file mode 100644 index 63f1573bf..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/PartialBatchFailureSuppressedHandler.java +++ /dev/null @@ -1,32 +0,0 @@ -package software.amazon.lambda.powertools.sqs.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import software.amazon.lambda.powertools.sqs.SqsBatch; -import software.amazon.lambda.powertools.sqs.SqsMessageHandler; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static software.amazon.lambda.powertools.sqs.internal.SqsMessageBatchProcessorAspectTest.interactionClient; - -public class PartialBatchFailureSuppressedHandler implements RequestHandler<SQSEvent, String> { - @Override - @SqsBatch(value = InnerMessageHandler.class, suppressException = true) - public String handleRequest(final SQSEvent sqsEvent, - final Context context) { - return "Success"; - } - - private class InnerMessageHandler implements SqsMessageHandler<Object> { - - @Override - public String process(SQSMessage message) { - if ("2e1424d4-f796-459a-8184-9c92662be6da".equals(message.getMessageId())) { - throw new RuntimeException("2e1424d4-f796-459a-8184-9c92662be6da"); - } - - interactionClient.listQueues(); - return "Success"; - } - } -} diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/PartialBatchPartialFailureHandler.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/PartialBatchPartialFailureHandler.java deleted file mode 100644 index 653459d82..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/PartialBatchPartialFailureHandler.java +++ /dev/null @@ -1,32 +0,0 @@ -package software.amazon.lambda.powertools.sqs.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import software.amazon.lambda.powertools.sqs.SqsBatch; -import software.amazon.lambda.powertools.sqs.SqsMessageHandler; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static software.amazon.lambda.powertools.sqs.internal.SqsMessageBatchProcessorAspectTest.interactionClient; - -public class PartialBatchPartialFailureHandler implements RequestHandler<SQSEvent, String> { - @Override - @SqsBatch(InnerMessageHandler.class) - public String handleRequest(final SQSEvent sqsEvent, - final Context context) { - return "Success"; - } - - private class InnerMessageHandler implements SqsMessageHandler<Object> { - - @Override - public String process(SQSMessage message) { - if ("2e1424d4-f796-459a-8184-9c92662be6da".equals(message.getMessageId())) { - throw new RuntimeException("2e1424d4-f796-459a-8184-9c92662be6da"); - } - - interactionClient.listQueues(); - return "Success"; - } - } -} diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/PartialBatchSuccessHandler.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/PartialBatchSuccessHandler.java deleted file mode 100644 index 926cdb4f5..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/PartialBatchSuccessHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -package software.amazon.lambda.powertools.sqs.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import software.amazon.lambda.powertools.sqs.SqsBatch; -import software.amazon.lambda.powertools.sqs.SqsMessageHandler; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static software.amazon.lambda.powertools.sqs.internal.SqsMessageBatchProcessorAspectTest.interactionClient; - -public class PartialBatchSuccessHandler implements RequestHandler<SQSEvent, String> { - @Override - @SqsBatch(InnerMessageHandler.class) - public String handleRequest(final SQSEvent sqsEvent, - final Context context) { - return "Success"; - } - - private class InnerMessageHandler implements SqsMessageHandler<Object> { - - @Override - public String process(SQSMessage message) { - interactionClient.listQueues(); - return "Success"; - } - } -} diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/SqsMessageHandler.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/SqsMessageHandler.java deleted file mode 100644 index ee8c100e6..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/SqsMessageHandler.java +++ /dev/null @@ -1,15 +0,0 @@ -package software.amazon.lambda.powertools.sqs.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import software.amazon.lambda.powertools.sqs.SqsLargeMessage; - -public class SqsMessageHandler implements RequestHandler<SQSEvent, String> { - - @Override - @SqsLargeMessage - public String handleRequest(SQSEvent sqsEvent, Context context) { - return sqsEvent.getRecords().get(0).getBody(); - } -} diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/SqsMessageHandlerWithNonRetryableHandler.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/SqsMessageHandlerWithNonRetryableHandler.java deleted file mode 100644 index 6eec87301..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/SqsMessageHandlerWithNonRetryableHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -package software.amazon.lambda.powertools.sqs.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import software.amazon.lambda.powertools.sqs.SqsBatch; -import software.amazon.lambda.powertools.sqs.SqsMessageHandler; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static software.amazon.lambda.powertools.sqs.internal.SqsMessageBatchProcessorAspectTest.interactionClient; - -public class SqsMessageHandlerWithNonRetryableHandler implements RequestHandler<SQSEvent, String> { - - @Override - @SqsBatch(value = InnerMessageHandler.class, nonRetryableExceptions = {IllegalStateException.class, IllegalArgumentException.class}) - public String handleRequest(final SQSEvent sqsEvent, - final Context context) { - return "Success"; - } - - private class InnerMessageHandler implements SqsMessageHandler<Object> { - - @Override - public String process(SQSMessage message) { - if(message.getMessageId().isEmpty()) { - throw new IllegalArgumentException("Invalid message and was moved to DLQ"); - } - - if("2e1424d4-f796-459a-9696-9c92662ba5da".equals(message.getMessageId())) { - throw new RuntimeException("Invalid message and should be reprocessed"); - } - - interactionClient.listQueues(); - return "Success"; - } - } -} diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/SqsMessageHandlerWithNonRetryableHandlerWithDelete.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/SqsMessageHandlerWithNonRetryableHandlerWithDelete.java deleted file mode 100644 index 789a7b86d..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/SqsMessageHandlerWithNonRetryableHandlerWithDelete.java +++ /dev/null @@ -1,39 +0,0 @@ -package software.amazon.lambda.powertools.sqs.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import software.amazon.lambda.powertools.sqs.SqsBatch; -import software.amazon.lambda.powertools.sqs.SqsMessageHandler; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static software.amazon.lambda.powertools.sqs.internal.SqsMessageBatchProcessorAspectTest.interactionClient; - -public class SqsMessageHandlerWithNonRetryableHandlerWithDelete implements RequestHandler<SQSEvent, String> { - - @Override - @SqsBatch(value = InnerMessageHandler.class, - nonRetryableExceptions = {IllegalStateException.class, IllegalArgumentException.class}, - deleteNonRetryableMessageFromQueue = true) - public String handleRequest(final SQSEvent sqsEvent, - final Context context) { - return "Success"; - } - - private class InnerMessageHandler implements SqsMessageHandler<Object> { - - @Override - public String process(SQSMessage message) { - if(message.getMessageId().isEmpty()) { - throw new IllegalArgumentException("Invalid message and was moved to DLQ"); - } - - if("2e1424d4-f796-459a-9696-9c92662ba5da".equals(message.getMessageId())) { - throw new RuntimeException("Invalid message and should be reprocessed"); - } - - interactionClient.listQueues(); - return "Success"; - } - } -} diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/SqsNoDeleteMessageHandler.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/SqsNoDeleteMessageHandler.java deleted file mode 100644 index 337592004..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/handlers/SqsNoDeleteMessageHandler.java +++ /dev/null @@ -1,15 +0,0 @@ -package software.amazon.lambda.powertools.sqs.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import software.amazon.lambda.powertools.sqs.SqsLargeMessage; - -public class SqsNoDeleteMessageHandler implements RequestHandler<SQSEvent, String> { - - @Override - @SqsLargeMessage(deletePayloads = false) - public String handleRequest(SQSEvent sqsEvent, Context context) { - return sqsEvent.getRecords().get(0).getBody(); - } -} \ No newline at end of file diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/internal/SqsLargeMessageAspectTest.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/internal/SqsLargeMessageAspectTest.java deleted file mode 100644 index 22844ab4c..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/internal/SqsLargeMessageAspectTest.java +++ /dev/null @@ -1,199 +0,0 @@ -package software.amazon.lambda.powertools.sqs.internal; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.function.Consumer; -import java.util.stream.Stream; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import software.amazon.awssdk.core.ResponseInputStream; -import software.amazon.awssdk.core.exception.SdkClientException; -import software.amazon.awssdk.http.AbortableInputStream; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectResponse; -import software.amazon.awssdk.services.s3.model.S3Exception; -import software.amazon.awssdk.utils.StringInputStream; -import software.amazon.lambda.powertools.sqs.SqsUtils; -import software.amazon.lambda.powertools.sqs.handlers.LambdaHandlerApiGateway; -import software.amazon.lambda.powertools.sqs.handlers.SqsMessageHandler; -import software.amazon.lambda.powertools.sqs.handlers.SqsNoDeleteMessageHandler; - -import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; -import static software.amazon.lambda.powertools.sqs.internal.SqsLargeMessageAspect.FailedProcessingLargePayloadException; - -public class SqsLargeMessageAspectTest { - - private RequestHandler<SQSEvent, String> requestHandler; - - @Mock - private Context context; - - @Mock - private S3Client s3Client; - - private static final String BUCKET_NAME = "bucketname"; - private static final String BUCKET_KEY = "c71eb2ae-37e0-4265-8909-32f4153faddf"; - - @BeforeEach - void setUp() { - openMocks(this); - setupContext(); - SqsUtils.overrideS3Client(s3Client); - requestHandler = new SqsMessageHandler(); - } - - @Test - public void testLargeMessage() { - when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); - SQSEvent sqsEvent = messageWithBody("[\"software.amazon.payloadoffloading.PayloadS3Pointer\",{\"s3BucketName\":\"" + BUCKET_NAME + "\",\"s3Key\":\"" + BUCKET_KEY + "\"}]"); - - String response = requestHandler.handleRequest(sqsEvent, context); - - assertThat(response) - .isEqualTo("A big message"); - - ArgumentCaptor<DeleteObjectRequest> delete = ArgumentCaptor.forClass(DeleteObjectRequest.class); - - verify(s3Client).deleteObject(delete.capture()); - - Assertions.assertThat(delete.getValue()) - .satisfies((Consumer<DeleteObjectRequest>) deleteObjectRequest -> { - assertThat(deleteObjectRequest.bucket()) - .isEqualTo(BUCKET_NAME); - - assertThat(deleteObjectRequest.key()) - .isEqualTo(BUCKET_KEY); - }); - } - - @Test - public void shouldNotProcessSmallMessageBody() { - when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); - - SQSEvent sqsEvent = messageWithBody("This is small message"); - - String response = requestHandler.handleRequest(sqsEvent, context); - - assertThat(response) - .isEqualTo("This is small message"); - - verifyNoInteractions(s3Client); - } - - @ParameterizedTest - @MethodSource("exception") - public void shouldFailEntireBatchIfFailedDownloadingFromS3(RuntimeException exception) { - when(s3Client.getObject(any(GetObjectRequest.class))).thenThrow(exception); - - String messageBody = "[\"software.amazon.payloadoffloading.PayloadS3Pointer\",{\"s3BucketName\":\"" + BUCKET_NAME + "\",\"s3Key\":\"" + BUCKET_KEY + "\"}]"; - SQSEvent sqsEvent = messageWithBody(messageBody); - - assertThatExceptionOfType(FailedProcessingLargePayloadException.class) - .isThrownBy(() -> requestHandler.handleRequest(sqsEvent, context)) - .withCause(exception); - - verify(s3Client, never()).deleteObject(any(DeleteObjectRequest.class)); - } - - @Test - public void testLargeMessageWithDeletionOff() { - requestHandler = new SqsNoDeleteMessageHandler(); - - when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3ObjectWithLargeMessage()); - SQSEvent sqsEvent = messageWithBody("[\"software.amazon.payloadoffloading.PayloadS3Pointer\",{\"s3BucketName\":\"" + BUCKET_NAME + "\",\"s3Key\":\"" + BUCKET_KEY + "\"}]"); - - String response = requestHandler.handleRequest(sqsEvent, context); - - assertThat(response).isEqualTo("A big message"); - - verify(s3Client, never()).deleteObject(any(DeleteObjectRequest.class)); - } - - - @Test - public void shouldFailEntireBatchIfFailedProcessingDownloadMessageFromS3() { - ResponseInputStream<GetObjectResponse> s3Response = new ResponseInputStream<>(GetObjectResponse.builder().build(), AbortableInputStream.create(new StringInputStream("test") { - @Override - public void close() throws IOException { - throw new IOException("Failed"); - } - })); - - when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3Response); - - String messageBody = "[\"software.amazon.payloadoffloading.PayloadS3Pointer\",{\"s3BucketName\":\"" + BUCKET_NAME + "\",\"s3Key\":\"" + BUCKET_KEY + "\"}]"; - SQSEvent sqsEvent = messageWithBody(messageBody); - - assertThatExceptionOfType(FailedProcessingLargePayloadException.class) - .isThrownBy(() -> requestHandler.handleRequest(sqsEvent, context)) - .withCauseInstanceOf(IOException.class); - - verify(s3Client, never()).deleteObject(any(DeleteObjectRequest.class)); - } - - @Test - public void shouldNotDoAnyProcessingWhenNotSqsEvent() { - LambdaHandlerApiGateway handler = new LambdaHandlerApiGateway(); - - String messageBody = "[\"software.amazon.payloadoffloading.PayloadS3Pointer\",{\"s3BucketName\":\"" + BUCKET_NAME + "\",\"s3Key\":\"" + BUCKET_KEY + "\"}]"; - - APIGatewayProxyRequestEvent event = new APIGatewayProxyRequestEvent(); - event.setBody(messageBody); - String response = handler.handleRequest(event, context); - - assertThat(response) - .isEqualTo(messageBody); - - verifyNoInteractions(s3Client); - } - - private ResponseInputStream<GetObjectResponse> s3ObjectWithLargeMessage() { - return new ResponseInputStream<>(GetObjectResponse.builder().build(), AbortableInputStream.create(new ByteArrayInputStream("A big message".getBytes()))); - } - - private static Stream<Arguments> exception() { - return Stream.of(Arguments.of(S3Exception.builder() - .message("Service Exception") - .build()), - Arguments.of(SdkClientException.builder() - .message("Client Exception") - .build())); - } - - private SQSEvent messageWithBody(String messageBody) { - SQSMessage sqsMessage = new SQSMessage(); - sqsMessage.setBody(messageBody); - SQSEvent sqsEvent = new SQSEvent(); - sqsEvent.setRecords(singletonList(sqsMessage)); - return sqsEvent; - } - - private void setupContext() { - when(context.getFunctionName()).thenReturn("testFunction"); - when(context.getInvokedFunctionArn()).thenReturn("testArn"); - when(context.getFunctionVersion()).thenReturn("1"); - when(context.getMemoryLimitInMB()).thenReturn(10); - } -} \ No newline at end of file diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/internal/SqsMessageBatchProcessorAspectTest.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/internal/SqsMessageBatchProcessorAspectTest.java deleted file mode 100644 index dd10ec1df..000000000 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/internal/SqsMessageBatchProcessorAspectTest.java +++ /dev/null @@ -1,277 +0,0 @@ -package software.amazon.lambda.powertools.sqs.internal; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.function.Consumer; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import software.amazon.awssdk.services.sqs.SqsClient; -import software.amazon.awssdk.services.sqs.model.DeleteMessageBatchRequest; -import software.amazon.awssdk.services.sqs.model.GetQueueAttributesRequest; -import software.amazon.awssdk.services.sqs.model.GetQueueAttributesResponse; -import software.amazon.awssdk.services.sqs.model.QueueAttributeName; -import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequest; -import software.amazon.lambda.powertools.sqs.SQSBatchProcessingException; -import software.amazon.lambda.powertools.sqs.handlers.LambdaHandlerApiGateway; -import software.amazon.lambda.powertools.sqs.handlers.PartialBatchFailureSuppressedHandler; -import software.amazon.lambda.powertools.sqs.handlers.PartialBatchPartialFailureHandler; -import software.amazon.lambda.powertools.sqs.handlers.PartialBatchSuccessHandler; -import software.amazon.lambda.powertools.sqs.handlers.SqsMessageHandlerWithNonRetryableHandler; -import software.amazon.lambda.powertools.sqs.handlers.SqsMessageHandlerWithNonRetryableHandlerWithDelete; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.in; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; -import static software.amazon.lambda.powertools.sqs.SqsUtils.overrideSqsClient; - -public class SqsMessageBatchProcessorAspectTest { - public static final SqsClient interactionClient = mock(SqsClient.class); - private static final SqsClient sqsClient = mock(SqsClient.class); - private static final ObjectMapper MAPPER = new ObjectMapper(); - - private SQSEvent event; - private RequestHandler<SQSEvent, String> requestHandler; - - private final Context context = mock(Context.class); - - @BeforeEach - void setUp() throws IOException { - overrideSqsClient(sqsClient); - reset(interactionClient); - reset(sqsClient); - setupContext(); - event = MAPPER.readValue(this.getClass().getResource("/sampleSqsBatchEvent.json"), SQSEvent.class); - requestHandler = new PartialBatchSuccessHandler(); - } - - @Test - void shouldBatchProcessAllMessageSuccessfullyAndNotDeleteFromSQS() { - requestHandler.handleRequest(event, context); - - verify(interactionClient, times(2)).listQueues(); - verify(sqsClient, times(0)).deleteMessageBatch(any(DeleteMessageBatchRequest.class)); - } - - @Test - void shouldBatchProcessMessageWithSuccessDeletedOnFailureInBatchFromSQS() { - requestHandler = new PartialBatchPartialFailureHandler(); - - assertThatExceptionOfType(SQSBatchProcessingException.class) - .isThrownBy(() -> requestHandler.handleRequest(event, context)) - .satisfies(e -> { - assertThat(e.getExceptions()) - .hasSize(1) - .extracting("message") - .containsExactly("2e1424d4-f796-459a-8184-9c92662be6da"); - - assertThat(e.getFailures()) - .hasSize(1) - .extracting("messageId") - .containsExactly("2e1424d4-f796-459a-8184-9c92662be6da"); - - assertThat(e.successMessageReturnValues()) - .hasSize(1) - .contains("Success"); - }); - - verify(interactionClient).listQueues(); - verify(sqsClient).deleteMessageBatch(any(DeleteMessageBatchRequest.class)); - } - - @Test - void shouldBatchProcessMessageWithSuccessDeletedOnFailureWithSuppressionInBatchFromSQS() { - requestHandler = new PartialBatchFailureSuppressedHandler(); - - requestHandler.handleRequest(event, context); - - verify(interactionClient).listQueues(); - verify(sqsClient).deleteMessageBatch(any(DeleteMessageBatchRequest.class)); - } - - @Test - void shouldNotTakeEffectOnNonSqsEventHandler() { - LambdaHandlerApiGateway handlerApiGateway = new LambdaHandlerApiGateway(); - - handlerApiGateway.handleRequest(mock(APIGatewayProxyRequestEvent.class), context); - - verifyNoInteractions(sqsClient); - } - - @Test - void shouldBatchProcessAndMoveNonRetryableExceptionToDlq() { - requestHandler = new SqsMessageHandlerWithNonRetryableHandler(); - event.getRecords().get(0).setMessageId(""); - - HashMap<QueueAttributeName, String> attributes = new HashMap<>(); - - attributes.put(QueueAttributeName.REDRIVE_POLICY, "{\n" + - " \"deadLetterTargetArn\": \"arn:aws:sqs:us-east-2:123456789012:retry-queue\",\n" + - " \"maxReceiveCount\": 2\n" + - "}"); - - when(sqsClient.getQueueAttributes(any(GetQueueAttributesRequest.class))).thenReturn(GetQueueAttributesResponse.builder() - .attributes(attributes) - .build()); - - requestHandler.handleRequest(event, context); - - verify(interactionClient).listQueues(); - verify(sqsClient).deleteMessageBatch(any(DeleteMessageBatchRequest.class)); - verify(sqsClient).sendMessageBatch(any(SendMessageBatchRequest.class)); - } - - @Test - void shouldBatchProcessAndDeleteNonRetryableExceptionMessage() { - requestHandler = new SqsMessageHandlerWithNonRetryableHandlerWithDelete(); - event.getRecords().get(0).setMessageId(""); - - requestHandler.handleRequest(event, context); - - verify(interactionClient).listQueues(); - ArgumentCaptor<DeleteMessageBatchRequest> captor = ArgumentCaptor.forClass(DeleteMessageBatchRequest.class); - verify(sqsClient).deleteMessageBatch(captor.capture()); - verify(sqsClient, never()).sendMessageBatch(any(SendMessageBatchRequest.class)); - verify(sqsClient, never()).getQueueAttributes(any(GetQueueAttributesRequest.class)); - - assertThat(captor.getValue()) - .satisfies(deleteMessageBatchRequest -> assertThat(deleteMessageBatchRequest.entries()) - .hasSize(2) - .extracting("id") - .contains("", "2e1424d4-f796-459a-8184-9c92662be6da")); - } - - @Test - void shouldBatchProcessAndFailWithExceptionForNonRetryableExceptionAndNoDlq() { - requestHandler = new SqsMessageHandlerWithNonRetryableHandler(); - - event.getRecords().get(0).setMessageId(""); - event.getRecords().forEach(sqsMessage -> sqsMessage.setEventSourceArn(sqsMessage.getEventSourceArn() + "-temp")); - - when(sqsClient.getQueueAttributes(any(GetQueueAttributesRequest.class))).thenReturn(GetQueueAttributesResponse.builder() - .build()); - - assertThatExceptionOfType(SQSBatchProcessingException.class) - .isThrownBy(() -> requestHandler.handleRequest(event, context)) - .satisfies(e -> { - assertThat(e.getExceptions()) - .hasSize(1) - .extracting("message") - .containsExactly("Invalid message and was moved to DLQ"); - - assertThat(e.getFailures()) - .hasSize(1) - .extracting("messageId") - .containsExactly(""); - - assertThat(e.successMessageReturnValues()) - .hasSize(1) - .contains("Success"); - }); - - verify(interactionClient).listQueues(); - verify(sqsClient).deleteMessageBatch(any(DeleteMessageBatchRequest.class)); - verify(sqsClient, never()).sendMessageBatch(any(SendMessageBatchRequest.class)); - } - - @Test - void shouldBatchProcessAndFailWithExceptionForNonRetryableExceptionWhenFailedParsingPolicy() { - requestHandler = new SqsMessageHandlerWithNonRetryableHandler(); - event.getRecords().get(0).setMessageId(""); - event.getRecords().forEach(sqsMessage -> sqsMessage.setEventSourceArn(sqsMessage.getEventSourceArn() + "-temp-queue")); - - HashMap<QueueAttributeName, String> attributes = new HashMap<>(); - - attributes.put(QueueAttributeName.REDRIVE_POLICY, "MalFormedRedrivePolicy"); - - when(sqsClient.getQueueAttributes(any(GetQueueAttributesRequest.class))).thenReturn(GetQueueAttributesResponse.builder() - .attributes(attributes) - .build()); - - assertThatExceptionOfType(SQSBatchProcessingException.class) - .isThrownBy(() -> requestHandler.handleRequest(event, context)) - .satisfies(e -> { - assertThat(e.getExceptions()) - .hasSize(1) - .extracting("message") - .containsExactly("Invalid message and was moved to DLQ"); - - assertThat(e.getFailures()) - .hasSize(1) - .extracting("messageId") - .containsExactly(""); - - assertThat(e.successMessageReturnValues()) - .hasSize(1) - .contains("Success"); - }); - - verify(interactionClient).listQueues(); - verify(sqsClient).deleteMessageBatch(any(DeleteMessageBatchRequest.class)); - verify(sqsClient, never()).sendMessageBatch(any(SendMessageBatchRequest.class)); - } - - @Test - void shouldBatchProcessAndMoveNonRetryableExceptionToDlqAndThrowException() throws IOException { - requestHandler = new SqsMessageHandlerWithNonRetryableHandler(); - event = MAPPER.readValue(this.getClass().getResource("/threeMessageSqsBatchEvent.json"), SQSEvent.class); - - event.getRecords().get(1).setMessageId(""); - - HashMap<QueueAttributeName, String> attributes = new HashMap<>(); - - attributes.put(QueueAttributeName.REDRIVE_POLICY, "{\n" + - " \"deadLetterTargetArn\": \"arn:aws:sqs:us-east-2:123456789012:retry-queue\",\n" + - " \"maxReceiveCount\": 2\n" + - "}"); - - when(sqsClient.getQueueAttributes(any(GetQueueAttributesRequest.class))).thenReturn(GetQueueAttributesResponse.builder() - .attributes(attributes) - .build()); - - assertThatExceptionOfType(SQSBatchProcessingException.class) - .isThrownBy(() -> requestHandler.handleRequest(event, context)) - .satisfies(e -> { - assertThat(e.getExceptions()) - .hasSize(1) - .extracting("message") - .containsExactly("Invalid message and should be reprocessed"); - - assertThat(e.getFailures()) - .hasSize(1) - .extracting("messageId") - .containsExactly("2e1424d4-f796-459a-9696-9c92662ba5da"); - - assertThat(e.successMessageReturnValues()) - .hasSize(1) - .contains("Success"); - }); - - verify(interactionClient).listQueues(); - verify(sqsClient).deleteMessageBatch(any(DeleteMessageBatchRequest.class)); - verify(sqsClient).sendMessageBatch(any(SendMessageBatchRequest.class)); - } - - private void setupContext() { - when(context.getFunctionName()).thenReturn("testFunction"); - when(context.getInvokedFunctionArn()).thenReturn("testArn"); - when(context.getFunctionVersion()).thenReturn("1"); - when(context.getMemoryLimitInMB()).thenReturn(10); - } -} \ No newline at end of file diff --git a/powertools-sqs/src/test/resources/sampleSqsBatchEvent.json b/powertools-sqs/src/test/resources/sampleSqsBatchEvent.json deleted file mode 100644 index 8a5fbf309..000000000 --- a/powertools-sqs/src/test/resources/sampleSqsBatchEvent.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "records": [ - { - "messageId": "059f36b4-87a3-44ab-83d2-661975830a7d", - "receiptHandle": "AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082649183", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082649185" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6da", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - } - ] -} \ No newline at end of file diff --git a/powertools-sqs/src/test/resources/sampleSqsBatchEventBatchSize25.json b/powertools-sqs/src/test/resources/sampleSqsBatchEventBatchSize25.json deleted file mode 100644 index 31b8862ad..000000000 --- a/powertools-sqs/src/test/resources/sampleSqsBatchEventBatchSize25.json +++ /dev/null @@ -1,404 +0,0 @@ -{ - "records": [ - { - "messageId": "059f36b4-87a3-44ab-83d2-661975830a7d", - "receiptHandle": "AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082649183", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082649185" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6da", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d3", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d4", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b5", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d6", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b7", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d8", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d9", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d10", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d11", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d12", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d13", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d14", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d15", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d16", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d17", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d18", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d19", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d20", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d21", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d22", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d23", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d24", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d25", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d26", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6d27", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue", - "awsRegion": "us-east-2" - } - ] -} \ No newline at end of file diff --git a/powertools-sqs/src/test/resources/threeMessageSqsBatchEvent.json b/powertools-sqs/src/test/resources/threeMessageSqsBatchEvent.json deleted file mode 100644 index b3b61da3b..000000000 --- a/powertools-sqs/src/test/resources/threeMessageSqsBatchEvent.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "records": [ - { - "messageId": "059f36b4-87a3-44ab-83d2-661975830a7d", - "receiptHandle": "AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082649183", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082649185" - }, - "messageAttributes": {}, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-1:906126917321:sqs-lambda-MySqsQueue-4DYWWJIE97N5", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-8184-9c92662be6da", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": { - "Attribute3" : { - "binaryValue" : "MTEwMA==", - "dataType" : "Binary" - }, - "Attribute2" : { - "stringValue" : "123", - "dataType" : "Number" - }, - "Attribute1" : { - "stringValue" : "AttributeValue1", - "dataType" : "String" - } - }, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-1:906126917321:sqs-lambda-MySqsQueue-4DYWWJIE97N5", - "awsRegion": "us-east-2" - }, - { - "messageId": "2e1424d4-f796-459a-9696-9c92662ba5da", - "receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...", - "body": "Test message.", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1545082650636", - "SenderId": "AIDAIENQZJOLO23YVJ4VO", - "ApproximateFirstReceiveTimestamp": "1545082650649" - }, - "messageAttributes": { - "Attribute2" : { - "stringValue" : "123", - "dataType" : "Number" - } - }, - "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3", - "eventSource": "aws:sqs", - "eventSourceArn": "arn:aws:sqs:us-east-1:906126917321:sqs-lambda-MySqsQueue-4DYWWJIE97N5", - "awsRegion": "us-east-2" - } - ] -} \ No newline at end of file diff --git a/powertools-test-suite/pom.xml b/powertools-test-suite/pom.xml deleted file mode 100644 index 07ac1c969..000000000 --- a/powertools-test-suite/pom.xml +++ /dev/null @@ -1,150 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - - <artifactId>powertools-test-suite</artifactId> - <packaging>jar</packaging> - - <parent> - <artifactId>powertools-parent</artifactId> - <groupId>software.amazon.lambda</groupId> - <version>1.10.2</version> - </parent> - - <name>AWS Lambda Powertools Java library Test Suite</name> - <description> - A suite of tests for interactions between the various Powertools modules. - </description> - <url>https://aws.amazon.com/lambda/</url> - <issueManagement> - <system>GitHub Issues</system> - <url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url> - </issueManagement> - <scm> - <url>https://github.com/awslabs/aws-lambda-powertools-java.git</url> - </scm> - <developers> - <developer> - <name>AWS Lambda Powertools team</name> - <organization>Amazon Web Services</organization> - <organizationUrl>https://aws.amazon.com/</organizationUrl> - </developer> - </developers> - - <properties> - <maven.deploy.skip>true</maven.deploy.skip> - </properties> - - <distributionManagement> - <snapshotRepository> - <id>ossrh</id> - <url>https://aws.oss.sonatype.org/content/repositories/snapshots</url> - </snapshotRepository> - </distributionManagement> - - <dependencies> - <dependency> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-core</artifactId> - </dependency> - <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-jcl</artifactId> - </dependency> - <dependency> - <groupId>com.amazonaws</groupId> - <artifactId>aws-lambda-java-core</artifactId> - </dependency> - <dependency> - <groupId>com.amazonaws</groupId> - <artifactId>aws-lambda-java-events</artifactId> - </dependency> - <dependency> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-logging</artifactId> - </dependency> - <dependency> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-tracing</artifactId> - </dependency> - <dependency> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-sqs</artifactId> - </dependency> - - <!-- Test dependencies --> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-api</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-engine</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.aspectj</groupId> - <artifactId>aspectjweaver</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.assertj</groupId> - <artifactId>assertj-core</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.skyscreamer</groupId> - <artifactId>jsonassert</artifactId> - <scope>test</scope> - </dependency> - </dependencies> - - <build> - <plugins> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>aspectj-maven-plugin</artifactId> - <version>1.14.0</version> - <configuration> - <source>${maven.compiler.source}</source> - <target>${maven.compiler.target}</target> - <complianceLevel>${maven.compiler.target}</complianceLevel> - <aspectLibraries> - <aspectLibrary> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-logging</artifactId> - </aspectLibrary> - <aspectLibrary> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-tracing</artifactId> - </aspectLibrary> - <aspectLibrary> - <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-sqs</artifactId> - </aspectLibrary> - </aspectLibraries> - </configuration> - <executions> - <execution> - <goals> - <goal>compile</goal> - </goals> - </execution> - </executions> - </plugin> - </plugins> - </build> -</project> \ No newline at end of file diff --git a/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/LoggingOrderTest.java b/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/LoggingOrderTest.java deleted file mode 100644 index 7c3e79112..000000000 --- a/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/LoggingOrderTest.java +++ /dev/null @@ -1,171 +0,0 @@ -package software.amazon.lambda.powertools.testsuite; - - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.channels.FileChannel; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.Map; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; -import com.amazonaws.xray.AWSXRay; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.ThreadContext; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import software.amazon.awssdk.core.ResponseInputStream; -import software.amazon.awssdk.http.AbortableInputStream; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectResponse; -import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; -import software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect; -import software.amazon.lambda.powertools.sqs.SqsUtils; -import software.amazon.lambda.powertools.testsuite.handler.LoggingOrderMessageHandler; -import software.amazon.lambda.powertools.testsuite.handler.TracingLoggingStreamMessageHandler; - -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; -import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; - -public class LoggingOrderTest { - - private static final String BUCKET_NAME = "ms-extended-sqs-client"; - private static final String BUCKET_KEY = "c71eb2ae-37e0-4265-8909-32f4153faddf"; - - @Mock - private Context context; - - @Mock - private S3Client s3Client; - - @BeforeEach - void setUp() throws IllegalAccessException, IOException, NoSuchMethodException, InvocationTargetException { - openMocks(this); - SqsUtils.overrideS3Client(s3Client); - ThreadContext.clearAll(); - writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true); - setupContext(); - //Make sure file is cleaned up before running full stack logging regression - FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); - resetLogLevel(Level.INFO); - AWSXRay.beginSegment(LoggingOrderTest.class.getName()); - } - - @AfterEach - void tearDown() { - AWSXRay.endSegment(); - } - - /** - * The SQSEvent payload will be altered by the @SqsLargeMessage annotation. Logging of the event should happen - * after the event has been altered - */ - @Test - public void testThatLoggingAnnotationActsLast() throws IOException { - ResponseInputStream<GetObjectResponse> s3Response = new ResponseInputStream<>(GetObjectResponse.builder().build(), AbortableInputStream.create(new ByteArrayInputStream("A big message".getBytes()))); - - when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3Response); - SQSEvent sqsEvent = messageWithBody("[\"software.amazon.payloadoffloading.PayloadS3Pointer\",{\"s3BucketName\":\"" + BUCKET_NAME + "\",\"s3Key\":\"" + BUCKET_KEY + "\"}]"); - - LoggingOrderMessageHandler requestHandler = new LoggingOrderMessageHandler(); - requestHandler.handleRequest(sqsEvent, context); - - assertThat(Files.lines(Paths.get("target/logfile.json"))) - .hasSize(2) - .satisfies(line -> { - Map<String, Object> actual = parseToMap(line.get(0)); - - String message = actual.get("message").toString(); - - assertThat(message) - .contains("A big message"); - }); - } - - @Test - public void testLoggingAnnotationActsAfterTracingForStreamingHandler() throws IOException { - - ByteArrayOutputStream output = new ByteArrayOutputStream(); - S3EventNotification s3EventNotification = s3EventNotification(); - - TracingLoggingStreamMessageHandler handler = new TracingLoggingStreamMessageHandler(); - handler.handleRequest(new ByteArrayInputStream(new ObjectMapper().writeValueAsBytes(s3EventNotification)), output, context); - - assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8)) - .isNotEmpty(); - } - - private void setupContext() { - when(context.getFunctionName()).thenReturn("testFunction"); - when(context.getInvokedFunctionArn()).thenReturn("testArn"); - when(context.getFunctionVersion()).thenReturn("1"); - when(context.getMemoryLimitInMB()).thenReturn(10); - when(context.getAwsRequestId()).thenReturn("RequestId"); - } - - private void resetLogLevel(Level level) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - Method resetLogLevels = LambdaLoggingAspect.class.getDeclaredMethod("resetLogLevels", Level.class); - resetLogLevels.setAccessible(true); - resetLogLevels.invoke(null, level); - writeStaticField(LambdaLoggingAspect.class, "LEVEL_AT_INITIALISATION", level, true); - } - - private Map<String, Object> parseToMap(String stringAsJson) { - try { - return new ObjectMapper().readValue(stringAsJson, Map.class); - } catch (JsonProcessingException e) { - fail("Failed parsing logger line " + stringAsJson); - return emptyMap(); - } - } - - private S3EventNotification s3EventNotification() { - S3EventNotification.S3EventNotificationRecord record = new S3EventNotification.S3EventNotificationRecord("us-west-2", - "ObjectCreated:Put", - "aws:s3", - null, - "2.1", - new S3EventNotification.RequestParametersEntity("127.0.0.1"), - new S3EventNotification.ResponseElementsEntity("C3D13FE58DE4C810", "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"), - new S3EventNotification.S3Entity("testConfigRule", - new S3EventNotification.S3BucketEntity("mybucket", - new S3EventNotification.UserIdentityEntity("A3NL1KOZZKExample"), - "arn:aws:s3:::mybucket"), - new S3EventNotification.S3ObjectEntity("HappyFace.jpg", - 1024L, - "d41d8cd98f00b204e9800998ecf8427e", - "096fKKXTRTtl3on89fVO.nfljtsv6qko", - "0055AED6DCD90281E5"), - "1.0"), - new S3EventNotification.UserIdentityEntity("AIDAJDPLRKLG7UEXAMPLE") - ); - - return new S3EventNotification(singletonList(record)); - } - - private SQSEvent messageWithBody(String messageBody) { - SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage(); - sqsMessage.setBody(messageBody); - SQSEvent sqsEvent = new SQSEvent(); - sqsEvent.setRecords(singletonList(sqsMessage)); - return sqsEvent; - } -} \ No newline at end of file diff --git a/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/handler/LoggingOrderMessageHandler.java b/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/handler/LoggingOrderMessageHandler.java deleted file mode 100644 index a85c81b1d..000000000 --- a/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/handler/LoggingOrderMessageHandler.java +++ /dev/null @@ -1,17 +0,0 @@ -package software.amazon.lambda.powertools.testsuite.handler; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.sqs.SqsLargeMessage; - -public class LoggingOrderMessageHandler implements RequestHandler<SQSEvent, String> { - - @Override - @SqsLargeMessage - @Logging(logEvent = true) - public String handleRequest(SQSEvent sqsEvent, Context context) { - return sqsEvent.getRecords().get(0).getBody(); - } -} diff --git a/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/handler/TracingLoggingStreamMessageHandler.java b/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/handler/TracingLoggingStreamMessageHandler.java deleted file mode 100644 index d0f2b3ac5..000000000 --- a/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/handler/TracingLoggingStreamMessageHandler.java +++ /dev/null @@ -1,23 +0,0 @@ -package software.amazon.lambda.powertools.testsuite.handler; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Map; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; -import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.tracing.Tracing; - -public class TracingLoggingStreamMessageHandler implements RequestStreamHandler { - - @Logging(logEvent = true) - @Tracing - @Override - public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { - ObjectMapper mapper = new ObjectMapper(); - mapper.writeValue(output, mapper.readValue(input, Map.class)); - } -} diff --git a/powertools-tracing/pom.xml b/powertools-tracing/pom.xml index d2d7efb30..545633c50 100644 --- a/powertools-tracing/pom.xml +++ b/powertools-tracing/pom.xml @@ -1,7 +1,21 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + <project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>powertools-tracing</artifactId> @@ -10,53 +24,35 @@ <parent> <artifactId>powertools-parent</artifactId> <groupId>software.amazon.lambda</groupId> - <version>1.10.2</version> + <version>2.9.0</version> </parent> - <name>AWS Lambda Powertools Java library Tracing</name> + <name>Powertools for AWS Lambda (Java) - Tracing</name> <description> A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. </description> - <url>https://aws.amazon.com/lambda/</url> - <issueManagement> - <system>GitHub Issues</system> - <url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url> - </issueManagement> - - <scm> - <url>https://github.com/awslabs/aws-lambda-powertools-java.git</url> - </scm> - <developers> - <developer> - <name>AWS Lambda Powertools team</name> - <organization>Amazon Web Services</organization> - <organizationUrl>https://aws.amazon.com/</organizationUrl> - </developer> - </developers> - - <distributionManagement> - <snapshotRepository> - <id>ossrh</id> - <url>https://aws.oss.sonatype.org/content/repositories/snapshots</url> - </snapshotRepository> - </distributionManagement> <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> + </dependency> <dependency> <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-core</artifactId> + <artifactId>powertools-common</artifactId> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>aws-core</artifactId> </dependency> <dependency> - <groupId>com.amazonaws</groupId> - <artifactId>aws-lambda-java-core</artifactId> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> </dependency> <dependency> - <groupId>org.aspectj</groupId> - <artifactId>aspectjrt</artifactId> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-core</artifactId> </dependency> <dependency> <groupId>com.amazonaws</groupId> @@ -74,6 +70,14 @@ <groupId>com.amazonaws</groupId> <artifactId>aws-xray-recorder-sdk-aws-sdk-v2-instrumentor</artifactId> </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + </dependency> <!-- Test dependencies --> <dependency> @@ -87,18 +91,25 @@ <scope>test</scope> </dependency> <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + <version>${project.version}</version> + <type>test-jar</type> <scope>test</scope> </dependency> <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> <scope>test</scope> </dependency> <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-inline</artifactId> + <groupId>org.junit-pioneer</groupId> + <artifactId>junit-pioneer</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> <scope>test</scope> </dependency> <dependency> @@ -111,6 +122,81 @@ <artifactId>assertj-core</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> </dependencies> -</project> \ No newline at end of file + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <environmentVariables> + <AWS_LAMBDA_INITIALIZATION_TYPE>on-demand</AWS_LAMBDA_INITIALIZATION_TYPE> + </environmentVariables> + </configuration> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>generate-graalvm-files</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Dorg.graalvm.nativeimage.imagecode=agent + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-tracing,experimental-class-define-support + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>graalvm-native</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>0.11.2</version> + <extensions>true</extensions> + <executions> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <imageName>powertools-tracing</imageName> + <buildArgs> + <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg> + <buildArg>--add-opens java.base/java.lang=ALL-UNNAMED</buildArg> + <buildArg>--enable-url-protocols=http</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>--verbose</buildArg> + <buildArg>--native-image-info</buildArg> + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> + <buildArg>-H:+ReportExceptionStackTraces</buildArg> + </buildArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/CaptureMode.java b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/CaptureMode.java index 9fd09e8ee..29d10d188 100644 --- a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/CaptureMode.java +++ b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/CaptureMode.java @@ -1,3 +1,17 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.tracing; public enum CaptureMode { diff --git a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/Tracing.java b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/Tracing.java index ef9898a4c..432e475a3 100644 --- a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/Tracing.java +++ b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/Tracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing; import java.lang.annotation.ElementType; @@ -20,7 +21,7 @@ /** * {@code Tracing} is used to signal that the annotated method should - * be extended with the Powertools tracing functionality. + * be extended with the Powertools for AWS Lambda (Java) tracing functionality. * * <p>{@code Tracing} provides functionality to reduce the overhead * of performing common tracing tasks.</p> @@ -39,7 +40,7 @@ * to a sub segment named after the method.</p> * * <p>To disable this functionality you can specify {@code @Tracing( captureError = false)}</p> - *e + * e * <p>All traces have a namespace set. If {@code @Tracing( namespace = "ExampleService")} is set * this takes precedent over any value set in the environment variable {@code POWER_TOOLS_SERVICE_NAME}. * If both are undefined then the value will default to {@code service_undefined}</p> @@ -48,20 +49,8 @@ @Target(ElementType.METHOD) public @interface Tracing { String namespace() default ""; - /** - * @deprecated As of release 1.2.0, replaced by captureMode() - * in order to support different modes and support via - * environment variables - */ - @Deprecated - boolean captureResponse() default true; - /** - * @deprecated As of release 1.2.0, replaced by captureMode() - * in order to support different modes and support via - * environment variables - */ - @Deprecated - boolean captureError() default true; + String segmentName() default ""; + CaptureMode captureMode() default CaptureMode.ENVIRONMENT_VAR; } diff --git a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/TracingUtils.java b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/TracingUtils.java index 0e956e539..954ed7da4 100644 --- a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/TracingUtils.java +++ b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/TracingUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,31 +11,38 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing; -import java.util.function.Consumer; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.serviceName; + import com.amazonaws.xray.AWSXRay; import com.amazonaws.xray.entities.Entity; import com.amazonaws.xray.entities.Subsegment; import com.fasterxml.jackson.databind.ObjectMapper; - -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.serviceName; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A class of helper functions to add additional functionality and ease * of use. - * */ public final class TracingUtils { + private static final Logger LOG = LoggerFactory.getLogger(TracingUtils.class); private static ObjectMapper objectMapper; /** * Put an annotation to the current subsegment with a String value. * - * @param key the key of the annotation + * @param key the key of the annotation * @param value the value of the annotation */ public static void putAnnotation(String key, String value) { + if (!isValidAnnotationKey(key)) { + LOG.warn("Ignoring annotation with unsupported characters in key: {}", key); + return; + } AWSXRay.getCurrentSubsegmentOptional() .ifPresent(segment -> segment.putAnnotation(key, value)); } @@ -43,33 +50,47 @@ public static void putAnnotation(String key, String value) { /** * Put an annotation to the current subsegment with a Number value. * - * @param key the key of the annotation + * @param key the key of the annotation * @param value the value of the annotation */ public static void putAnnotation(String key, Number value) { + if (!isValidAnnotationKey(key)) { + LOG.warn("Ignoring annotation with unsupported characters in key: {}", key); + return; + } AWSXRay.getCurrentSubsegmentOptional() - .ifPresent(segment -> segment.putAnnotation(key, value)); + .ifPresent(segment -> segment.putAnnotation(key, value)); + } + + /** + Make sure that the annotation key is valid according to + <a href='https://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html#api-segmentdocuments-annotations'>the documentation</a>. + + Annotation keys that are added that are invalid are ignored by x-ray. + **/ + private static boolean isValidAnnotationKey(String key) { + return key.matches("^[a-zA-Z0-9_]+$"); } /** * Put an annotation to the current subsegment with a Boolean value. * - * @param key the key of the annotation + * @param key the key of the annotation * @param value the value of the annotation */ public static void putAnnotation(String key, Boolean value) { AWSXRay.getCurrentSubsegmentOptional() - .ifPresent(segment -> segment.putAnnotation(key, value)); + .ifPresent(segment -> segment.putAnnotation(key, value)); } /** * Put additional metadata for the current subsegment. - * + * <p> * The namespace used will be the namespace of the current subsegment if it * is set else it will follow the namespace process as described in * {@link Tracing} * - * @param key the key of the metadata + * @param key the key of the metadata * @param value the value of the metadata */ public static void putMetadata(String key, Object value) { @@ -83,8 +104,8 @@ public static void putMetadata(String key, Object value) { * Put additional metadata for the current subsegment. * * @param namespace the namespace of the metadata - * @param key the key of the metadata - * @param value the value of the metadata + * @param key the key of the metadata + * @param value the value of the metadata */ public static void putMetadata(String namespace, String key, Object value) { AWSXRay.getCurrentSubsegmentOptional() @@ -94,14 +115,14 @@ public static void putMetadata(String namespace, String key, Object value) { /** * Adds a new subsegment around the passed consumer. This also provides access to * the newly created subsegment. - * + * <p> * The namespace used follows the flow as described in {@link Tracing} - * + * <p> * This method is intended for use with multi-threaded programming where the * context is lost between threads. * - * @param name the name of the subsegment - * @param entity the current x-ray context + * @param name the name of the subsegment + * @param entity the current x-ray context * @param subsegment the x-ray subsegment for the wrapped consumer */ public static void withEntitySubsegment(String name, Entity entity, Consumer<Subsegment> subsegment) { @@ -112,16 +133,17 @@ public static void withEntitySubsegment(String name, Entity entity, Consumer<Sub /** * Adds a new subsegment around the passed consumer. This also provides access to * the newly created subsegment. - * + * <p> * This method is intended for use with multi-threaded programming where the * context is lost between threads. * - * @param namespace the namespace of the subsegment - * @param name the name of the subsegment - * @param entity the current x-ray context + * @param namespace the namespace of the subsegment + * @param name the name of the subsegment + * @param entity the current x-ray context * @param subsegment the x-ray subsegment for the wrapped consumer */ - public static void withEntitySubsegment(String namespace, String name, Entity entity, Consumer<Subsegment> subsegment) { + public static void withEntitySubsegment(String namespace, String name, Entity entity, + Consumer<Subsegment> subsegment) { AWSXRay.setTraceEntity(entity); withSubsegment(namespace, name, subsegment); } @@ -129,10 +151,10 @@ public static void withEntitySubsegment(String namespace, String name, Entity en /** * Adds a new subsegment around the passed consumer. This also provides access to * the newly created subsegment. - * + * <p> * The namespace used follows the flow as described in {@link Tracing} * - * @param name the name of the subsegment + * @param name the name of the subsegment * @param subsegment the x-ray subsegment for the wrapped consumer */ public static void withSubsegment(String name, Consumer<Subsegment> subsegment) { @@ -143,8 +165,8 @@ public static void withSubsegment(String name, Consumer<Subsegment> subsegment) * Adds a new subsegment around the passed consumer. This also provides access to * the newly created subsegment. * - * @param namespace the namespace for the subsegment - * @param name the name of the subsegment + * @param namespace the namespace for the subsegment + * @param name the name of the subsegment * @param subsegment the x-ray subsegment for the wrapped consumer */ public static void withSubsegment(String namespace, String name, Consumer<Subsegment> subsegment) { diff --git a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/internal/LambdaTracingAspect.java b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/internal/LambdaTracingAspect.java index 19fc1b038..a4a48532c 100644 --- a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/internal/LambdaTracingAspect.java +++ b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/internal/LambdaTracingAspect.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,26 +11,25 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing.internal; -import java.util.function.Supplier; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.coldStartDone; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.isColdStart; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.isHandlerMethod; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.isSamLocal; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.serviceName; +import static software.amazon.lambda.powertools.tracing.TracingUtils.objectMapper; + import com.amazonaws.xray.AWSXRay; import com.amazonaws.xray.entities.Subsegment; +import java.util.function.Supplier; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import software.amazon.lambda.powertools.tracing.Tracing; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.coldStartDone; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isColdStart; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isHandlerMethod; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isSamLocal; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnRequestHandler; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnStreamHandler; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.serviceName; -import static software.amazon.lambda.powertools.tracing.TracingUtils.objectMapper; - @Aspect public final class LambdaTracingAspect { @SuppressWarnings({"EmptyMethod"}) @@ -44,11 +43,11 @@ public Object around(ProceedingJoinPoint pjp, Object[] proceedArgs = pjp.getArgs(); Subsegment segment = AWSXRay.beginSubsegment( - customSegmentNameOrDefault(tracing, - () -> "## " + pjp.getSignature().getName())); + customSegmentNameOrDefault(tracing, + () -> "## " + pjp.getSignature().getName())); segment.setNamespace(namespace(tracing)); - if (placedOnHandlerMethod(pjp)) { + if (isHandlerMethod(pjp)) { segment.putAnnotation("ColdStart", isColdStart()); segment.putAnnotation("Service", namespace(tracing)); } @@ -59,17 +58,19 @@ public Object around(ProceedingJoinPoint pjp, try { Object methodReturn = pjp.proceed(proceedArgs); if (captureResponse) { - segment.putMetadata(namespace(tracing), pjp.getSignature().getName() + " response", null != objectMapper() ? objectMapper().writeValueAsString(methodReturn): methodReturn); + segment.putMetadata(namespace(tracing), pjp.getSignature().getName() + " response", + null != objectMapper() ? objectMapper().writeValueAsString(methodReturn) : methodReturn); } - if (placedOnHandlerMethod(pjp)) { + if (isHandlerMethod(pjp)) { coldStartDone(); } return methodReturn; } catch (Exception e) { if (captureError) { - segment.putMetadata(namespace(tracing), pjp.getSignature().getName() + " error", null != objectMapper() ? objectMapper().writeValueAsString(e) : e); + segment.putMetadata(namespace(tracing), pjp.getSignature().getName() + " error", + null != objectMapper() ? objectMapper().writeValueAsString(e) : e); } throw e; } finally { @@ -82,8 +83,8 @@ public Object around(ProceedingJoinPoint pjp, private boolean captureResponse(Tracing powerToolsTracing) { switch (powerToolsTracing.captureMode()) { case ENVIRONMENT_VAR: - boolean captureResponse = environmentVariable("POWERTOOLS_TRACER_CAPTURE_RESPONSE"); - return isEnvironmentVariableSet("POWERTOOLS_TRACER_CAPTURE_RESPONSE") ? captureResponse : powerToolsTracing.captureResponse(); + return isEnvironmentVariableSet("POWERTOOLS_TRACER_CAPTURE_RESPONSE") + && environmentVariable("POWERTOOLS_TRACER_CAPTURE_RESPONSE"); case RESPONSE: case RESPONSE_AND_ERROR: return true; @@ -96,8 +97,8 @@ private boolean captureResponse(Tracing powerToolsTracing) { private boolean captureError(Tracing powerToolsTracing) { switch (powerToolsTracing.captureMode()) { case ENVIRONMENT_VAR: - boolean captureError = environmentVariable("POWERTOOLS_TRACER_CAPTURE_ERROR"); - return isEnvironmentVariableSet("POWERTOOLS_TRACER_CAPTURE_ERROR") ? captureError : powerToolsTracing.captureError(); + return isEnvironmentVariableSet("POWERTOOLS_TRACER_CAPTURE_ERROR") + && environmentVariable("POWERTOOLS_TRACER_CAPTURE_ERROR"); case ERROR: case RESPONSE_AND_ERROR: return true; @@ -116,11 +117,6 @@ private String namespace(Tracing powerToolsTracing) { return powerToolsTracing.namespace().isEmpty() ? serviceName() : powerToolsTracing.namespace(); } - private boolean placedOnHandlerMethod(ProceedingJoinPoint pjp) { - return isHandlerMethod(pjp) - && (placedOnRequestHandler(pjp) || placedOnStreamHandler(pjp)); - } - private boolean environmentVariable(String key) { return Boolean.parseBoolean(SystemWrapper.getenv(key)); } diff --git a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/internal/SystemWrapper.java b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/internal/SystemWrapper.java index da1a92ced..c66b8b7ee 100644 --- a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/internal/SystemWrapper.java +++ b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/internal/SystemWrapper.java @@ -1,3 +1,17 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.tracing.internal; public class SystemWrapper { diff --git a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/internal/TracingUserAgentInterceptor.java b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/internal/TracingUserAgentInterceptor.java new file mode 100644 index 000000000..489243517 --- /dev/null +++ b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/internal/TracingUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.tracing.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-tracing module is on the classpath. + */ +public final class TracingUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("tracing"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-tracing/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-tracing/jni-config.json b/powertools-tracing/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-tracing/jni-config.json new file mode 100644 index 000000000..2c4de0562 --- /dev/null +++ b/powertools-tracing/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-tracing/jni-config.json @@ -0,0 +1,26 @@ +[ +{ + "name":"java.lang.Boolean", + "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.String", + "methods":[{"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }] +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"org.apache.maven.surefire.booter.ForkedBooter", + "methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }] +}, +{ + "name":"sun.instrument.InstrumentationImpl", + "methods":[{"name":"<init>","parameterTypes":["long","boolean","boolean","boolean"] }, {"name":"loadClassAndCallAgentmain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"loadClassAndCallPremain","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"transform","parameterTypes":["java.lang.Module","java.lang.ClassLoader","java.lang.String","java.lang.Class","java.security.ProtectionDomain","byte[]","boolean"] }] +}, +{ + "name":"sun.management.VMManagementImpl", + "fields":[{"name":"compTimeMonitoringSupport"}, {"name":"currentThreadCpuTimeSupport"}, {"name":"objectMonitorUsageSupport"}, {"name":"otherThreadCpuTimeSupport"}, {"name":"remoteDiagnosticCommandsSupport"}, {"name":"synchronizerUsageSupport"}, {"name":"threadAllocatedMemorySupport"}, {"name":"threadContentionMonitoringSupport"}] +} +] diff --git a/powertools-tracing/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-tracing/reflect-config.json b/powertools-tracing/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-tracing/reflect-config.json new file mode 100644 index 000000000..97e0a7a86 --- /dev/null +++ b/powertools-tracing/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-tracing/reflect-config.json @@ -0,0 +1,369 @@ +[ +{ + "name":"[Lcom.fasterxml.jackson.databind.deser.Deserializers;" +}, +{ + "name":"[Lcom.fasterxml.jackson.databind.ser.BeanSerializerModifier;" +}, +{ + "name":"[Lcom.fasterxml.jackson.databind.ser.Serializers;" +}, +{ + "name":"[Ljava.lang.StackTraceElement;" +}, +{ + "name":"[Ljava.lang.Throwable;" +}, +{ + "name":"com.amazonaws.services.lambda.runtime.Context", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getAwsRequestId","parameterTypes":[] }, {"name":"getClientContext","parameterTypes":[] }, {"name":"getFunctionName","parameterTypes":[] }, {"name":"getFunctionVersion","parameterTypes":[] }, {"name":"getIdentity","parameterTypes":[] }, {"name":"getInvokedFunctionArn","parameterTypes":[] }, {"name":"getLogGroupName","parameterTypes":[] }, {"name":"getLogStreamName","parameterTypes":[] }, {"name":"getLogger","parameterTypes":[] }, {"name":"getMemoryLimitInMB","parameterTypes":[] }, {"name":"getRemainingTimeInMillis","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.xray.entities.Cause", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.xray.entities.Entity", + "queryAllDeclaredMethods":true +}, +{ + "name":"com.amazonaws.xray.entities.EntityImpl", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "methods":[{"name":"getAnnotations","parameterTypes":[] }, {"name":"getAws","parameterTypes":[] }, {"name":"getCause","parameterTypes":[] }, {"name":"getEndTime","parameterTypes":[] }, {"name":"getHttp","parameterTypes":[] }, {"name":"getId","parameterTypes":[] }, {"name":"getMetadata","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getNamespace","parameterTypes":[] }, {"name":"getParentId","parameterTypes":[] }, {"name":"getSql","parameterTypes":[] }, {"name":"getStartTime","parameterTypes":[] }, {"name":"getSubsegments","parameterTypes":[] }, {"name":"getTraceId","parameterTypes":[] }, {"name":"isError","parameterTypes":[] }, {"name":"isFault","parameterTypes":[] }, {"name":"isInProgress","parameterTypes":[] }, {"name":"isThrottle","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.xray.entities.Segment", + "queryAllDeclaredMethods":true +}, +{ + "name":"com.amazonaws.xray.entities.SegmentImpl", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getOrigin","parameterTypes":[] }, {"name":"getResourceArn","parameterTypes":[] }, {"name":"getService","parameterTypes":[] }, {"name":"getUser","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.xray.entities.Subsegment", + "queryAllDeclaredMethods":true +}, +{ + "name":"com.amazonaws.xray.entities.SubsegmentImpl", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getNamespace","parameterTypes":[] }, {"name":"getPrecursorIds","parameterTypes":[] }] +}, +{ + "name":"com.amazonaws.xray.strategy.sampling.manifest.SamplingRuleManifest", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setDefaultRule","parameterTypes":["com.amazonaws.xray.strategy.sampling.rule.SamplingRule"] }, {"name":"setRules","parameterTypes":["java.util.List"] }, {"name":"setVersion","parameterTypes":["int"] }] +}, +{ + "name":"com.amazonaws.xray.strategy.sampling.reservoir.Reservoir", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.amazonaws.xray.strategy.sampling.rule.SamplingRule", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setFixedTarget","parameterTypes":["int"] }, {"name":"setRate","parameterTypes":["float"] }] +}, +{ + "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.fasterxml.jackson.databind.ser.std.ToStringSerializer", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"com.sun.tools.attach.VirtualMachine" +}, +{ + "name":"double", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.io.InputStream" +}, +{ + "name":"java.io.OutputStream" +}, +{ + "name":"java.io.Serializable", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"handleRequest response"}] +}, +{ + "name":"java.lang.AutoCloseable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Class", + "methods":[{"name":"forName","parameterTypes":["java.lang.String"] }, {"name":"getAnnotatedInterfaces","parameterTypes":[] }, {"name":"getAnnotatedSuperclass","parameterTypes":[] }, {"name":"getDeclaredMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getModule","parameterTypes":[] }, {"name":"getNestHost","parameterTypes":[] }, {"name":"getNestMembers","parameterTypes":[] }, {"name":"getPermittedSubclasses","parameterTypes":[] }, {"name":"getRecordComponents","parameterTypes":[] }, {"name":"isNestmateOf","parameterTypes":["java.lang.Class"] }, {"name":"isRecord","parameterTypes":[] }, {"name":"isSealed","parameterTypes":[] }] +}, +{ + "name":"java.lang.ClassLoader", + "methods":[{"name":"getDefinedPackage","parameterTypes":["java.lang.String"] }, {"name":"getUnnamedModule","parameterTypes":[] }, {"name":"registerAsParallelCapable","parameterTypes":[] }] +}, +{ + "name":"java.lang.Exception", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Module", + "methods":[{"name":"addExports","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"addReads","parameterTypes":["java.lang.Module"] }, {"name":"canRead","parameterTypes":["java.lang.Module"] }, {"name":"getClassLoader","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPackages","parameterTypes":[] }, {"name":"getResourceAsStream","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"isNamed","parameterTypes":[] }, {"name":"isOpen","parameterTypes":["java.lang.String","java.lang.Module"] }] +}, +{ + "name":"java.lang.Object", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"handleRequest response"}], + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"clone","parameterTypes":[] }, {"name":"getClass","parameterTypes":[] }, {"name":"getHandleRequest response","parameterTypes":[] }, {"name":"isHandleRequest response","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"java.lang.ProcessEnvironment", + "fields":[{"name":"theCaseInsensitiveEnvironment"}, {"name":"theEnvironment"}] +}, +{ + "name":"java.lang.ProcessHandle", + "methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime", + "methods":[{"name":"version","parameterTypes":[] }] +}, +{ + "name":"java.lang.Runtime$Version", + "methods":[{"name":"feature","parameterTypes":[] }] +}, +{ + "name":"java.lang.RuntimeException", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.StackTraceElement", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.StackWalker" +}, +{ + "name":"java.lang.String" +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getSecurityManager","parameterTypes":[] }] +}, +{ + "name":"java.lang.Thread", + "fields":[{"name":"threadLocalRandomProbe"}], + "methods":[{"name":"getContextClassLoader","parameterTypes":[] }] +}, +{ + "name":"java.lang.Throwable", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "methods":[{"name":"getCause","parameterTypes":[] }, {"name":"getLocalizedMessage","parameterTypes":[] }, {"name":"getMessage","parameterTypes":[] }, {"name":"getStackTrace","parameterTypes":[] }, {"name":"getSuppressed","parameterTypes":[] }] +}, +{ + "name":"java.lang.annotation.Retention", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.annotation.Target", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.invoke.MethodHandle", + "methods":[{"name":"bindTo","parameterTypes":["java.lang.Object"] }, {"name":"invokeWithArguments","parameterTypes":["java.lang.Object[]"] }] +}, +{ + "name":"java.lang.invoke.MethodHandles", + "methods":[{"name":"lookup","parameterTypes":[] }] +}, +{ + "name":"java.lang.invoke.MethodHandles$Lookup", + "methods":[{"name":"findVirtual","parameterTypes":["java.lang.Class","java.lang.String","java.lang.invoke.MethodType"] }] +}, +{ + "name":"java.lang.invoke.MethodType", + "methods":[{"name":"methodType","parameterTypes":["java.lang.Class","java.lang.Class[]"] }] +}, +{ + "name":"java.lang.reflect.AccessibleObject", + "methods":[{"name":"setAccessible","parameterTypes":["boolean"] }] +}, +{ + "name":"java.lang.reflect.AnnotatedArrayType", + "methods":[{"name":"getAnnotatedGenericComponentType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.AnnotatedType", + "methods":[{"name":"getType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Executable", + "methods":[{"name":"getAnnotatedExceptionTypes","parameterTypes":[] }, {"name":"getAnnotatedParameterTypes","parameterTypes":[] }, {"name":"getAnnotatedReceiverType","parameterTypes":[] }, {"name":"getParameterCount","parameterTypes":[] }, {"name":"getParameters","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"getAnnotatedReturnType","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Parameter", + "methods":[{"name":"getModifiers","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"isNamePresent","parameterTypes":[] }] +}, +{ + "name":"java.security.AccessController", + "methods":[{"name":"doPrivileged","parameterTypes":["java.security.PrivilegedAction"] }, {"name":"doPrivileged","parameterTypes":["java.security.PrivilegedExceptionAction"] }] +}, +{ + "name":"java.security.SecureRandomParameters" +}, +{ + "name":"java.util.AbstractMap", + "fields":[{"name":"handleRequest response"}], + "methods":[{"name":"getHandleRequest response","parameterTypes":[] }, {"name":"isHandleRequest response","parameterTypes":[] }] +}, +{ + "name":"java.util.Collections$UnmodifiableMap", + "fields":[{"name":"m"}] +}, +{ + "name":"java.util.Map", + "fields":[{"name":"handleRequest response"}] +}, +{ + "name":"java.util.concurrent.ConcurrentHashMap", + "fields":[{"name":"handleRequest response"}], + "methods":[{"name":"getHandleRequest response","parameterTypes":[] }, {"name":"isHandleRequest response","parameterTypes":[] }] +}, +{ + "name":"java.util.concurrent.ConcurrentMap", + "fields":[{"name":"handleRequest response"}] +}, +{ + "name":"java.util.concurrent.ForkJoinTask", + "fields":[{"name":"aux"}, {"name":"status"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.Striped64", + "fields":[{"name":"base"}, {"name":"cellsBusy"}] +}, +{ + "name":"jdk.internal.misc.Unsafe" +}, + +{ + "name":"org.apache.commons.logging.LogFactory" +}, +{ + "name":"org.apache.commons.logging.impl.Jdk14Logger", + "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }, {"name":"setLogFactory","parameterTypes":["org.apache.commons.logging.LogFactory"] }] +}, +{ + "name":"org.apache.commons.logging.impl.Log4JLogger" +}, +{ + "name":"org.apache.commons.logging.impl.LogFactoryImpl", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"org.apache.commons.logging.impl.WeakHashtable", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor", + "fields":[{"name":"isColdStart"}] +}, +{ + "name":"software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabled", + "methods":[{"name":"handleRequest","parameterTypes":["java.lang.Object","com.amazonaws.services.lambda.runtime.Context"] }] +}, +{ + "name":"software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledExplicitlyForResponseAndError", + "methods":[{"name":"handleRequest","parameterTypes":["java.lang.Object","com.amazonaws.services.lambda.runtime.Context"] }] +}, +{ + "name":"software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledForError", + "methods":[{"name":"handleRequest","parameterTypes":["java.lang.Object","com.amazonaws.services.lambda.runtime.Context"] }] +}, +{ + "name":"software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledForResponse", + "methods":[{"name":"handleRequest","parameterTypes":["java.lang.Object","com.amazonaws.services.lambda.runtime.Context"] }] +}, +{ + "name":"software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledForResponseWithCustomMapper", + "methods":[{"name":"handleRequest","parameterTypes":["java.lang.Object","com.amazonaws.services.lambda.runtime.Context"] }] +}, +{ + "name":"software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledForResponseWithCustomMapper$ParentClass", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledForStream", + "methods":[{"name":"handleRequest","parameterTypes":["java.io.InputStream","java.io.OutputStream","com.amazonaws.services.lambda.runtime.Context"] }] +}, +{ + "name":"software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledForStreamWithNoMetaData", + "methods":[{"name":"handleRequest","parameterTypes":["java.io.InputStream","java.io.OutputStream","com.amazonaws.services.lambda.runtime.Context"] }] +}, +{ + "name":"software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledWithException", + "methods":[{"name":"handleRequest","parameterTypes":["java.lang.Object","com.amazonaws.services.lambda.runtime.Context"] }] +}, +{ + "name":"software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledWithNoMetaData", + "methods":[{"name":"handleRequest","parameterTypes":["java.lang.Object","com.amazonaws.services.lambda.runtime.Context"] }] +}, + +{ + "name":"software.amazon.lambda.powertools.tracing.nonhandler.PowerToolNonHandler", + "methods":[{"name":"doSomething","parameterTypes":[] }, {"name":"doSomethingCustomName","parameterTypes":[] }] +}, +{ + "name":"sun.reflect.ReflectionFactory", + "methods":[{"name":"getReflectionFactory","parameterTypes":[] }, {"name":"newConstructorForSerialization","parameterTypes":["java.lang.Class","java.lang.reflect.Constructor"] }] +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.SHA", + "methods":[{"name":"<init>","parameterTypes":[] }] +}, +{ + "name":"software.amazon.lambda.powertools.tracing.internal.TracingUserAgentInterceptor", + "methods":[{"name":"<init>","parameterTypes":[] }] +} +] diff --git a/powertools-tracing/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-tracing/resource-config.json b/powertools-tracing/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-tracing/resource-config.json new file mode 100644 index 000000000..64408b8b6 --- /dev/null +++ b/powertools-tracing/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-tracing/resource-config.json @@ -0,0 +1,33 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/com.fasterxml.jackson.databind.Module\\E" + }, { + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.apache.commons.logging.LogFactory\\E" + }, { + "pattern":"\\QMETA-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory\\E" + }, { + "pattern":"\\QMETA-INF/services/org.assertj.core.configuration.Configuration\\E" + }, { + "pattern":"\\QMETA-INF/services/org.assertj.core.presentation.Representation\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\Qcom/amazonaws/xray/sdk.properties\\E" + }, { + "pattern":"\\Qcom/amazonaws/xray/strategy/sampling/DefaultSamplingRules.json\\E" + }, { + "pattern":"\\Qcommons-logging.properties\\E" + }, { + "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" + }, { + "pattern":"\\Qsoftware/amazon/awssdk/global/handlers/execution.interceptors\\E" + }]}, + "bundles":[] +} diff --git a/powertools-tracing/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-tracing/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..5f15a8000 --- /dev/null +++ b/powertools-tracing/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.tracing.internal.TracingUserAgentInterceptor diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/TracingUtilsTest.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/TracingUtilsTest.java index d2a96ec65..db9807dbd 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/TracingUtilsTest.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/TracingUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,23 +11,21 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing; -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.xray.AWSXRay; -import com.amazonaws.xray.entities.Entity; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static software.amazon.lambda.powertools.tracing.TracingUtils.withEntitySubsegment; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static software.amazon.lambda.powertools.tracing.TracingUtils.withEntitySubsegment; +import com.amazonaws.xray.AWSXRay; +import com.amazonaws.xray.entities.Entity; class TracingUtilsTest { - @BeforeEach void setUp() { AWSXRay.beginSegment("test"); @@ -51,12 +49,11 @@ void shouldSetAnnotationOnCurrentSubSegment() { TracingUtils.putAnnotation("booleanKey", false); assertThat(AWSXRay.getTraceEntity().getAnnotations()) - .hasSize(3) - .contains( - entry("stringKey", "val"), - entry("numberKey", 10), - entry("booleanKey", false) - ); + .hasSize(3) + .contains( + entry("stringKey", "val"), + entry("numberKey", 10), + entry("booleanKey", false)); } @Test @@ -76,10 +73,8 @@ void shouldSetMetadataOnCurrentSubSegment() { assertThat(AWSXRay.getTraceEntity().getMetadata()) .hasSize(1) .containsKey("service_undefined") - .satisfies(map -> - assertThat(map.get("service_undefined")) - .containsEntry("key", "val") - ); + .satisfies(map -> assertThat(map.get("service_undefined")) + .containsEntry("key", "val")); } @Test @@ -92,16 +87,11 @@ void shouldNotSetMetaDataIfNoCurrentSubSegment() { @Test void shouldInvokeCodeBlockWrappedWithinSubsegment() { - Context test = mock(Context.class); - TracingUtils.withSubsegment("testSubSegment", subsegment -> { subsegment.putAnnotation("key", "val"); subsegment.putMetadata("key", "val"); - test.getFunctionName(); }); - verify(test).getFunctionName(); - assertThat(AWSXRay.getTraceEntity().getSubsegments()) .hasSize(1) .allSatisfy(subsegment -> { @@ -121,17 +111,30 @@ void shouldInvokeCodeBlockWrappedWithinSubsegment() { } @Test - void shouldInvokeCodeBlockWrappedWithinNamespacedSubsegment() { - Context test = mock(Context.class); + void shouldNotAddAnnotationIfInvalidCharacterInKey() { + AWSXRay.beginSubsegment("subSegment"); + String inputKey = "stringKey with spaces"; + TracingUtils.putAnnotation(inputKey, "val"); + AWSXRay.getCurrentSubsegmentOptional() + .ifPresent(segment -> assertThat(segment.getAnnotations()).size().isEqualTo(0)); + } + + @Test + void shouldAddAnnotationIfValidCharactersInKey() { + AWSXRay.beginSubsegment("subSegment"); + String inputKey = "validKey"; + TracingUtils.putAnnotation(inputKey, "val"); + AWSXRay.getCurrentSubsegmentOptional() + .ifPresent(segment -> assertThat(segment.getAnnotations()).size().isEqualTo(1)); + } + @Test + void shouldInvokeCodeBlockWrappedWithinNamespacedSubsegment() { TracingUtils.withSubsegment("testNamespace", "testSubSegment", subsegment -> { subsegment.putAnnotation("key", "val"); subsegment.putMetadata("key", "val"); - test.getFunctionName(); }); - verify(test).getFunctionName(); - assertThat(AWSXRay.getTraceEntity().getSubsegments()) .hasSize(1) .allSatisfy(subsegment -> { @@ -152,20 +155,15 @@ void shouldInvokeCodeBlockWrappedWithinNamespacedSubsegment() { @Test void shouldInvokeCodeBlockWrappedWithinEntitySubsegment() throws InterruptedException { - Context test = mock(Context.class); - Entity traceEntity = AWSXRay.getTraceEntity(); Thread thread = new Thread(() -> withEntitySubsegment("testSubSegment", traceEntity, subsegment -> { subsegment.putAnnotation("key", "val"); - test.getFunctionName(); })); thread.start(); thread.join(); - verify(test).getFunctionName(); - assertThat(AWSXRay.getTraceEntity().getSubsegments()) .hasSize(1) .allSatisfy(subsegment -> { @@ -183,20 +181,16 @@ void shouldInvokeCodeBlockWrappedWithinEntitySubsegment() throws InterruptedExce @Test void shouldInvokeCodeBlockWrappedWithinNamespacedEntitySubsegment() throws InterruptedException { - Context test = mock(Context.class); - Entity traceEntity = AWSXRay.getTraceEntity(); - Thread thread = new Thread(() -> withEntitySubsegment("testNamespace", "testSubSegment", traceEntity, subsegment -> { - subsegment.putAnnotation("key", "val"); - test.getFunctionName(); - })); + Thread thread = new Thread( + () -> withEntitySubsegment("testNamespace", "testSubSegment", traceEntity, subsegment -> { + subsegment.putAnnotation("key", "val"); + })); thread.start(); thread.join(); - verify(test).getFunctionName(); - assertThat(AWSXRay.getTraceEntity().getSubsegments()) .hasSize(1) .allSatisfy(subsegment -> { @@ -211,4 +205,4 @@ void shouldInvokeCodeBlockWrappedWithinNamespacedEntitySubsegment() throws Inter .containsEntry("key", "val"); }); } -} \ No newline at end of file +} diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerToolDisabled.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerToolDisabled.java index 78878ffe5..1a9dc851a 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerToolDisabled.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerToolDisabled.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing.handlers; import com.amazonaws.services.lambda.runtime.Context; diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerToolDisabledForStream.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerToolDisabledForStream.java index 80f37b8b6..49bc4e095 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerToolDisabledForStream.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerToolDisabledForStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,13 +11,13 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.tracing.handlers; -import java.io.InputStream; -import java.io.OutputStream; +package software.amazon.lambda.powertools.tracing.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import java.io.InputStream; +import java.io.OutputStream; public class PowerToolDisabledForStream implements RequestStreamHandler { diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabled.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabled.java index 3be79fb76..5af3c65af 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabled.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabled.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing.handlers; import com.amazonaws.services.lambda.runtime.Context; diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledExplicitlyForResponseAndError.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledExplicitlyForResponseAndError.java index cd026f427..88b42c690 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledExplicitlyForResponseAndError.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledExplicitlyForResponseAndError.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,14 +11,15 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing.handlers; +import static software.amazon.lambda.powertools.tracing.CaptureMode.RESPONSE_AND_ERROR; + import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import software.amazon.lambda.powertools.tracing.Tracing; -import static software.amazon.lambda.powertools.tracing.CaptureMode.RESPONSE_AND_ERROR; - public class PowerTracerToolEnabledExplicitlyForResponseAndError implements RequestHandler<Object, Object> { @Override diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForError.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForError.java index c84d25763..47e23ed18 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForError.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForError.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,14 +11,15 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing.handlers; +import static software.amazon.lambda.powertools.tracing.CaptureMode.ERROR; + import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import software.amazon.lambda.powertools.tracing.Tracing; -import static software.amazon.lambda.powertools.tracing.CaptureMode.ERROR; - public class PowerTracerToolEnabledForError implements RequestHandler<Object, Object> { @Override diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForResponse.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForResponse.java index 1e82f2148..be0fe9b24 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForResponse.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,14 +11,15 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing.handlers; +import static software.amazon.lambda.powertools.tracing.CaptureMode.RESPONSE; + import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import software.amazon.lambda.powertools.tracing.Tracing; -import static software.amazon.lambda.powertools.tracing.CaptureMode.RESPONSE; - public class PowerTracerToolEnabledForResponse implements RequestHandler<Object, Object> { @Override diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForResponseWithCustomMapper.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForResponseWithCustomMapper.java index b7c908473..a18b1580d 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForResponseWithCustomMapper.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForResponseWithCustomMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,9 +11,11 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing.handlers; -import java.io.IOException; +import static software.amazon.lambda.powertools.tracing.CaptureMode.RESPONSE; + import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.fasterxml.jackson.core.JsonGenerator; @@ -21,11 +23,10 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; import software.amazon.lambda.powertools.tracing.Tracing; import software.amazon.lambda.powertools.tracing.TracingUtils; -import static software.amazon.lambda.powertools.tracing.CaptureMode.RESPONSE; - public class PowerTracerToolEnabledForResponseWithCustomMapper implements RequestHandler<Object, Object> { static { ObjectMapper objectMapper = new ObjectMapper(); @@ -35,6 +36,7 @@ public class PowerTracerToolEnabledForResponseWithCustomMapper implements Reques TracingUtils.defaultObjectMapper(objectMapper); } + @Override @Tracing(namespace = "lambdaHandler", captureMode = RESPONSE) public Object handleRequest(Object input, Context context) { @@ -44,6 +46,25 @@ public Object handleRequest(Object input, Context context) { return parentClass; } + public static class ChildSerializer extends StdSerializer<ChildClass> { + + public ChildSerializer() { + this(null); + } + + public ChildSerializer(Class<ChildClass> t) { + super(t); + } + + @Override + public void serialize(ChildClass value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + jgen.writeStartObject(); + jgen.writeStringField("name", value.name); + jgen.writeStringField("p", value.p.name); + jgen.writeEndObject(); + } + } + public class ParentClass { public String name; public ChildClass c; @@ -66,23 +87,4 @@ public ChildClass(String name, ParentClass p) { this.p = p; } } - - public static class ChildSerializer extends StdSerializer<ChildClass> { - - public ChildSerializer() { - this(null); - } - - public ChildSerializer(Class<ChildClass> t) { - super(t); - } - - @Override - public void serialize(ChildClass value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - jgen.writeStartObject(); - jgen.writeStringField("name", value.name); - jgen.writeStringField("p", value.p.name); - jgen.writeEndObject(); - } - } } diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForStream.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForStream.java index e316ffe7d..5c693445a 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForStream.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,14 +11,14 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import software.amazon.lambda.powertools.tracing.Tracing; - import java.io.InputStream; import java.io.OutputStream; +import software.amazon.lambda.powertools.tracing.Tracing; public class PowerTracerToolEnabledForStream implements RequestStreamHandler { diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForStreamWithNoMetaData.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForStreamWithNoMetaData.java index 4cd381807..4cd2bb0b7 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForStreamWithNoMetaData.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledForStreamWithNoMetaData.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,17 +11,17 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing.handlers; -import java.io.InputStream; -import java.io.OutputStream; +import static software.amazon.lambda.powertools.tracing.CaptureMode.DISABLED; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import java.io.InputStream; +import java.io.OutputStream; import software.amazon.lambda.powertools.tracing.Tracing; -import static software.amazon.lambda.powertools.tracing.CaptureMode.DISABLED; - public class PowerTracerToolEnabledForStreamWithNoMetaData implements RequestStreamHandler { @Override diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledWithException.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledWithException.java index cc184d020..88c506502 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledWithException.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledWithException.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing.handlers; import com.amazonaws.services.lambda.runtime.Context; diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledWithNoMetaData.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledWithNoMetaData.java index 2109d6647..b4d77cde3 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledWithNoMetaData.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/handlers/PowerTracerToolEnabledWithNoMetaData.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,14 +11,15 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing.handlers; +import static software.amazon.lambda.powertools.tracing.CaptureMode.DISABLED; + import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import software.amazon.lambda.powertools.tracing.Tracing; -import static software.amazon.lambda.powertools.tracing.CaptureMode.DISABLED; - public class PowerTracerToolEnabledWithNoMetaData implements RequestHandler<Object, Object> { @Override diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/internal/LambdaTracingAspectTest.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/internal/LambdaTracingAspectTest.java index 8cd9b2f71..b22f7a9af 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/internal/LambdaTracingAspectTest.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/internal/LambdaTracingAspectTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,22 +11,30 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.tracing.internal; +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.catchThrowable; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; + import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; import com.amazonaws.xray.AWSXRay; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; + +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; import software.amazon.lambda.powertools.tracing.handlers.PowerToolDisabled; import software.amazon.lambda.powertools.tracing.handlers.PowerToolDisabledForStream; import software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabled; @@ -38,40 +46,21 @@ import software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledForStreamWithNoMetaData; import software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledWithException; import software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledWithNoMetaData; -import software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledWithNoMetaDataDeprecated; import software.amazon.lambda.powertools.tracing.nonhandler.PowerToolNonHandler; -import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.assertj.core.api.Assertions.catchThrowable; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; - +@SetEnvironmentVariable(key = "POWERTOOLS_TRACER_CAPTURE_RESPONSE", value = "false") +@SetEnvironmentVariable(key = "POWERTOOLS_TRACER_CAPTURE_ERROR", value = "false") class LambdaTracingAspectTest { private RequestHandler<Object, Object> requestHandler; private RequestStreamHandler streamHandler; private PowerToolNonHandler nonHandlerMethod; - @Mock private Context context; - @BeforeAll - static void beforeAll() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("POWERTOOLS_TRACER_CAPTURE_RESPONSE")).thenReturn(null); - mocked.when(() -> SystemWrapper.getenv("POWERTOOLS_TRACER_CAPTURE_ERROR")).thenReturn(null); - mocked.when(() -> SystemWrapper.containsKey("POWERTOOLS_TRACER_CAPTURE_RESPONSE")).thenReturn(false); - mocked.when(() -> SystemWrapper.containsKey("POWERTOOLS_TRACER_CAPTURE_ERROR")).thenReturn(false); - } - } - @BeforeEach void setUp() throws IllegalAccessException { - openMocks(this); - writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true); - setupContext(); + writeStaticField(LambdaHandlerProcessor.class, "isColdStart", null, true); + context = new TestLambdaContext(); requestHandler = new PowerTracerToolEnabled(); streamHandler = new PowerTracerToolEnabledForStream(); nonHandlerMethod = new PowerToolNonHandler(); @@ -92,8 +81,7 @@ void shouldCaptureNonHandlerMethod() { assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) - .anySatisfy(segment -> - assertThat(segment.getName()).isEqualTo("## doSomething")); + .anySatisfy(segment -> assertThat(segment.getName()).isEqualTo("## doSomething")); } @Test @@ -105,14 +93,34 @@ void shouldCaptureNonHandlerMethodWithCustomSegmentName() { assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) - .anySatisfy(segment -> - assertThat(segment.getName()).isEqualTo("custom")); + .anySatisfy(segment -> assertThat(segment.getName()).isEqualTo("custom")); } @Test void shouldCaptureTraces() { requestHandler.handleRequest(new Object(), context); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) + .hasSize(1) + .allSatisfy(subsegment -> { + assertThat(subsegment.getAnnotations()) + .hasSize(2) + .containsEntry("ColdStart", true) + .containsEntry("Service", "lambdaHandler"); + + assertThat(subsegment.getMetadata()) + .isEmpty(); + }); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_TRACER_CAPTURE_RESPONSE", value = "true") + void shouldCaptureTracesWithResponseMetadata() { + requestHandler.handleRequest(new Object(), context); + assertThat(AWSXRay.getTraceEntity()) .isNotNull(); @@ -131,6 +139,7 @@ void shouldCaptureTraces() { } @Test + @SetEnvironmentVariable(key = "POWERTOOLS_TRACER_CAPTURE_ERROR", value = "true") void shouldCaptureTracesWithExceptionMetaData() { requestHandler = new PowerTracerToolEnabledWithException(); @@ -161,6 +170,24 @@ void shouldCaptureTracesWithExceptionMetaData() { void shouldCaptureTracesForStream() throws IOException { streamHandler.handleRequest(new ByteArrayInputStream("test".getBytes()), new ByteArrayOutputStream(), context); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) + .hasSize(1) + .allSatisfy(subsegment -> { + assertThat(subsegment.getAnnotations()) + .hasSize(2) + .containsEntry("ColdStart", true) + .containsEntry("Service", "streamHandler"); + }); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_TRACER_CAPTURE_RESPONSE", value = "true") + void shouldCaptureTracesForStreamWithResponseMetadata() throws IOException { + streamHandler.handleRequest(new ByteArrayInputStream("test".getBytes()), new ByteArrayOutputStream(), context); + assertThat(AWSXRay.getTraceEntity()) .isNotNull(); @@ -241,9 +268,8 @@ void shouldCaptureTracesForStreamWithNoMetadata() throws IOException { } @Test - void shouldCaptureTracesWithNoMetadataDeprecated() { - requestHandler = new PowerTracerToolEnabledWithNoMetaDataDeprecated(); - + @SetEnvironmentVariable(key = "POWERTOOLS_TRACER_CAPTURE_RESPONSE", value = "false") + void shouldNotCaptureTracesIfDisabledViaEnvironmentVariable() { requestHandler.handleRequest(new Object(), context); assertThat(AWSXRay.getTraceEntity()) @@ -255,7 +281,7 @@ void shouldCaptureTracesWithNoMetadataDeprecated() { assertThat(subsegment.getAnnotations()) .hasSize(2) .containsEntry("ColdStart", true) - .containsEntry("Service", "service_undefined"); + .containsEntry("Service", "lambdaHandler"); assertThat(subsegment.getMetadata()) .isEmpty(); @@ -263,54 +289,27 @@ void shouldCaptureTracesWithNoMetadataDeprecated() { } @Test - void shouldNotCaptureTracesIfDisabledViaEnvironmentVariable() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.containsKey("POWERTOOLS_TRACER_CAPTURE_RESPONSE")).thenReturn(true); - mocked.when(() -> SystemWrapper.getenv("POWERTOOLS_TRACER_CAPTURE_RESPONSE")).thenReturn("false"); - - requestHandler.handleRequest(new Object(), context); - - assertThat(AWSXRay.getTraceEntity()) - .isNotNull(); - - assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) - .hasSize(1) - .allSatisfy(subsegment -> { - assertThat(subsegment.getAnnotations()) - .hasSize(2) - .containsEntry("ColdStart", true) - .containsEntry("Service", "lambdaHandler"); - - assertThat(subsegment.getMetadata()) - .isEmpty(); - }); - } - } - - @Test + @SetEnvironmentVariable(key = "POWERTOOLS_TRACER_CAPTURE_RESPONSE", value = "false") void shouldCaptureTracesIfExplicitlyEnabledAndEnvironmentVariableIsDisabled() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("POWERTOOLS_TRACER_CAPTURE_RESPONSE")).thenReturn("false"); - requestHandler = new PowerTracerToolEnabledForResponse(); - - requestHandler.handleRequest(new Object(), context); - - assertThat(AWSXRay.getTraceEntity()) - .isNotNull(); - - assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) - .hasSize(1) - .allSatisfy(subsegment -> { - assertThat(subsegment.getAnnotations()) - .hasSize(2) - .containsEntry("ColdStart", true) - .containsEntry("Service", "lambdaHandler"); - - assertThat(subsegment.getMetadata()) - .hasSize(1) - .containsKey("lambdaHandler"); - }); - } + requestHandler = new PowerTracerToolEnabledForResponse(); + + requestHandler.handleRequest(new Object(), context); + + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) + .hasSize(1) + .allSatisfy(subsegment -> { + assertThat(subsegment.getAnnotations()) + .hasSize(2) + .containsEntry("ColdStart", true) + .containsEntry("Service", "lambdaHandler"); + + assertThat(subsegment.getMetadata()) + .hasSize(1) + .containsKey("lambdaHandler"); + }); } @Test @@ -330,7 +329,8 @@ void shouldCaptureTracesForSelfReferencingReturnTypesViaCustomMapper() { .containsKey("lambdaHandler"); assertThat(subsegment.getMetadata().get("lambdaHandler")) - .hasFieldOrPropertyWithValue("handleRequest response", "{\"name\":\"parent\",\"c\":{\"name\":\"child\",\"p\":\"parent\"}}"); + .hasFieldOrPropertyWithValue("handleRequest response", + "{\"name\":\"parent\",\"c\":{\"name\":\"child\",\"p\":\"parent\"}}"); }); assertThatNoException().isThrownBy(AWSXRay::endSegment); @@ -339,98 +339,82 @@ void shouldCaptureTracesForSelfReferencingReturnTypesViaCustomMapper() { } @Test + @SetEnvironmentVariable(key = "POWERTOOLS_TRACER_CAPTURE_RESPONSE", value = "false") + @SetEnvironmentVariable(key = "POWERTOOLS_TRACER_CAPTURE_ERROR", value = "false") void shouldCaptureTracesIfExplicitlyEnabledBothAndEnvironmentVariableIsDisabled() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.containsKey("POWERTOOLS_TRACER_CAPTURE_RESPONSE")).thenReturn(true); - mocked.when(() -> SystemWrapper.getenv("POWERTOOLS_TRACER_CAPTURE_RESPONSE")).thenReturn("false"); - mocked.when(() -> SystemWrapper.containsKey("POWERTOOLS_TRACER_CAPTURE_ERROR")).thenReturn(true); - mocked.when(() -> SystemWrapper.getenv("POWERTOOLS_TRACER_CAPTURE_ERROR")).thenReturn("false"); - requestHandler = new PowerTracerToolEnabledExplicitlyForResponseAndError(); - - requestHandler.handleRequest(new Object(), context); - - assertThat(AWSXRay.getTraceEntity()) - .isNotNull(); - - assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) - .hasSize(1) - .allSatisfy(subsegment -> { - assertThat(subsegment.getAnnotations()) - .hasSize(2) - .containsEntry("ColdStart", true) - .containsEntry("Service", "lambdaHandler"); - - assertThat(subsegment.getMetadata()) - .hasSize(1) - .containsKey("lambdaHandler"); - }); - } + requestHandler = new PowerTracerToolEnabledExplicitlyForResponseAndError(); + + requestHandler.handleRequest(new Object(), context); + + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) + .hasSize(1) + .allSatisfy(subsegment -> { + assertThat(subsegment.getAnnotations()) + .hasSize(2) + .containsEntry("ColdStart", true) + .containsEntry("Service", "lambdaHandler"); + + assertThat(subsegment.getMetadata()) + .hasSize(1) + .containsKey("lambdaHandler"); + }); } @Test + @SetEnvironmentVariable(key = "POWERTOOLS_TRACER_CAPTURE_ERROR", value = "false") void shouldNotCaptureTracesWithExceptionMetaDataIfDisabledViaEnvironmentVariable() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.containsKey("POWERTOOLS_TRACER_CAPTURE_ERROR")).thenReturn(true); - mocked.when(() -> SystemWrapper.getenv("POWERTOOLS_TRACER_CAPTURE_ERROR")).thenReturn("false"); - requestHandler = new PowerTracerToolEnabledWithException(); - - Throwable throwable = catchThrowable(() -> requestHandler.handleRequest(new Object(), context)); - - assertThat(throwable) - .isInstanceOf(RuntimeException.class); - - assertThat(AWSXRay.getTraceEntity()) - .isNotNull(); - - assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) - .hasSize(1) - .allSatisfy(subsegment -> { - assertThat(subsegment.getAnnotations()) - .hasSize(2) - .containsEntry("ColdStart", true) - .containsEntry("Service", "lambdaHandler"); - - assertThat(subsegment.getMetadata()) - .isEmpty(); - }); - } + requestHandler = new PowerTracerToolEnabledWithException(); + + Throwable throwable = catchThrowable(() -> requestHandler.handleRequest(new Object(), context)); + + assertThat(throwable) + .isInstanceOf(RuntimeException.class); + + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) + .hasSize(1) + .allSatisfy(subsegment -> { + assertThat(subsegment.getAnnotations()) + .hasSize(2) + .containsEntry("ColdStart", true) + .containsEntry("Service", "lambdaHandler"); + + assertThat(subsegment.getMetadata()) + .isEmpty(); + }); } @Test + @SetEnvironmentVariable(key = "POWERTOOLS_TRACER_CAPTURE_ERROR", value = "false") void shouldCaptureTracesWithExceptionMetaDataEnabledExplicitlyAndEnvironmentVariableDisabled() { - try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> SystemWrapper.getenv("POWERTOOLS_TRACER_CAPTURE_ERROR")).thenReturn("false"); + requestHandler = new PowerTracerToolEnabledForError(); - requestHandler = new PowerTracerToolEnabledForError(); - - Throwable exception = catchThrowable(() -> requestHandler.handleRequest(new Object(), context)); + Throwable exception = catchThrowable(() -> requestHandler.handleRequest(new Object(), context)); - assertThat(AWSXRay.getTraceEntity()) - .isNotNull(); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); - assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) - .hasSize(1) - .allSatisfy(subsegment -> { - assertThat(subsegment.getAnnotations()) - .hasSize(2) - .containsEntry("ColdStart", true) - .containsEntry("Service", "lambdaHandler"); + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) + .hasSize(1) + .allSatisfy(subsegment -> { + assertThat(subsegment.getAnnotations()) + .hasSize(2) + .containsEntry("ColdStart", true) + .containsEntry("Service", "lambdaHandler"); - assertThat(subsegment.getMetadata()) - .hasSize(1) - .containsKey("lambdaHandler"); + assertThat(subsegment.getMetadata()) + .hasSize(1) + .containsKey("lambdaHandler"); - assertThat(subsegment.getMetadata().get("lambdaHandler")) - .satisfies(stringObjectMap -> assertThat(stringObjectMap) - .containsEntry("handleRequest error", exception)); - }); - } + assertThat(subsegment.getMetadata().get("lambdaHandler")) + .satisfies(stringObjectMap -> assertThat(stringObjectMap) + .containsEntry("handleRequest error", exception)); + }); } - private void setupContext() { - when(context.getFunctionName()).thenReturn("testFunction"); - when(context.getInvokedFunctionArn()).thenReturn("testArn"); - when(context.getFunctionVersion()).thenReturn("1"); - when(context.getMemoryLimitInMB()).thenReturn(10); - } } diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/internal/TracingUserAgentInterceptorTest.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/internal/TracingUserAgentInterceptorTest.java new file mode 100644 index 000000000..b24f6c6f7 --- /dev/null +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/internal/TracingUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.tracing.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class TracingUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/TRACING/"); + } +} \ No newline at end of file diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/nonhandler/PowerToolNonHandler.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/nonhandler/PowerToolNonHandler.java index 309eb5598..48ffc93ae 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/nonhandler/PowerToolNonHandler.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/nonhandler/PowerToolNonHandler.java @@ -1,3 +1,17 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.tracing.nonhandler; import software.amazon.lambda.powertools.tracing.Tracing; diff --git a/powertools-tracing/src/test/resources/log4j2.xml b/powertools-tracing/src/test/resources/log4j2.xml index 108e32b75..030d11725 100644 --- a/powertools-tracing/src/test/resources/log4j2.xml +++ b/powertools-tracing/src/test/resources/log4j2.xml @@ -2,7 +2,7 @@ <Configuration packages="com.amazonaws.services.lambda.runtime.log4j2"> <Appenders> <File name="JsonAppender" fileName="target/logfile.json"> - <LambdaJsonLayout compact="true" eventEol="true"/> + <JsonTemplateLayout eventTemplateUri="classpath:LambdaJsonLayout.json" /> </File> </Appenders> <Loggers> diff --git a/powertools-validation/pom.xml b/powertools-validation/pom.xml index 00bc6711b..8dfce6b6a 100644 --- a/powertools-validation/pom.xml +++ b/powertools-validation/pom.xml @@ -1,6 +1,20 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +<!-- + ~ Copyright 2023 Amazon.com, Inc. or its affiliates. + ~ 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 + ~ http://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. + ~ + --> + +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> @@ -10,41 +24,27 @@ <parent> <artifactId>powertools-parent</artifactId> <groupId>software.amazon.lambda</groupId> - <version>1.10.2</version> + <version>2.9.0</version> </parent> - <name>AWS Lambda Powertools Java validation library</name> + <name>Powertools for AWS Lambda (Java) - Validation</name> <description> Json schema validation for Lambda events and responses </description> - <url>https://aws.amazon.com/lambda/</url> - <issueManagement> - <system>GitHub Issues</system> - <url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url> - </issueManagement> - - <scm> - <url>https://github.com/awslabs/aws-lambda-powertools-java.git</url> - </scm> - <developers> - <developer> - <name>AWS Lambda Powertools team</name> - <organization>Amazon Web Services</organization> - <organizationUrl>https://aws.amazon.com/</organizationUrl> - </developer> - </developers> - - <distributionManagement> - <snapshotRepository> - <id>ossrh</id> - <url>https://aws.oss.sonatype.org/content/repositories/snapshots</url> - </snapshotRepository> - </distributionManagement> <dependencies> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjrt</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>software.amazon.lambda</groupId> + <artifactId>powertools-common</artifactId> + </dependency> <dependency> <groupId>software.amazon.lambda</groupId> - <artifactId>powertools-core</artifactId> + <artifactId>powertools-serialization</artifactId> </dependency> <dependency> <groupId>com.amazonaws</groupId> @@ -62,14 +62,23 @@ <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> - <dependency> - <groupId>org.aspectj</groupId> - <artifactId>aspectjrt</artifactId> - </dependency> <dependency> <groupId>com.networknt</groupId> <artifactId>json-schema-validator</artifactId> - <version>1.0.65</version> + <version>1.5.9</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-serialization</artifactId> + </dependency> + <dependency> + <groupId>org.crac</groupId> + <artifactId>crac</artifactId> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>sdk-core</artifactId> + <scope>provided</scope> </dependency> <!-- Test dependencies --> @@ -84,8 +93,8 @@ <scope>test</scope> </dependency> <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> <scope>test</scope> </dependency> <dependency> @@ -93,6 +102,16 @@ <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-lambda-java-tests</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> @@ -103,6 +122,36 @@ <artifactId>assertj-core</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <scope>test</scope> + </dependency> </dependencies> -</project> \ No newline at end of file + <profiles> + <profile> + <id>generate-classesloaded-file</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> + -Xlog:class+load=info:classesloaded.txt + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + </argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validation.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validation.java index 68260cb47..f41364c1a 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validation.java +++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validation.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,11 +11,13 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation; +import static com.networknt.schema.SpecVersion.VersionFlag.V7; + import com.amazonaws.services.lambda.runtime.Context; import com.networknt.schema.SpecVersion.VersionFlag; - import java.io.InputStream; import java.io.OutputStream; import java.lang.annotation.ElementType; @@ -23,8 +25,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import static com.networknt.schema.SpecVersion.VersionFlag.V7; - /** * {@link Validation} is used to specify that the annotated method input and/or output needs to be valid.<br> * diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java index 191c50107..346dc6fa3 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java +++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,60 +11,61 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; import io.burt.jmespath.JmesPath; -import io.burt.jmespath.RuntimeConfiguration; import io.burt.jmespath.function.BaseFunction; -import io.burt.jmespath.function.FunctionRegistry; -import io.burt.jmespath.jackson.JacksonRuntime; -import software.amazon.lambda.powertools.validation.jmespath.Base64Function; -import software.amazon.lambda.powertools.validation.jmespath.Base64GZipFunction; - -import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; +import org.crac.Context; +import org.crac.Core; +import org.crac.Resource; +import software.amazon.lambda.powertools.common.internal.ClassPreLoader; +import software.amazon.lambda.powertools.utilities.JsonConfig; +import software.amazon.lambda.powertools.utilities.jmespath.Base64Function; +import software.amazon.lambda.powertools.utilities.jmespath.Base64GZipFunction; /** * Use this if you need to customize some part of the JSON Schema validation - * (eg. specification version, Jackson ObjectMapper, or adding functions to JMESPath) + * (eg. specification version, Jackson ObjectMapper, or adding functions to JMESPath). + * <p> + * For everything but the validation features (factory, schemaVersion), {@link ValidationConfig} + * is just a wrapper of {@link JsonConfig}. */ -public class ValidationConfig { - private ValidationConfig() { +public class ValidationConfig implements Resource { + private SpecVersion.VersionFlag jsonSchemaVersion = SpecVersion.VersionFlag.V7; + private JsonSchemaFactory factory = JsonSchemaFactory.getInstance(jsonSchemaVersion); + + // Static block to ensure CRaC registration happens at class loading time + static { + Core.getGlobalContext().register(get()); } - private static class ConfigHolder { - private final static ValidationConfig instance = new ValidationConfig(); + private ValidationConfig() { } public static ValidationConfig get() { return ConfigHolder.instance; } - private static final ThreadLocal<ObjectMapper> om = ThreadLocal.withInitial(() -> { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); - return objectMapper; - }); - - private SpecVersion.VersionFlag jsonSchemaVersion = SpecVersion.VersionFlag.V7; - private JsonSchemaFactory factory = JsonSchemaFactory.getInstance(jsonSchemaVersion); - - private final FunctionRegistry defaultFunctions = FunctionRegistry.defaultRegistry(); - private final FunctionRegistry customFunctions = defaultFunctions.extend( - new Base64Function(), - new Base64GZipFunction()); - private final RuntimeConfiguration configuration = new RuntimeConfiguration.Builder() - .withFunctionRegistry(customFunctions) - .build(); - private JmesPath<JsonNode> jmesPath = new JacksonRuntime(configuration, getObjectMapper()); + public SpecVersion.VersionFlag getSchemaVersion() { + return jsonSchemaVersion; + } /** - * Set the version of the json schema specifications (default is V7) + * Set the version of the json schema specifications to use if $schema is not + * explicitly specified within the schema (default is V7). If $schema is + * explicitly specified within the schema is explicitly specified within the + * schema, the validator will use the specified dialect. * - * @param version May be V4, V6, V7 or V201909 + * @param version May be V4, V6, V7, V201909 or V202012 + * @see <a href= + * "https://json-schema.org/understanding-json-schema/reference/schema#declaring-a-dialect">Declaring + * a Dialect</a> */ public void setSchemaVersion(SpecVersion.VersionFlag version) { if (version != jsonSchemaVersion) { @@ -73,25 +74,15 @@ public void setSchemaVersion(SpecVersion.VersionFlag version) { } } - public SpecVersion.VersionFlag getSchemaVersion() { - return jsonSchemaVersion; - } - /** * Add a custom {@link io.burt.jmespath.function.Function} to JMESPath * {@link Base64Function} and {@link Base64GZipFunction} are already built-in. * * @param function the function to add - * @param <T> Must extends {@link BaseFunction} + * @param <T> Must extend {@link BaseFunction} */ public <T extends BaseFunction> void addFunction(T function) { - FunctionRegistry functionRegistryWithExtendedFunctions = configuration.functionRegistry().extend(function); - - RuntimeConfiguration updatedConfig = new RuntimeConfiguration.Builder() - .withFunctionRegistry(functionRegistryWithExtendedFunctions) - .build(); - - jmesPath = new JacksonRuntime(updatedConfig, getObjectMapper()); + JsonConfig.get().addFunction(function); } /** @@ -109,7 +100,7 @@ public JsonSchemaFactory getFactory() { * @return the {@link JmesPath} */ public JmesPath<JsonNode> getJmesPath() { - return jmesPath; + return JsonConfig.get().getJmesPath(); } /** @@ -118,6 +109,30 @@ public JmesPath<JsonNode> getJmesPath() { * @return the {@link ObjectMapper} to serialize / deserialize JSON */ public ObjectMapper getObjectMapper() { - return om.get(); + return JsonConfig.get().getObjectMapper(); + } + + @Override + public void beforeCheckpoint(Context<? extends Resource> context) throws Exception { + // Initialize key components + getObjectMapper(); + getJmesPath(); + getFactory(); + + // Dummy validation + String sampleSchema = "{\"type\":\"object\"}"; + JsonSchema schema = ValidationUtils.getJsonSchema(sampleSchema); + ValidationUtils.validate("{\"test\":\"dummy\"}", schema); + + ClassPreLoader.preloadClasses(); + } + + @Override + public void afterRestore(Context<? extends Resource> context) throws Exception { + // No action needed after restore + } + + private static class ConfigHolder { + private static final ValidationConfig instance = new ValidationConfig(); } } diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationException.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationException.java index 2d3e1b350..fd4cb66a6 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationException.java +++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation; public class ValidationException extends RuntimeException { diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java index 83f34ebfd..35b309f07 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java +++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,23 +11,26 @@ * limitations under the License. * */ -package software.amazon.lambda.powertools.validation; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; +package software.amazon.lambda.powertools.validation; +import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; +import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeType; +import com.fasterxml.jackson.databind.node.NullNode; import com.networknt.schema.JsonSchema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaValidatorsConfig; import com.networknt.schema.ValidationMessage; import io.burt.jmespath.Expression; -import software.amazon.lambda.powertools.validation.internal.ValidationAspect; +import java.io.ByteArrayOutputStream; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; /** * Validation utility, used to manually validate Json against Json Schema @@ -65,11 +68,18 @@ public static void validate(Object obj, JsonSchema jsonSchema, String envelope) } JsonNode subNode; try { - JsonNode jsonNode = ValidationConfig.get().getObjectMapper().valueToTree(obj); + PojoSerializer pojoSerializer = + LambdaEventSerializers.serializerFor(obj.getClass(), ClassLoader.getSystemClassLoader()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + pojoSerializer.toJson(obj, out); + JsonNode jsonNode = ValidationConfig.get().getObjectMapper().readTree(out.toString("UTF-8")); Expression<JsonNode> expression = ValidationConfig.get().getJmesPath().compile(envelope); subNode = expression.search(jsonNode); + if (subNode == null || subNode instanceof NullNode) { + throw new ValidationException("Envelope not found in the object"); + } } catch (Exception e) { - throw new ValidationException("Cannot find envelope <"+envelope+"> in the object <"+obj+">", e); + throw new ValidationException("Cannot find envelope <" + envelope + "> in the object <" + obj + ">", e); } if (subNode.getNodeType() == JsonNodeType.ARRAY) { subNode.forEach(jsonNode -> validate(jsonNode, jsonSchema)); @@ -80,7 +90,8 @@ public static void validate(Object obj, JsonSchema jsonSchema, String envelope) try { validate(subNode.asText(), jsonSchema); } catch (ValidationException e) { - throw new ValidationException("Invalid format for '" + envelope + "': 'STRING' and no JSON found in it."); + throw new ValidationException( + "Invalid format for '" + envelope + "': 'STRING' and no JSON found in it."); } } else { throw new ValidationException("Invalid format for '" + envelope + "': '" + subNode.getNodeType() + "'"); @@ -110,7 +121,7 @@ public static void validate(Object obj, JsonSchema jsonSchema) throws Validation try { jsonNode = ValidationConfig.get().getObjectMapper().valueToTree(obj); } catch (Exception e) { - throw new ValidationException("Object <"+obj+"> is not valid against the schema provided", e); + throw new ValidationException("Object <" + obj + "> is not valid against the schema provided", e); } validate(jsonNode, jsonSchema); @@ -139,7 +150,7 @@ public static void validate(String json, JsonSchema jsonSchema) throws Validatio try { jsonNode = ValidationConfig.get().getObjectMapper().readTree(json); } catch (Exception e) { - throw new ValidationException("Json <"+json+"> is not valid against the schema provided", e); + throw new ValidationException("Json <" + json + "> is not valid against the schema provided", e); } validate(jsonNode, jsonSchema); @@ -168,7 +179,7 @@ public static void validate(Map<String, Object> map, JsonSchema jsonSchema) thro try { jsonNode = ValidationConfig.get().getObjectMapper().valueToTree(map); } catch (Exception e) { - throw new ValidationException("Map <"+map+"> cannot be converted to json for validation", e); + throw new ValidationException("Map <" + map + "> cannot be converted to json for validation", e); } validate(jsonNode, jsonSchema); @@ -199,9 +210,11 @@ public static void validate(JsonNode jsonNode, JsonSchema jsonSchema) throws Val if (!validationMessages.isEmpty()) { String message; try { - message = ValidationConfig.get().getObjectMapper().writeValueAsString(new ValidationErrors(validationMessages)); + message = ValidationConfig.get().getObjectMapper() + .writeValueAsString(new ValidationErrors(validationMessages)); } catch (JsonProcessingException e) { - message = validationMessages.stream().map(ValidationMessage::getMessage).collect(Collectors.joining(", ")); + message = validationMessages.stream().map(ValidationMessage::getMessage) + .collect(Collectors.joining(", ")); } throw new ValidationException(message); } @@ -229,42 +242,46 @@ public static JsonSchema getJsonSchema(String schema) { * @return the loaded json schema */ public static JsonSchema getJsonSchema(String schema, boolean validateSchema) { - JsonSchema jsonSchema = schemas.get(schema); + JsonSchema jsonSchema = schemas.computeIfAbsent(schema, ValidationUtils::createJsonSchema); - if (jsonSchema != null) { - return jsonSchema; + if (validateSchema) { + validateSchema(schema, jsonSchema); } - if (schema.startsWith(CLASSPATH)) { - String filePath = schema.substring(CLASSPATH.length()); - try (InputStream schemaStream = ValidationAspect.class.getResourceAsStream(filePath)) { - if (schemaStream == null) { - throw new IllegalArgumentException("'" + schema + "' is invalid, verify '" + filePath + "' is in your classpath"); - } - - jsonSchema = ValidationConfig.get().getFactory().getSchema(schemaStream); - } catch (IOException e) { - throw new IllegalArgumentException("'" + schema + "' is invalid, verify '" + filePath + "' is in your classpath"); - } - } else { - jsonSchema = ValidationConfig.get().getFactory().getSchema(schema); - } + return jsonSchema; + } - if (validateSchema) { - String version = ValidationConfig.get().getSchemaVersion().toString(); + private static JsonSchema createJsonSchema(String schema) { + JsonSchema jsonSchema; + SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true) + .preloadJsonSchemaRefMaxNestingDepth(10).build(); + if (schema.startsWith(CLASSPATH)) { try { - validate(jsonSchema.getSchemaNode(), - getJsonSchema("classpath:/schemas/meta_schema_" + version)); - } catch (ValidationException ve) { - throw new IllegalArgumentException("The schema " + schema + " is not valid, it does not respect the specification " + version, ve); + jsonSchema = ValidationConfig.get().getFactory().getSchema(SchemaLocation.of(schema), config); + } catch (Exception e) { + String filePath = schema.substring(CLASSPATH.length()); + throw new IllegalArgumentException( + "'" + schema + "' is invalid, verify '" + filePath + "' is in your classpath", e); } + } else { + jsonSchema = ValidationConfig.get().getFactory().getSchema(schema, config); } - schemas.put(schema, jsonSchema); - return jsonSchema; } + private static void validateSchema(String schema, JsonSchema jsonSchema) { + String schemaId = jsonSchema.getValidationContext().getMetaSchema().getIri() + .replace("https://json-schema.org", "").replace("http://json-schema.org", ""); + try { + validate(jsonSchema.getSchemaNode(), + getJsonSchema(CLASSPATH + schemaId)); + } catch (ValidationException ve) { + throw new IllegalArgumentException( + "The schema " + schema + " is not valid, it does not respect the specification " + schemaId, ve); + } + } + /** * */ diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java index b665ca2e0..68900d334 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java +++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,31 +11,62 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation.internal; -import com.amazonaws.services.lambda.runtime.events.*; +import static com.networknt.schema.SpecVersion.VersionFlag.V201909; +import static java.nio.charset.StandardCharsets.UTF_8; +import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.placedOnRequestHandler; +import static software.amazon.lambda.powertools.utilities.jmespath.Base64Function.decode; +import static software.amazon.lambda.powertools.utilities.jmespath.Base64GZipFunction.decompress; +import static software.amazon.lambda.powertools.validation.ValidationUtils.getJsonSchema; +import static software.amazon.lambda.powertools.validation.ValidationUtils.validate; + +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2WebSocketResponse; +import com.amazonaws.services.lambda.runtime.events.ActiveMQEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent; +import com.amazonaws.services.lambda.runtime.events.KafkaEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsInputPreprocessingResponse; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; import com.networknt.schema.JsonSchema; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.validation.Validation; import software.amazon.lambda.powertools.validation.ValidationConfig; - -import static com.networknt.schema.SpecVersion.VersionFlag.V201909; -import static java.nio.charset.StandardCharsets.UTF_8; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isHandlerMethod; -import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnRequestHandler; -import static software.amazon.lambda.powertools.validation.ValidationUtils.getJsonSchema; -import static software.amazon.lambda.powertools.validation.ValidationUtils.validate; -import static software.amazon.lambda.powertools.validation.jmespath.Base64Function.decode; -import static software.amazon.lambda.powertools.validation.jmespath.Base64GZipFunction.decompress; +import software.amazon.lambda.powertools.validation.ValidationException; /** - * Aspect for {@link Validation} annotation + * Aspect for {@link Validation} annotation. Internal to Powertools, use the annotation itself. */ @Aspect public class ValidationAspect { + private static final Logger LOG = LoggerFactory.getLogger(ValidationAspect.class); + @SuppressWarnings({"EmptyMethod"}) @Pointcut("@annotation(validation)") public void callAt(Validation validation) { @@ -51,26 +82,36 @@ public Object around(ProceedingJoinPoint pjp, ValidationConfig.get().setSchemaVersion(validation.schemaVersion()); } - if (isHandlerMethod(pjp) - && placedOnRequestHandler(pjp)) { + // we need this result object to be null at this point as validation of API events, if + // it fails, will catch the ValidationException and generate a 400 API response. This response + // will be stored in the result object to prevent executing the lambda + Object validationResult = null; + boolean failFast = false; + + if (placedOnRequestHandler(pjp)) { validationNeeded = true; if (!validation.inboundSchema().isEmpty()) { JsonSchema inboundJsonSchema = getJsonSchema(validation.inboundSchema(), true); Object obj = pjp.getArgs()[0]; - if (obj instanceof APIGatewayProxyRequestEvent) { + if (validation.envelope() != null && !validation.envelope().isEmpty()) { + validate(obj, inboundJsonSchema, validation.envelope()); + } else if (obj instanceof APIGatewayProxyRequestEvent) { APIGatewayProxyRequestEvent event = (APIGatewayProxyRequestEvent) obj; - validate(event.getBody(), inboundJsonSchema); + validationResult = validateAPIGatewayProxyBody(event.getBody(), inboundJsonSchema, null, null); + failFast = true; } else if (obj instanceof APIGatewayV2HTTPEvent) { APIGatewayV2HTTPEvent event = (APIGatewayV2HTTPEvent) obj; - validate(event.getBody(), inboundJsonSchema); + validationResult = validateAPIGatewayV2HTTPBody(event.getBody(), inboundJsonSchema, null, null); + failFast = true; } else if (obj instanceof SNSEvent) { SNSEvent event = (SNSEvent) obj; - event.getRecords().forEach(record -> validate(record.getSNS().getMessage(), inboundJsonSchema)); + event.getRecords() + .forEach(snsRecord -> validate(snsRecord.getSNS().getMessage(), inboundJsonSchema)); } else if (obj instanceof SQSEvent) { SQSEvent event = (SQSEvent) obj; - event.getRecords().forEach(record -> validate(record.getBody(), inboundJsonSchema)); + validationResult = validateSQSEventMessages(event.getRecords(), inboundJsonSchema); } else if (obj instanceof ScheduledEvent) { ScheduledEvent event = (ScheduledEvent) obj; validate(event.getDetail(), inboundJsonSchema); @@ -85,50 +126,216 @@ && placedOnRequestHandler(pjp)) { validate(event.getResourceProperties(), inboundJsonSchema); } else if (obj instanceof KinesisEvent) { KinesisEvent event = (KinesisEvent) obj; - event.getRecords().forEach(record -> validate(decode(record.getKinesis().getData()), inboundJsonSchema)); + validationResult = validateKinesisEventRecords(event.getRecords(), inboundJsonSchema); } else if (obj instanceof KinesisFirehoseEvent) { KinesisFirehoseEvent event = (KinesisFirehoseEvent) obj; - event.getRecords().forEach(record -> validate(decode(record.getData()), inboundJsonSchema)); + event.getRecords() + .forEach(eventRecord -> validate(decode(eventRecord.getData()), inboundJsonSchema)); } else if (obj instanceof KafkaEvent) { KafkaEvent event = (KafkaEvent) obj; - event.getRecords().forEach((s, records) -> records.forEach(record -> validate(record.getValue(), inboundJsonSchema))); - }else if (obj instanceof KinesisAnalyticsFirehoseInputPreprocessingEvent) { - KinesisAnalyticsFirehoseInputPreprocessingEvent event = (KinesisAnalyticsFirehoseInputPreprocessingEvent) obj; - event.getRecords().forEach(record -> validate(decode(record.getData()), inboundJsonSchema)); + event.getRecords().forEach((s, records) -> records.forEach( + eventRecord -> validate(decode(eventRecord.getValue()), inboundJsonSchema))); + } else if (obj instanceof ActiveMQEvent) { + ActiveMQEvent event = (ActiveMQEvent) obj; + event.getMessages().forEach(message -> validate(decode(message.getData()), inboundJsonSchema)); + } else if (obj instanceof RabbitMQEvent) { + RabbitMQEvent event = (RabbitMQEvent) obj; + event.getRmqMessagesByQueue().forEach((s, records) -> records.forEach( + message -> validate(decode(message.getData()), inboundJsonSchema))); + } else if (obj instanceof KinesisAnalyticsFirehoseInputPreprocessingEvent) { + KinesisAnalyticsFirehoseInputPreprocessingEvent event = + (KinesisAnalyticsFirehoseInputPreprocessingEvent) obj; + event.getRecords() + .forEach(eventRecord -> validate(decode(eventRecord.getData()), inboundJsonSchema)); } else if (obj instanceof KinesisAnalyticsStreamsInputPreprocessingEvent) { - KinesisAnalyticsStreamsInputPreprocessingEvent event = (KinesisAnalyticsStreamsInputPreprocessingEvent) obj; - event.getRecords().forEach(record -> validate(decode(record.getData()), inboundJsonSchema)); + KinesisAnalyticsStreamsInputPreprocessingEvent event = + (KinesisAnalyticsStreamsInputPreprocessingEvent) obj; + event.getRecords() + .forEach(eventRecord -> validate(decode(eventRecord.getData()), inboundJsonSchema)); } else { - validate(obj, inboundJsonSchema, validation.envelope()); + LOG.warn("Unhandled event type {}, please use the 'envelope' parameter to specify what to validate", + obj.getClass().getName()); + } + } + } + + Object result; + + // don't execute the lambda if result was set by previous validation step and should fail fast + // in that case result should already hold a response with validation information + if (failFast && validationResult != null) { + LOG.error("Incoming API event's body failed inbound schema validation."); + return validationResult; + } else { + result = pjp.proceed(proceedArgs); + + if (validationResult != null && result != null) { + // in the case of batches (SQS, Kinesis), we copy the batch item failures to the result + if (result instanceof SQSBatchResponse && validationResult instanceof SQSBatchResponse) { + SQSBatchResponse validationResponse = (SQSBatchResponse) validationResult; + SQSBatchResponse response = (SQSBatchResponse) result; + if (response.getBatchItemFailures() == null) { + response.setBatchItemFailures(validationResponse.getBatchItemFailures()); + } else { + response.getBatchItemFailures().addAll(validationResponse.getBatchItemFailures()); + } + } else if (result instanceof StreamsEventResponse && validationResult instanceof StreamsEventResponse) { + StreamsEventResponse validationResponse = (StreamsEventResponse) validationResult; + StreamsEventResponse response = (StreamsEventResponse) result; + if (response.getBatchItemFailures() == null) { + response.setBatchItemFailures(validationResponse.getBatchItemFailures()); + } else { + response.getBatchItemFailures().addAll(validationResponse.getBatchItemFailures()); + } + } + } + + if (result != null && validationNeeded && !validation.outboundSchema().isEmpty()) { + JsonSchema outboundJsonSchema = getJsonSchema(validation.outboundSchema(), true); + + Object overridenResponse = null; + // The normal behavior of @Validation is to throw an exception if response's validation fails. + // but in the case of APIGatewayProxyResponseEvent and APIGatewayV2HTTPResponse we want to return + // a 400 response with the validation errors instead of throwing an exception. + if (result instanceof APIGatewayProxyResponseEvent) { + APIGatewayProxyResponseEvent response = (APIGatewayProxyResponseEvent) result; + overridenResponse = + validateAPIGatewayProxyBody(response.getBody(), outboundJsonSchema, response.getHeaders(), + response.getMultiValueHeaders()); + } else if (result instanceof APIGatewayV2HTTPResponse) { + APIGatewayV2HTTPResponse response = (APIGatewayV2HTTPResponse) result; + overridenResponse = + validateAPIGatewayV2HTTPBody(response.getBody(), outboundJsonSchema, response.getHeaders(), + response.getMultiValueHeaders()); + // all type of below responses will throw an exception if validation fails + } else if (result instanceof APIGatewayV2WebSocketResponse) { + APIGatewayV2WebSocketResponse response = (APIGatewayV2WebSocketResponse) result; + validate(response.getBody(), outboundJsonSchema); + } else if (result instanceof ApplicationLoadBalancerResponseEvent) { + ApplicationLoadBalancerResponseEvent response = (ApplicationLoadBalancerResponseEvent) result; + validate(response.getBody(), outboundJsonSchema); + } else if (result instanceof KinesisAnalyticsInputPreprocessingResponse) { + KinesisAnalyticsInputPreprocessingResponse response = + (KinesisAnalyticsInputPreprocessingResponse) result; + response.getRecords().forEach(record -> validate(decode(record.getData()), outboundJsonSchema)); + } else { + LOG.warn( + "Unhandled response type {}, please use the 'envelope' parameter to specify what to validate", + result.getClass().getName()); } + + if (overridenResponse != null) { + result = overridenResponse; + LOG.error("API response failed outbound schema validation."); + } + } + } + + return result; + } + + /** + * Validate each Kinesis record body. If an error occurs, do not fail the whole batch but only add invalid items in BatchItemFailure. + * Note that the valid records will be decoded twice (during validation and within the handler by the user), which will slightly reduce performance. + * @param records Kinesis records + * @param inboundJsonSchema validation schema + * @return the stream response with items in failure + */ + private StreamsEventResponse validateKinesisEventRecords(List<KinesisEvent.KinesisEventRecord> records, + JsonSchema inboundJsonSchema) { + StreamsEventResponse response = StreamsEventResponse.builder().withBatchItemFailures(new ArrayList<>()).build(); + + ListIterator<KinesisEvent.KinesisEventRecord> listIterator = records.listIterator(); // using iterator to remove while browsing + while (listIterator.hasNext()) { + KinesisEvent.KinesisEventRecord eventRecord = listIterator.next(); + try { + validate(decode(eventRecord.getKinesis().getData()), inboundJsonSchema); + } catch (ValidationException e) { + LOG.error("Validation error on message {}: {}", eventRecord.getKinesis().getSequenceNumber(), + e.getMessage()); + listIterator.remove(); + response.getBatchItemFailures().add(StreamsEventResponse.BatchItemFailure.builder() + .withItemIdentifier(eventRecord.getKinesis().getSequenceNumber()).build()); } } + return response; + } - Object result = pjp.proceed(proceedArgs); - - if (validationNeeded && !validation.outboundSchema().isEmpty()) { - JsonSchema outboundJsonSchema = getJsonSchema(validation.outboundSchema(), true); - - if (result instanceof APIGatewayProxyResponseEvent) { - APIGatewayProxyResponseEvent response = (APIGatewayProxyResponseEvent) result; - validate(response.getBody(), outboundJsonSchema); - } else if (result instanceof APIGatewayV2HTTPResponse) { - APIGatewayV2HTTPResponse response = (APIGatewayV2HTTPResponse) result; - validate(response.getBody(), outboundJsonSchema); - } else if (result instanceof APIGatewayV2WebSocketResponse) { - APIGatewayV2WebSocketResponse response = (APIGatewayV2WebSocketResponse) result; - validate(response.getBody(), outboundJsonSchema); - } else if (result instanceof ApplicationLoadBalancerResponseEvent) { - ApplicationLoadBalancerResponseEvent response = (ApplicationLoadBalancerResponseEvent) result; - validate(response.getBody(), outboundJsonSchema); - } else if (result instanceof KinesisAnalyticsInputPreprocessingResponse) { - KinesisAnalyticsInputPreprocessingResponse response = (KinesisAnalyticsInputPreprocessingResponse) result; - response.getRecords().forEach(record -> validate(decode(record.getData()), outboundJsonSchema)); - } else { - validate(result, outboundJsonSchema, validation.envelope()); + /** + * Validate each SQS message body. If an error occurs, do not fail the whole batch but only add invalid items in BatchItemFailure. + * + * @param messages SQS messages + * @param inboundJsonSchema validation schema + * @return the SQS batch response + */ + private SQSBatchResponse validateSQSEventMessages(List<SQSEvent.SQSMessage> messages, + JsonSchema inboundJsonSchema) { + SQSBatchResponse response = SQSBatchResponse.builder().withBatchItemFailures(new ArrayList<>()).build(); + ListIterator<SQSEvent.SQSMessage> listIterator = messages.listIterator(); // using iterator to remove while browsing + while (listIterator.hasNext()) { + SQSEvent.SQSMessage message = listIterator.next(); + try { + validate(message.getBody(), inboundJsonSchema); + } catch (ValidationException e) { + LOG.error("Validation error on message {}: {}", message.getMessageId(), e.getMessage()); + listIterator.remove(); + response.getBatchItemFailures() + .add(SQSBatchResponse.BatchItemFailure.builder().withItemIdentifier(message.getMessageId()) + .build()); } } + return response; + } + /** + * Validates the given body against the provided JsonSchema. If validation fails the ValidationException + * will be catched and transformed to a 400, bad request, API response + * + * @param body body of the event to validate + * @param jsonSchema validation schema + * @return null if validation passed, or a 400 response object otherwise + */ + private APIGatewayProxyResponseEvent validateAPIGatewayProxyBody(final String body, final JsonSchema jsonSchema, + final Map<String, String> headers, + Map<String, List<String>> multivalueHeaders) { + APIGatewayProxyResponseEvent result = null; + try { + validate(body, jsonSchema); + } catch (ValidationException e) { + LOG.error("There were validation errors: {}", e.getMessage()); + result = new APIGatewayProxyResponseEvent(); + result.setBody(e.getMessage()); + result.setHeaders(headers == null ? Collections.emptyMap() : headers); + result.setMultiValueHeaders(multivalueHeaders == null ? Collections.emptyMap() : multivalueHeaders); + result.setStatusCode(400); + result.setIsBase64Encoded(false); + } + return result; + } + + /** + * Validates the given body against the provided JsonSchema. If validation fails the ValidationException + * will be catched and transformed to a 400, bad request, API response + * + * @param body body of the event to validate + * @param jsonSchema validation schema + * @return null if validation passed, or a 400 response object otherwise + */ + private APIGatewayV2HTTPResponse validateAPIGatewayV2HTTPBody(final String body, final JsonSchema jsonSchema, + final Map<String, String> headers, + Map<String, List<String>> multivalueHeaders) { + APIGatewayV2HTTPResponse result = null; + try { + validate(body, jsonSchema); + } catch (ValidationException e) { + LOG.error("There were validation errors: {}", e.getMessage()); + result = new APIGatewayV2HTTPResponse(); + result.setBody(e.getMessage()); + result.setHeaders(headers == null ? Collections.emptyMap() : headers); + result.setMultiValueHeaders(multivalueHeaders == null ? Collections.emptyMap() : multivalueHeaders); + result.setStatusCode(400); + result.setIsBase64Encoded(false); + } return result; } } diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationUserAgentInterceptor.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationUserAgentInterceptor.java new file mode 100644 index 000000000..ee92ff367 --- /dev/null +++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationUserAgentInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.validation.internal; + +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; + +/** + * Global interceptor that configures the User-Agent for all AWS SDK clients + * when the powertools-validation module is on the classpath. + */ +public final class ValidationUserAgentInterceptor implements ExecutionInterceptor { + static { + UserAgentConfigurator.configureUserAgent("validation"); + } + + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + // This is a no-op interceptor. We use this class to configure the PT User-Agent in the static block. It is + // loaded by AWS SDK Global Interceptors. + return context.request(); + } +} diff --git a/powertools-validation/src/main/resources/classesloaded.txt b/powertools-validation/src/main/resources/classesloaded.txt new file mode 100644 index 000000000..adf442b37 --- /dev/null +++ b/powertools-validation/src/main/resources/classesloaded.txt @@ -0,0 +1,7285 @@ +java.lang.Object +java.io.Serializable +java.lang.Comparable +java.lang.CharSequence +java.lang.constant.Constable +java.lang.constant.ConstantDesc +java.lang.String +java.lang.reflect.AnnotatedElement +java.lang.reflect.GenericDeclaration +java.lang.reflect.Type +java.lang.invoke.TypeDescriptor +java.lang.invoke.TypeDescriptor$OfField +java.lang.Class +java.lang.Cloneable +java.lang.ClassLoader +java.lang.System +java.lang.Throwable +java.lang.Error +java.lang.ThreadDeath +java.lang.Exception +java.lang.RuntimeException +java.lang.SecurityManager +java.security.ProtectionDomain +java.security.AccessControlContext +java.security.AccessController +java.security.SecureClassLoader +java.lang.ReflectiveOperationException +java.lang.ClassNotFoundException +java.lang.Record +java.lang.LinkageError +java.lang.NoClassDefFoundError +java.lang.ClassCastException +java.lang.ArrayStoreException +java.lang.VirtualMachineError +java.lang.InternalError +java.lang.OutOfMemoryError +java.lang.StackOverflowError +java.lang.IllegalMonitorStateException +java.lang.ref.Reference +java.lang.ref.SoftReference +java.lang.ref.WeakReference +java.lang.ref.FinalReference +java.lang.ref.PhantomReference +java.lang.ref.Finalizer +java.lang.Runnable +java.lang.Thread +java.lang.Thread$UncaughtExceptionHandler +java.lang.ThreadGroup +java.util.Dictionary +java.util.Map +java.util.Hashtable +java.util.Properties +java.lang.Module +java.lang.reflect.AccessibleObject +java.lang.reflect.Member +java.lang.reflect.Field +java.lang.reflect.Parameter +java.lang.reflect.Executable +java.lang.reflect.Method +java.lang.reflect.Constructor +jdk.internal.reflect.MagicAccessorImpl +jdk.internal.reflect.MethodAccessor +jdk.internal.reflect.MethodAccessorImpl +jdk.internal.reflect.ConstructorAccessor +jdk.internal.reflect.ConstructorAccessorImpl +jdk.internal.reflect.DelegatingClassLoader +jdk.internal.reflect.ConstantPool +jdk.internal.reflect.FieldAccessor +jdk.internal.reflect.FieldAccessorImpl +jdk.internal.reflect.UnsafeFieldAccessorImpl +jdk.internal.reflect.UnsafeStaticFieldAccessorImpl +java.lang.annotation.Annotation +jdk.internal.reflect.CallerSensitive +jdk.internal.reflect.NativeConstructorAccessorImpl +java.lang.invoke.MethodHandle +java.lang.invoke.DirectMethodHandle +java.lang.invoke.VarHandle +java.lang.invoke.MemberName +java.lang.invoke.ResolvedMethodName +java.lang.invoke.MethodHandleNatives +java.lang.invoke.LambdaForm +java.lang.invoke.TypeDescriptor$OfMethod +java.lang.invoke.MethodType +java.lang.BootstrapMethodError +java.lang.invoke.CallSite +jdk.internal.invoke.NativeEntryPoint +java.lang.invoke.MethodHandleNatives$CallSiteContext +java.lang.invoke.ConstantCallSite +java.lang.invoke.MutableCallSite +java.lang.invoke.VolatileCallSite +java.lang.AssertionStatusDirectives +java.lang.Appendable +java.lang.AbstractStringBuilder +java.lang.StringBuffer +java.lang.StringBuilder +jdk.internal.misc.UnsafeConstants +jdk.internal.misc.Unsafe +jdk.internal.module.Modules +java.lang.AutoCloseable +java.io.Closeable +java.io.InputStream +java.io.ByteArrayInputStream +java.net.URL +java.util.jar.Manifest +jdk.internal.loader.BuiltinClassLoader +jdk.internal.loader.ClassLoaders +jdk.internal.loader.ClassLoaders$AppClassLoader +jdk.internal.loader.ClassLoaders$PlatformClassLoader +java.security.CodeSource +java.util.AbstractMap +java.util.concurrent.ConcurrentMap +java.util.concurrent.ConcurrentHashMap +java.lang.Iterable +java.util.Collection +java.util.AbstractCollection +java.util.List +java.util.AbstractList +java.util.RandomAccess +java.util.ArrayList +java.lang.StackTraceElement +java.nio.Buffer +java.lang.StackWalker +java.lang.StackStreamFactory$AbstractStackWalker +java.lang.StackWalker$StackFrame +java.lang.StackFrameInfo +java.lang.LiveStackFrame +java.lang.LiveStackFrameInfo +java.util.concurrent.locks.AbstractOwnableSynchronizer +java.lang.Boolean +java.lang.Character +java.lang.Number +java.lang.Float +java.lang.Double +java.lang.Byte +java.lang.Short +java.lang.Integer +java.lang.Long +java.util.Iterator +java.lang.reflect.RecordComponent +jdk.internal.vm.vector.VectorSupport +jdk.internal.vm.vector.VectorSupport$VectorPayload +jdk.internal.vm.vector.VectorSupport$Vector +jdk.internal.vm.vector.VectorSupport$VectorMask +jdk.internal.vm.vector.VectorSupport$VectorShuffle +java.lang.Integer$IntegerCache +java.lang.Long$LongCache +java.lang.Byte$ByteCache +java.lang.Short$ShortCache +java.lang.Character$CharacterCache +java.util.jar.Attributes$Name +java.util.ImmutableCollections$AbstractImmutableMap +java.util.ImmutableCollections$MapN +sun.util.locale.BaseLocale +jdk.internal.module.ArchivedModuleGraph +java.lang.module.ModuleFinder +jdk.internal.module.SystemModuleFinders$SystemModuleFinder +java.util.ImmutableCollections$AbstractImmutableCollection +java.util.Set +java.util.ImmutableCollections$AbstractImmutableSet +java.util.ImmutableCollections$SetN +java.lang.module.ModuleReference +jdk.internal.module.ModuleReferenceImpl +java.lang.module.ModuleDescriptor +java.lang.module.ModuleDescriptor$Version +java.util.ImmutableCollections$Set12 +java.lang.module.ModuleDescriptor$Requires +java.lang.Enum +java.lang.module.ModuleDescriptor$Requires$Modifier +java.lang.module.ModuleDescriptor$Exports +java.net.URI +java.util.function.Supplier +jdk.internal.module.SystemModuleFinders$2 +jdk.internal.module.ModuleHashes$HashSupplier +jdk.internal.module.SystemModuleFinders$3 +java.lang.module.ModuleDescriptor$Provides +java.util.ImmutableCollections$AbstractImmutableList +java.util.ImmutableCollections$List12 +java.util.ImmutableCollections$ListN +java.lang.module.ModuleDescriptor$Opens +jdk.internal.module.ModuleTarget +jdk.internal.module.ModuleHashes +java.util.Collections$UnmodifiableMap +java.util.HashMap +java.util.Map$Entry +java.util.HashMap$Node +java.lang.module.Configuration +java.lang.module.ResolvedModule +java.util.function.Function +jdk.internal.module.ModuleLoaderMap$Mapper +java.util.ImmutableCollections +java.lang.ModuleLayer +jdk.internal.math.FDBigInteger +java.lang.NullPointerException +java.lang.ArithmeticException +java.io.ObjectStreamField +java.util.Comparator +java.lang.String$CaseInsensitiveComparator +java.lang.Module$ArchivedData +jdk.internal.misc.CDS +java.util.Objects +jdk.internal.access.JavaLangReflectAccess +java.lang.reflect.ReflectAccess +jdk.internal.access.SharedSecrets +java.lang.invoke.MethodHandles +java.lang.invoke.MemberName$Factory +java.security.Guard +java.security.Permission +java.security.BasicPermission +java.lang.reflect.ReflectPermission +java.lang.StringLatin1 +java.lang.invoke.MethodHandles$Lookup +jdk.internal.reflect.Reflection +java.lang.Math +java.util.AbstractSet +java.util.ImmutableCollections$MapN$1 +java.util.ImmutableCollections$MapN$MapNIterator +java.util.KeyValueHolder +java.util.LinkedHashMap$Entry +java.util.HashMap$TreeNode +java.lang.Runtime +java.util.concurrent.locks.Lock +java.util.concurrent.locks.ReentrantLock +java.util.concurrent.ConcurrentHashMap$Segment +java.util.concurrent.ConcurrentHashMap$CounterCell +java.util.concurrent.ConcurrentHashMap$Node +java.util.concurrent.locks.LockSupport +java.util.concurrent.ConcurrentHashMap$ReservationNode +java.security.PrivilegedAction +jdk.internal.reflect.ReflectionFactory$GetReflectionFactoryAction +jdk.internal.reflect.ReflectionFactory +java.lang.ref.Reference$ReferenceHandler +jdk.internal.ref.Cleaner +java.lang.ref.ReferenceQueue +java.lang.ref.ReferenceQueue$Null +java.lang.ref.ReferenceQueue$Lock +jdk.internal.access.JavaLangRefAccess +java.lang.ref.Reference$1 +java.lang.ref.Finalizer$FinalizerThread +jdk.internal.access.JavaLangAccess +java.lang.System$2 +jdk.internal.misc.VM +jdk.internal.util.SystemProps +jdk.internal.util.SystemProps$Raw +java.lang.StringConcatHelper +java.lang.VersionProps +java.util.Arrays +java.lang.CharacterData +java.lang.CharacterDataLatin1 +java.util.HashMap$EntrySet +java.util.HashMap$HashIterator +java.util.HashMap$EntryIterator +jdk.internal.util.StaticProperty +java.io.FileInputStream +java.io.FileDescriptor +jdk.internal.access.JavaIOFileDescriptorAccess +java.io.FileDescriptor$1 +java.io.Flushable +java.io.OutputStream +java.io.FileOutputStream +java.io.FilterInputStream +java.io.BufferedInputStream +java.io.FilterOutputStream +java.io.PrintStream +java.io.BufferedOutputStream +java.io.Writer +java.io.OutputStreamWriter +java.nio.charset.Charset +java.nio.charset.spi.CharsetProvider +sun.nio.cs.StandardCharsets +java.lang.ThreadLocal +java.util.concurrent.atomic.AtomicInteger +sun.security.action.GetPropertyAction +sun.nio.cs.HistoricallyNamedCharset +sun.nio.cs.Unicode +sun.nio.cs.UTF_8 +sun.nio.cs.StreamEncoder +java.nio.charset.CharsetEncoder +sun.nio.cs.UTF_8$Encoder +java.nio.charset.CodingErrorAction +java.nio.ByteBuffer +jdk.internal.misc.ScopedMemoryAccess +jdk.internal.access.JavaNioAccess +java.nio.Buffer$1 +java.nio.HeapByteBuffer +java.nio.ByteOrder +java.io.BufferedWriter +java.lang.Terminator +jdk.internal.misc.Signal$Handler +java.lang.Terminator$1 +jdk.internal.misc.Signal +java.util.Hashtable$Entry +jdk.internal.misc.Signal$NativeHandler +jdk.internal.misc.OSEnvironment +java.util.Collections +java.util.Collections$EmptySet +java.util.Collections$EmptyList +java.util.Collections$EmptyMap +java.lang.IllegalArgumentException +java.lang.invoke.MethodHandleStatics +jdk.internal.module.ModuleBootstrap +sun.invoke.util.VerifyAccess +java.lang.reflect.Modifier +jdk.internal.access.JavaLangModuleAccess +java.lang.module.ModuleDescriptor$1 +java.io.File +java.io.DefaultFileSystem +java.io.FileSystem +java.io.UnixFileSystem +jdk.internal.util.ArraysSupport +jdk.internal.module.ModulePatcher +jdk.internal.module.ModuleBootstrap$Counters +jdk.internal.module.ArchivedBootLayer +jdk.internal.access.JavaNetUriAccess +java.net.URI$1 +jdk.internal.loader.ArchivedClassLoaders +jdk.internal.loader.ClassLoaders$BootClassLoader +java.security.cert.Certificate +java.lang.ClassLoader$ParallelLoaders +java.util.WeakHashMap +java.util.WeakHashMap$Entry +java.util.Collections$SetFromMap +java.util.WeakHashMap$KeySet +jdk.internal.access.JavaSecurityAccess +java.security.ProtectionDomain$JavaSecurityAccessImpl +java.security.ProtectionDomain$Key +java.security.Principal +jdk.internal.loader.NativeLibraries +jdk.internal.loader.ClassLoaderHelper +java.util.HashSet +java.util.Queue +java.util.Deque +java.util.ArrayDeque +jdk.internal.loader.URLClassPath +java.net.URLStreamHandlerFactory +java.net.URL$DefaultFactory +jdk.internal.access.JavaNetURLAccess +java.net.URL$3 +java.io.File$PathStatus +sun.net.www.ParseUtil +java.util.HexFormat +java.net.URLStreamHandler +sun.net.www.protocol.file.Handler +sun.net.util.IPAddressUtil +jdk.internal.util.Preconditions +sun.net.www.protocol.jar.Handler +jdk.internal.module.ServicesCatalog +jdk.internal.loader.AbstractClassLoaderValue +jdk.internal.loader.ClassLoaderValue +java.util.Optional +jdk.internal.loader.BootLoader +jdk.internal.loader.BuiltinClassLoader$LoadedModule +java.util.ImmutableCollections$SetN$SetNIterator +java.util.ImmutableCollections$Set12$1 +java.util.ListIterator +java.util.ImmutableCollections$ListItr +jdk.internal.module.ModuleLoaderMap +jdk.internal.loader.AbstractClassLoaderValue$Memoizer +jdk.internal.module.ServicesCatalog$ServiceProvider +java.util.concurrent.CopyOnWriteArrayList +java.util.HashMap$KeySet +java.util.HashMap$KeyIterator +java.lang.ModuleLayer$Controller +java.lang.invoke.LambdaMetafactory +java.lang.invoke.MethodType$ConcurrentWeakInternSet +java.lang.Void +java.lang.invoke.MethodTypeForm +java.lang.invoke.MethodType$ConcurrentWeakInternSet$WeakEntry +sun.invoke.util.Wrapper +sun.invoke.util.Wrapper$Format +java.lang.invoke.LambdaForm$NamedFunction +java.lang.invoke.DirectMethodHandle$Holder +sun.invoke.util.ValueConversions +java.lang.invoke.MethodHandleImpl +java.lang.invoke.Invokers +java.lang.invoke.LambdaForm$Kind +java.lang.NoSuchMethodException +java.lang.invoke.LambdaForm$BasicType +java.lang.reflect.Array +java.lang.invoke.LambdaForm$Name +java.lang.invoke.LambdaForm$Holder +java.lang.invoke.InvokerBytecodeGenerator +java.lang.invoke.InvokerBytecodeGenerator$2 +java.lang.invoke.MethodHandleImpl$Intrinsic +java.lang.invoke.BootstrapMethodInvoker +java.lang.invoke.VarHandle$AccessMode +java.lang.invoke.VarHandle$AccessType +java.lang.invoke.Invokers$Holder +jdk.internal.access.JavaLangInvokeAccess +java.lang.invoke.MethodHandleImpl$1 +java.lang.invoke.AbstractValidatingLambdaMetafactory +java.lang.invoke.InnerClassLambdaMetafactory +jdk.internal.org.objectweb.asm.Type +sun.security.action.GetBooleanAction +jdk.internal.org.objectweb.asm.Handle +sun.invoke.util.BytecodeDescriptor +jdk.internal.org.objectweb.asm.ConstantDynamic +java.lang.invoke.MethodHandleInfo +java.lang.invoke.InfoFromMemberName +jdk.internal.org.objectweb.asm.ClassVisitor +jdk.internal.org.objectweb.asm.ClassWriter +jdk.internal.org.objectweb.asm.SymbolTable +jdk.internal.org.objectweb.asm.Symbol +jdk.internal.org.objectweb.asm.SymbolTable$Entry +jdk.internal.org.objectweb.asm.ByteVector +java.lang.invoke.LambdaProxyClassArchive +jdk.internal.org.objectweb.asm.MethodVisitor +jdk.internal.org.objectweb.asm.MethodWriter +jdk.internal.org.objectweb.asm.Label +java.lang.invoke.TypeConvertingMethodAdapter +java.lang.invoke.InnerClassLambdaMetafactory$ForwardingMethodGenerator +jdk.internal.org.objectweb.asm.Handler +jdk.internal.org.objectweb.asm.Attribute +jdk.internal.org.objectweb.asm.AnnotationVisitor +jdk.internal.org.objectweb.asm.AnnotationWriter +java.lang.invoke.MethodHandles$Lookup$ClassOption +java.lang.invoke.MethodHandles$Lookup$ClassFile +jdk.internal.org.objectweb.asm.ClassReader +java.lang.StringUTF16 +java.lang.invoke.MethodHandles$Lookup$ClassDefiner +jdk.internal.module.ModuleBootstrap$$Lambda$1/0x00007faab4040850 +java.lang.invoke.InnerClassLambdaMetafactory$1 +java.lang.Class$ReflectionData +java.lang.Class$Atomic +jdk.internal.reflect.DelegatingConstructorAccessorImpl +java.lang.invoke.BoundMethodHandle +java.lang.invoke.ClassSpecializer +java.lang.invoke.BoundMethodHandle$Specializer +java.lang.invoke.ClassSpecializer$1 +java.lang.invoke.ClassSpecializer$SpeciesData +java.lang.invoke.BoundMethodHandle$SpeciesData +java.lang.invoke.ClassSpecializer$Factory +java.lang.invoke.BoundMethodHandle$Specializer$Factory +java.lang.invoke.SimpleMethodHandle +java.lang.NoSuchFieldException +java.lang.invoke.BoundMethodHandle$Species_L +sun.invoke.util.VerifyType +sun.invoke.empty.Empty +java.lang.invoke.DirectMethodHandle$2 +java.lang.invoke.DirectMethodHandle$Accessor +java.lang.invoke.DelegatingMethodHandle +java.lang.invoke.MethodHandleImpl$IntrinsicMethodHandle +java.lang.invoke.DelegatingMethodHandle$Holder +sun.invoke.util.Wrapper$1 +java.lang.invoke.LambdaFormEditor +java.lang.invoke.LambdaFormEditor$TransformKey +java.lang.invoke.LambdaFormBuffer +java.lang.invoke.LambdaFormEditor$Transform +jdk.internal.org.objectweb.asm.Frame +java.lang.invoke.InvokerBytecodeGenerator$ClassData +java.util.ArrayList$Itr +jdk.internal.org.objectweb.asm.FieldVisitor +jdk.internal.org.objectweb.asm.FieldWriter +java.lang.invoke.LambdaForm$MH/0x00007faab4000400 +jdk.internal.ref.CleanerFactory +java.util.concurrent.ThreadFactory +jdk.internal.ref.CleanerFactory$1 +java.lang.ref.Cleaner +java.lang.ref.Cleaner$1 +jdk.internal.ref.CleanerImpl +java.lang.ref.Cleaner$Cleanable +jdk.internal.ref.PhantomCleanable +jdk.internal.ref.CleanerImpl$PhantomCleanableRef +jdk.internal.ref.CleanerImpl$CleanerCleanable +jdk.internal.misc.InnocuousThread +java.util.ArrayList$SubList +java.lang.Module$ReflectionData +java.lang.WeakPairMap +java.lang.WeakPairMap$Pair +java.lang.WeakPairMap$Pair$Lookup +java.util.function.BiFunction +java.lang.Module$$Lambda$2/0x00007faab4040a90 +java.lang.WeakPairMap$WeakRefPeer +java.lang.WeakPairMap$Pair$Weak +java.lang.WeakPairMap$Pair$Weak$1 +java.lang.WeakPairMap$$Lambda$3/0x00007faab40413e8 +java.lang.invoke.DirectMethodHandle$Constructor +java.lang.invoke.StringConcatFactory +java.lang.invoke.StringConcatFactory$1 +java.lang.invoke.StringConcatFactory$2 +java.lang.invoke.StringConcatFactory$3 +sun.launcher.LauncherHelper +java.lang.StringCoding +java.util.zip.ZipConstants +java.util.zip.ZipFile +java.util.jar.JarFile +jdk.internal.access.JavaUtilZipFileAccess +java.util.zip.ZipFile$1 +jdk.internal.access.JavaUtilJarAccess +java.util.jar.JavaUtilJarAccessImpl +java.lang.Runtime$Version +java.util.zip.ZipFile$CleanableResource +java.util.zip.ZipCoder +java.util.zip.ZipCoder$UTF8ZipCoder +java.util.zip.ZipFile$Source +sun.nio.fs.DefaultFileSystemProvider +java.nio.file.spi.FileSystemProvider +sun.nio.fs.AbstractFileSystemProvider +sun.nio.fs.UnixFileSystemProvider +sun.nio.fs.LinuxFileSystemProvider +java.nio.file.OpenOption +java.nio.file.StandardOpenOption +java.nio.file.FileSystem +sun.nio.fs.UnixFileSystem +sun.nio.fs.LinuxFileSystem +java.nio.file.Watchable +java.nio.file.Path +sun.nio.fs.UnixPath +sun.nio.fs.Util +sun.nio.fs.UnixNativeDispatcher +jdk.internal.loader.NativeLibraries$LibraryPaths +jdk.internal.loader.NativeLibraries$1 +java.util.ArrayDeque$DeqIterator +jdk.internal.loader.NativeLibrary +jdk.internal.loader.NativeLibraries$NativeLibraryImpl +java.util.concurrent.ConcurrentHashMap$CollectionView +java.util.concurrent.ConcurrentHashMap$ValuesView +java.util.concurrent.ConcurrentHashMap$Traverser +java.util.concurrent.ConcurrentHashMap$BaseIterator +java.util.Enumeration +java.util.concurrent.ConcurrentHashMap$ValueIterator +java.nio.file.attribute.BasicFileAttributes +java.nio.file.attribute.PosixFileAttributes +sun.nio.fs.UnixFileAttributes +sun.nio.fs.UnixFileStoreAttributes +sun.nio.fs.UnixMountEntry +java.util.zip.ZipFile$Source$Key +java.nio.file.CopyOption +java.nio.file.LinkOption +java.nio.file.Files +java.nio.file.attribute.DosFileAttributes +java.nio.file.attribute.AttributeView +java.nio.file.attribute.FileAttributeView +java.nio.file.attribute.BasicFileAttributeView +java.nio.file.attribute.DosFileAttributeView +java.nio.file.attribute.UserDefinedFileAttributeView +sun.nio.fs.UnixFileAttributeViews +sun.nio.fs.DynamicFileAttributeView +sun.nio.fs.AbstractBasicFileAttributeView +sun.nio.fs.UnixFileAttributeViews$Basic +sun.nio.fs.NativeBuffers +jdk.internal.misc.TerminatingThreadLocal +sun.nio.fs.NativeBuffers$1 +jdk.internal.misc.TerminatingThreadLocal$1 +java.lang.ThreadLocal$ThreadLocalMap +java.lang.ThreadLocal$ThreadLocalMap$Entry +java.util.IdentityHashMap +java.util.IdentityHashMap$KeySet +sun.nio.fs.NativeBuffer +sun.nio.fs.NativeBuffer$Deallocator +sun.nio.fs.UnixFileAttributes$UnixAsBasicFileAttributes +java.io.DataOutput +java.io.DataInput +java.io.RandomAccessFile +jdk.internal.access.JavaIORandomAccessFileAccess +java.io.RandomAccessFile$2 +java.io.FileCleanable +java.util.zip.ZipFile$Source$End +java.util.zip.ZipUtils +java.util.concurrent.TimeUnit +java.nio.file.attribute.FileTime +jdk.internal.perf.PerfCounter +jdk.internal.perf.Perf$GetPerfAction +jdk.internal.perf.Perf +jdk.internal.perf.PerfCounter$CoreCounters +sun.nio.ch.DirectBuffer +java.nio.MappedByteBuffer +java.nio.DirectByteBuffer +java.nio.Bits +java.util.concurrent.atomic.AtomicLong +jdk.internal.misc.VM$BufferPool +java.nio.Bits$1 +java.nio.LongBuffer +java.nio.DirectLongBufferU +java.util.zip.ZipEntry +java.util.jar.JarEntry +java.util.jar.JarFile$JarFileEntry +java.util.zip.ZipFile$ZipFileInputStream +java.util.zip.InflaterInputStream +java.util.zip.ZipFile$ZipFileInflaterInputStream +java.util.zip.Inflater +java.util.zip.Inflater$InflaterZStreamRef +java.util.zip.ZipFile$InflaterCleanupAction +sun.security.util.SignatureFileVerifier +sun.security.util.Debug +java.util.Locale +sun.util.locale.LocaleUtils +sun.security.action.GetIntegerAction +java.util.jar.JarVerifier +java.security.CodeSigner +java.io.ByteArrayOutputStream +java.util.jar.Attributes +java.util.LinkedHashMap +java.util.jar.Manifest$FastInputStream +java.io.RandomAccessFile$1 +sun.net.util.URLUtil +java.security.PrivilegedExceptionAction +jdk.internal.loader.URLClassPath$3 +jdk.internal.loader.URLClassPath$Loader +jdk.internal.loader.URLClassPath$JarLoader +jdk.internal.loader.URLClassPath$JarLoader$1 +jdk.internal.loader.FileURLMapper +jdk.internal.util.jar.JarIndex +java.util.StringTokenizer +jdk.internal.loader.Resource +jdk.internal.loader.URLClassPath$JarLoader$2 +java.lang.NamedPackage +java.lang.Package +java.lang.Package$VersionInfo +sun.nio.ByteBuffered +java.util.zip.Checksum +java.util.zip.CRC32 +java.util.zip.Checksum$1 +java.security.SecureClassLoader$CodeSourceKey +java.security.SecureClassLoader$1 +java.security.PermissionCollection +sun.security.util.LazyCodeSourcePermissionCollection +java.security.Permissions +java.lang.RuntimePermission +java.security.BasicPermissionCollection +java.security.AllPermission +java.security.UnresolvedPermission +java.security.SecureClassLoader$DebugHolder +org.apache.maven.surefire.booter.ForkedBooter +org.apache.maven.surefire.api.fork.ForkNodeArguments +org.apache.maven.plugin.surefire.log.api.ConsoleLogger +java.lang.SecurityException +java.security.AccessControlException +java.io.IOException +org.apache.maven.surefire.api.provider.CommandListener +org.apache.maven.surefire.api.report.ReporterFactory +java.util.concurrent.ConcurrentHashMap$ForwardingNode +org.apache.maven.surefire.api.provider.CommandChainReader +java.lang.InterruptedException +java.util.concurrent.Executor +java.util.concurrent.ExecutorService +java.util.concurrent.ScheduledExecutorService +java.lang.PublicMethods$MethodList +java.lang.PublicMethods$Key +java.util.concurrent.Semaphore +java.util.concurrent.locks.AbstractQueuedSynchronizer +java.util.concurrent.Semaphore$Sync +java.util.concurrent.Semaphore$NonfairSync +org.apache.maven.surefire.booter.BooterDeserializer +org.apache.maven.surefire.booter.SystemPropertyManager +java.util.Properties$LineReader +java.util.Properties$EntrySet +java.util.concurrent.ConcurrentHashMap$EntrySetView +java.util.Collections$SynchronizedCollection +java.util.Collections$SynchronizedSet +java.util.concurrent.ConcurrentHashMap$EntryIterator +java.util.concurrent.ConcurrentHashMap$MapEntry +java.util.Collections$UnmodifiableCollection +java.util.Collections$UnmodifiableSet +java.util.Collections$UnmodifiableCollection$1 +org.apache.maven.surefire.booter.KeyValueSource +org.apache.maven.surefire.booter.PropertiesWrapper +java.lang.IllegalStateException +java.io.FileInputStream$1 +org.apache.maven.surefire.booter.TypeEncodedValue +org.apache.maven.surefire.api.testset.DirectoryScannerParameters +org.apache.maven.surefire.api.util.RunOrder +org.apache.maven.surefire.api.testset.RunOrderParameters +org.apache.maven.surefire.api.testset.TestArtifactInfo +org.apache.maven.surefire.api.testset.TestRequest +org.apache.maven.surefire.api.testset.TestFilter +org.apache.maven.surefire.api.testset.GenericTestPattern +org.apache.maven.surefire.api.testset.TestListResolver +java.util.Collections$SingletonSet +org.apache.maven.surefire.api.testset.IncludedExcludedPatterns +java.util.LinkedHashSet +java.util.Collections$1 +org.apache.maven.surefire.shared.utils.StringUtils +java.lang.IndexOutOfBoundsException +java.lang.StringIndexOutOfBoundsException +org.apache.maven.surefire.api.testset.ResolvedTest +org.apache.maven.surefire.api.testset.ResolvedTest$Type +org.apache.maven.surefire.api.testset.ResolvedTest$ClassMatcher +org.apache.maven.surefire.api.testset.ResolvedTest$MethodMatcher +org.apache.maven.surefire.api.report.ReporterConfiguration +org.apache.maven.surefire.api.booter.Shutdown +java.lang.Class$3 +jdk.internal.reflect.NativeMethodAccessorImpl +jdk.internal.reflect.DelegatingMethodAccessorImpl +org.apache.maven.surefire.booter.ProviderConfiguration +org.apache.maven.surefire.api.cli.CommandLineOption +org.apache.maven.surefire.api.booter.DumpErrorSingleton +org.apache.maven.surefire.api.util.internal.DumpFileUtils +java.lang.ProcessEnvironment +java.lang.ProcessEnvironment$ExternalData +java.lang.ProcessEnvironment$Variable +java.lang.ProcessEnvironment$Value +java.lang.ProcessEnvironment$StringEnvironment +java.lang.management.ManagementFactory +java.lang.IncompatibleClassChangeError +java.lang.NoSuchMethodError +java.lang.invoke.LambdaForm$DMH/0x00007faab4006000 +java.lang.management.ManagementFactory$$Lambda$4/0x00007faab40451f8 +java.lang.management.PlatformManagedObject +java.lang.management.RuntimeMXBean +java.lang.management.ManagementFactory$PlatformMBeanFinder +java.lang.management.ManagementFactory$PlatformMBeanFinder$1 +java.io.FilePermission +jdk.internal.access.JavaIOFilePermissionAccess +java.io.FilePermission$1 +sun.security.util.FilePermCompat +sun.security.util.SecurityProperties +java.security.Security +jdk.internal.access.JavaSecuritySystemConfiguratorAccess +java.security.Security$1 +java.security.Security$2 +java.security.SystemConfigurator +java.security.SystemConfigurator$1 +sun.security.util.PropertyExpander +java.net.URLConnection +sun.net.www.URLConnection +sun.net.www.protocol.file.FileURLConnection +sun.net.www.MessageHeader +sun.net.ProgressMonitor +sun.net.ProgressMeteringPolicy +sun.net.DefaultProgressMeteringPolicy +jdk.internal.access.JavaSecurityPropertiesAccess +java.security.Security$3 +sun.management.spi.PlatformMBeanProvider +java.util.ServiceLoader +java.util.ServiceLoader$ModuleServicesLookupIterator +java.util.ServiceLoader$LazyClassPathLookupIterator +java.util.ServiceLoader$2 +java.util.ServiceLoader$3 +java.util.concurrent.CopyOnWriteArrayList$COWIterator +com.sun.management.internal.PlatformMBeanProviderImpl +java.util.ServiceLoader$1 +java.util.ServiceLoader$Provider +java.util.ServiceLoader$ProviderImpl +com.sun.management.internal.PlatformMBeanProviderImpl$$Lambda$5/0x00007faab40468a0 +sun.management.spi.PlatformMBeanProvider$PlatformComponent +com.sun.management.internal.PlatformMBeanProviderImpl$1 +java.util.stream.BaseStream +java.util.stream.Stream +java.util.Spliterators +java.util.Spliterators$EmptySpliterator +java.util.Spliterator +java.util.Spliterators$EmptySpliterator$OfRef +java.util.Spliterator$OfPrimitive +java.util.Spliterator$OfInt +java.util.Spliterators$EmptySpliterator$OfInt +java.util.Spliterator$OfLong +java.util.Spliterators$EmptySpliterator$OfLong +java.util.Spliterator$OfDouble +java.util.Spliterators$EmptySpliterator$OfDouble +java.util.Spliterators$ArraySpliterator +java.util.stream.StreamSupport +java.util.stream.PipelineHelper +java.util.stream.AbstractPipeline +java.util.stream.ReferencePipeline +java.util.stream.ReferencePipeline$Head +java.util.stream.StreamOpFlag +java.util.stream.StreamOpFlag$Type +java.util.stream.StreamOpFlag$MaskBuilder +java.util.EnumMap +java.util.EnumMap$1 +sun.reflect.annotation.AnnotationParser +java.util.stream.Collectors +java.util.stream.Collector$Characteristics +java.util.EnumSet +java.util.RegularEnumSet +java.util.stream.Collector +java.util.stream.Collectors$CollectorImpl +java.util.stream.Collectors$$Lambda$30/0x800000041 +java.util.function.BiConsumer +java.lang.invoke.DirectMethodHandle$Interface +java.util.stream.Collectors$$Lambda$22/0x800000035 +java.util.function.BinaryOperator +java.util.stream.Collectors$$Lambda$25/0x80000003c +java.util.stream.Collectors$$Lambda$27/0x80000003e +java.util.stream.ReduceOps +java.util.stream.TerminalOp +java.util.stream.ReduceOps$ReduceOp +java.util.stream.ReduceOps$3 +java.util.stream.StreamShape +java.util.stream.ReduceOps$Box +java.util.function.Consumer +java.util.stream.Sink +java.util.stream.TerminalSink +java.util.stream.ReduceOps$AccumulatingSink +java.util.stream.ReduceOps$3ReducingSink +com.sun.management.internal.PlatformMBeanProviderImpl$2 +com.sun.management.internal.PlatformMBeanProviderImpl$3 +com.sun.management.internal.PlatformMBeanProviderImpl$4 +javax.management.DynamicMBean +com.sun.management.DiagnosticCommandMBean +javax.management.NotificationBroadcaster +javax.management.NotificationEmitter +sun.management.NotificationEmitterSupport +com.sun.management.internal.DiagnosticCommandImpl +sun.management.ManagementFactoryHelper +sun.management.VMManagement +sun.management.VMManagementImpl +com.sun.management.internal.PlatformMBeanProviderImpl$5 +java.util.Collections$UnmodifiableList +java.util.Collections$UnmodifiableRandomAccessList +jdk.management.jfr.internal.FlightRecorderMXBeanProvider +java.util.concurrent.Callable +java.util.Collections$EmptyEnumeration +java.lang.management.DefaultPlatformMBeanProvider +java.lang.management.DefaultPlatformMBeanProvider$1 +java.lang.management.DefaultPlatformMBeanProvider$2 +java.lang.management.DefaultPlatformMBeanProvider$3 +java.lang.management.DefaultPlatformMBeanProvider$4 +java.lang.management.DefaultPlatformMBeanProvider$5 +java.lang.management.DefaultPlatformMBeanProvider$6 +java.lang.management.DefaultPlatformMBeanProvider$7 +java.lang.management.DefaultPlatformMBeanProvider$8 +sun.management.ManagementFactoryHelper$LoggingMXBeanAccess +sun.management.ManagementFactoryHelper$LoggingMXBeanAccess$1 +java.util.logging.LogManager +java.lang.management.DefaultPlatformMBeanProvider$9 +java.lang.management.DefaultPlatformMBeanProvider$10 +java.lang.management.DefaultPlatformMBeanProvider$11 +jdk.management.jfr.FlightRecorderMXBean +jdk.management.jfr.internal.FlightRecorderMXBeanProvider$SingleMBeanComponent +java.util.Collections$SingletonList +java.util.HashMap$Values +java.util.HashMap$HashMapSpliterator +java.util.HashMap$ValueSpliterator +java.util.function.Predicate +java.lang.management.ManagementFactory$PlatformMBeanFinder$$Lambda$10/0x00007faab404c288 +java.util.stream.ReferencePipeline$StatelessOp +java.util.stream.ReferencePipeline$2 +java.lang.management.ManagementFactory$PlatformMBeanFinder$$Lambda$11/0x00007faab404c4e0 +java.util.stream.ReduceOps$2 +java.util.stream.ReduceOps$2ReducingSink +java.util.stream.Sink$ChainedReference +java.util.stream.ReferencePipeline$2$1 +sun.management.RuntimeImpl +java.util.Collections$SingletonMap +java.util.Collections$2 +java.lang.invoke.LambdaForm$DMH/0x00007faab4006400 +sun.management.spi.PlatformMBeanProvider$PlatformComponent$$Lambda$12/0x00007faab404d2d0 +sun.management.spi.PlatformMBeanProvider$PlatformComponent$$Lambda$13/0x00007faab404d528 +java.util.stream.ReferencePipeline$3 +java.util.stream.Collectors$$Lambda$14/0x00007faab404d770 +java.util.stream.Collectors$$Lambda$15/0x00007faab404d990 +java.util.stream.Collectors$$Lambda$16/0x00007faab404dbc0 +java.util.stream.ReferencePipeline$3$1 +sun.management.Util +java.lang.management.ManagementPermission +java.util.Arrays$ArrayList +java.util.Arrays$ArrayItr +org.apache.maven.surefire.booter.ClassLoaderConfiguration +org.apache.maven.surefire.booter.AbstractPathConfiguration +org.apache.maven.surefire.booter.ClasspathConfiguration +org.apache.maven.surefire.booter.Classpath +java.net.MalformedURLException +java.net.URLClassLoader +org.apache.maven.surefire.booter.IsolatedClassLoader +org.apache.maven.surefire.booter.SurefireExecutionException +org.apache.maven.surefire.booter.ProcessCheckerType +org.apache.maven.surefire.booter.StartupConfiguration +org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory +java.util.Spliterators$1Adapter +java.util.HashMap$ValueIterator +jdk.internal.module.Resources +jdk.internal.loader.BuiltinClassLoader$2 +jdk.internal.loader.BuiltinClassLoader$5 +java.lang.module.ModuleReader +jdk.internal.module.SystemModuleFinders$SystemModuleReader +jdk.internal.module.SystemModuleFinders$SystemImage +jdk.internal.jimage.ImageReaderFactory +java.nio.file.Paths +java.nio.file.FileSystems +java.nio.file.FileSystems$DefaultFileSystemHolder +java.nio.file.FileSystems$DefaultFileSystemHolder$1 +java.net.URI$Parser +jdk.internal.jimage.ImageReaderFactory$1 +jdk.internal.jimage.ImageReader +jdk.internal.jimage.BasicImageReader +jdk.internal.jimage.ImageReader$SharedImageReader +jdk.internal.jimage.BasicImageReader$1 +jdk.internal.jimage.NativeImageBuffer +jdk.internal.jimage.NativeImageBuffer$1 +jdk.internal.jimage.ImageHeader +java.nio.IntBuffer +java.nio.DirectIntBufferU +java.nio.DirectByteBufferR +java.nio.DirectIntBufferRU +jdk.internal.jimage.ImageStrings +jdk.internal.jimage.ImageStringsReader +jdk.internal.jimage.decompressor.Decompressor +jdk.internal.jimage.ImageLocation +java.util.Collections$EmptyIterator +jdk.internal.loader.BuiltinClassLoader$1 +java.lang.CompoundEnumeration +jdk.internal.loader.URLClassPath$1 +jdk.internal.loader.URLClassPath$FileLoader +java.util.SortedSet +java.util.NavigableSet +java.util.TreeSet +java.util.SortedMap +java.util.NavigableMap +java.util.TreeMap +java.util.TreeMap$Entry +java.util.TreeMap$KeySet +java.util.TreeMap$PrivateEntryIterator +java.util.TreeMap$KeyIterator +java.lang.Readable +java.io.Reader +java.io.BufferedReader +java.io.InputStreamReader +sun.nio.cs.StreamDecoder +java.nio.charset.CharsetDecoder +sun.nio.cs.UTF_8$Decoder +java.util.Vector +java.nio.CharBuffer +java.nio.HeapCharBuffer +java.nio.charset.CoderResult +java.util.AbstractSequentialList +java.util.LinkedList +java.util.LinkedList$Node +java.net.JarURLConnection +sun.net.www.protocol.jar.JarURLConnection +sun.net.www.protocol.jar.URLJarFile$URLJarFileCloseController +sun.net.www.protocol.jar.JarFileFactory +sun.net.www.protocol.jar.URLJarFile +sun.nio.fs.UnixFileKey +sun.net.www.protocol.jar.URLJarFile$URLJarFileEntry +sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream +java.util.LinkedHashMap$LinkedKeySet +java.util.LinkedHashMap$LinkedHashIterator +java.util.LinkedHashMap$LinkedKeyIterator +org.apache.maven.surefire.booter.spi.AbstractMasterProcessChannelProcessorFactory +org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory +org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder +org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder +org.apache.maven.surefire.api.util.internal.DaemonThreadFactory +java.util.concurrent.Executors +java.util.concurrent.Executors$DefaultThreadFactory +org.apache.maven.surefire.api.util.internal.DaemonThreadFactory$NamedThreadFactory +java.util.concurrent.AbstractExecutorService +java.util.concurrent.ThreadPoolExecutor +java.util.concurrent.ScheduledThreadPoolExecutor +java.util.concurrent.RejectedExecutionHandler +java.util.concurrent.ThreadPoolExecutor$AbortPolicy +java.util.concurrent.BlockingQueue +java.util.AbstractQueue +java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue +java.util.concurrent.Future +java.util.concurrent.RunnableFuture +java.util.concurrent.Delayed +java.util.concurrent.ScheduledFuture +java.util.concurrent.RunnableScheduledFuture +java.util.concurrent.locks.ReentrantLock$Sync +java.util.concurrent.locks.ReentrantLock$NonfairSync +java.util.concurrent.locks.Condition +java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject +org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory +java.net.URISyntaxException +java.util.concurrent.ExecutionException +java.net.SocketAddress +java.net.InetSocketAddress +java.nio.channels.Channel +java.nio.channels.AsynchronousChannel +java.nio.channels.AsynchronousByteChannel +org.apache.maven.surefire.booter.ForkedNodeArg +java.lang.UnsupportedOperationException +org.apache.maven.plugin.surefire.log.api.NullConsoleLogger +org.apache.maven.surefire.api.util.internal.Channels +org.apache.maven.surefire.api.util.internal.Channels$2 +org.apache.maven.surefire.api.util.internal.Channels$1 +java.nio.channels.WritableByteChannel +org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel +java.nio.channels.ReadableByteChannel +org.apache.maven.surefire.api.util.internal.AbstractNoninterruptibleWritableChannel +org.apache.maven.surefire.api.util.internal.Channels$4 +java.nio.channels.ClosedChannelException +java.nio.channels.NonWritableChannelException +org.apache.maven.surefire.booter.spi.AbstractMasterProcessChannelProcessorFactory$1 +java.util.concurrent.FutureTask +java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask +java.lang.invoke.VarHandles +java.lang.invoke.VarHandleInts$FieldInstanceReadOnly +java.lang.invoke.VarHandleInts$FieldInstanceReadWrite +java.lang.invoke.VarHandle$1 +jdk.internal.util.Preconditions$1 +java.lang.invoke.VarHandleGuards +java.lang.invoke.VarForm +java.lang.invoke.VarHandleReferences$FieldInstanceReadOnly +java.lang.invoke.VarHandleReferences$FieldInstanceReadWrite +java.util.concurrent.FutureTask$WaitNode +java.util.concurrent.Executors$RunnableAdapter +java.util.concurrent.ThreadPoolExecutor$Worker +java.lang.Thread$State +java.util.concurrent.TimeUnit$1 +java.time.temporal.TemporalUnit +java.time.temporal.ChronoUnit +java.time.temporal.TemporalAmount +java.time.Duration +java.math.BigInteger +org.apache.maven.surefire.api.stream.AbstractStreamEncoder +org.apache.maven.surefire.booter.stream.EventEncoder +org.apache.maven.surefire.booter.spi.EventChannelEncoder +java.lang.invoke.VarHandle$AccessDescriptor +java.lang.AssertionError +org.apache.maven.surefire.api.booter.ForkedProcessEventType +java.util.concurrent.ForkJoinPool$ManagedBlocker +java.util.concurrent.locks.AbstractQueuedSynchronizer$Node +java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode +org.apache.maven.surefire.api.report.ReportEntry +java.util.concurrent.atomic.AtomicBoolean +org.apache.maven.surefire.booter.spi.CommandChannelDecoder +org.apache.maven.surefire.api.stream.MalformedChannelException +org.apache.maven.surefire.api.stream.AbstractStreamDecoder +org.apache.maven.surefire.booter.stream.CommandDecoder +org.apache.maven.surefire.api.util.internal.AbstractNoninterruptibleReadableChannel +org.apache.maven.surefire.api.util.internal.Channels$3 +java.nio.channels.NonReadableChannelException +java.io.EOFException +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$MalformedFrameException +java.io.FileNotFoundException +org.apache.maven.surefire.api.stream.SegmentType +org.apache.maven.surefire.api.booter.Constants +java.nio.charset.StandardCharsets +sun.nio.cs.US_ASCII +sun.nio.cs.ISO_8859_1 +sun.nio.cs.UTF_16BE +sun.nio.cs.UTF_16LE +sun.nio.cs.UTF_16 +org.apache.maven.surefire.api.booter.MasterProcessCommand +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$Segment +org.apache.maven.surefire.booter.ForkedBooter$8 +org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils +java.lang.ApplicationShutdownHooks +java.lang.ApplicationShutdownHooks$1 +java.lang.Shutdown +java.lang.Shutdown$Lock +org.apache.maven.surefire.api.booter.ForkingReporterFactory +org.apache.maven.surefire.api.report.RunListener +org.apache.maven.surefire.api.report.TestOutputReceiver +org.apache.maven.surefire.api.report.TestReportListener +org.apache.maven.surefire.api.booter.ForkingRunListener +org.apache.maven.surefire.booter.CommandReader +org.apache.maven.surefire.api.testset.TestSetFailedException +java.util.concurrent.ConcurrentLinkedQueue +java.util.concurrent.ConcurrentLinkedQueue$Node +org.apache.maven.surefire.booter.CommandReader$CommandRunnable +java.util.concurrent.atomic.AtomicReference +java.util.concurrent.CountDownLatch +java.util.concurrent.CountDownLatch$Sync +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$Memento +org.apache.maven.surefire.booter.PpidChecker +org.apache.maven.surefire.booter.PpidChecker$ProcessInfoConsumer +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$BufferedStream +org.apache.maven.surefire.booter.PpidChecker$1 +org.apache.maven.surefire.booter.PpidChecker$2 +org.apache.maven.surefire.api.stream.AbstractStreamDecoder$StreamReadStatus +org.apache.maven.surefire.booter.stream.CommandDecoder$1 +java.lang.NoSuchFieldError +org.apache.maven.surefire.shared.lang3.SystemUtils +org.apache.maven.surefire.api.booter.Command +org.apache.maven.surefire.booter.CommandReader$1 +java.util.concurrent.ConcurrentLinkedQueue$Itr +org.apache.maven.surefire.shared.lang3.SystemProperties +org.apache.maven.surefire.shared.lang3.function.Suppliers +org.apache.maven.surefire.shared.lang3.function.Suppliers$$Lambda$17/0x00007faab4010000 +org.apache.maven.surefire.shared.lang3.StringUtils +java.util.regex.Pattern +java.util.regex.Pattern$Node +java.util.regex.Pattern$LastNode +java.util.regex.Pattern$GroupHead +java.util.regex.CharPredicates +java.lang.Character$Subset +java.lang.Character$UnicodeBlock +java.util.regex.Pattern$CharPredicate +java.lang.invoke.LambdaForm$DMH/0x00007faab4014000 +java.util.regex.CharPredicates$$Lambda$18/0x00007faab405bdc8 +java.util.regex.Pattern$BmpCharPredicate +java.util.regex.Pattern$CharProperty +java.util.regex.Pattern$Qtype +java.util.regex.Pattern$BmpCharProperty +java.util.regex.Pattern$CharPropertyGreedy +java.util.regex.Pattern$SliceNode +java.util.regex.Pattern$Slice +java.util.regex.Pattern$Begin +java.util.regex.Pattern$First +java.util.regex.Pattern$Start +java.util.regex.Pattern$StartS +java.util.regex.Pattern$TreeInfo +org.apache.maven.surefire.shared.lang3.JavaVersion +org.apache.maven.surefire.shared.lang3.SystemProperties$$Lambda$19/0x00007faab4010678 +org.apache.maven.surefire.shared.lang3.math.NumberUtils +java.lang.NumberFormatException +java.math.BigDecimal +jdk.internal.math.FloatingDecimal +jdk.internal.math.FloatingDecimal$BinaryToASCIIConverter +jdk.internal.math.FloatingDecimal$ExceptionalBinaryToASCIIBuffer +jdk.internal.math.FloatingDecimal$BinaryToASCIIBuffer +jdk.internal.math.FloatingDecimal$1 +jdk.internal.math.FloatingDecimal$ASCIIToBinaryConverter +jdk.internal.math.FloatingDecimal$PreparedASCIIToBinaryBuffer +jdk.internal.math.FloatingDecimal$ASCIIToBinaryBuffer +org.apache.maven.surefire.shared.lang3.SystemUtils$$Lambda$20/0x00007faab4010ca0 +java.util.regex.Pattern$GroupTail +java.util.regex.CharPredicates$$Lambda$16/0x800000024 +java.util.regex.Pattern$BmpCharPropertyGreedy +java.util.regex.Pattern$$Lambda$18/0x800000028 +java.util.regex.Pattern$Ques +java.util.regex.Pattern$BranchConn +java.util.regex.Pattern$Branch +java.util.regex.ASCII +java.util.regex.Pattern$Curly +java.util.regex.CharPredicates$$Lambda$17/0x800000025 +java.util.regex.Pattern$Dollar +java.util.regex.Pattern$BitClass +org.apache.maven.surefire.booter.ForkedBooter$4 +org.apache.maven.surefire.api.booter.BiProperty +org.apache.maven.surefire.booter.ForkedBooter$3 +org.apache.maven.surefire.booter.ForkedBooter$PingScheduler +org.apache.maven.surefire.api.provider.ProviderParameters +org.apache.maven.surefire.api.booter.BaseProviderFactory +org.apache.maven.surefire.api.util.DirectoryScanner +org.apache.maven.surefire.api.util.ScanResult +org.apache.maven.surefire.api.util.RunOrderCalculator +org.apache.maven.surefire.api.util.ReflectionUtils +org.apache.maven.surefire.api.util.SurefireReflectionException +java.lang.reflect.InvocationTargetException +java.lang.IllegalAccessException +org.apache.maven.surefire.api.provider.SurefireProvider +org.apache.maven.surefire.api.provider.AbstractProvider +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +org.junit.platform.launcher.Launcher +org.apache.maven.surefire.api.util.ScannerFilter +java.io.StringReader +java.io.UncheckedIOException +org.apache.maven.surefire.junitplatform.LazyLauncher +org.junit.platform.launcher.TagFilter +org.junit.platform.commons.JUnitException +org.junit.platform.commons.util.PreconditionViolationException +org.junit.platform.commons.PreconditionViolationException +org.junit.platform.engine.Filter +org.junit.platform.launcher.PostDiscoveryFilter +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$24/0x00007faab4016200 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$25/0x00007faab4016440 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$26/0x00007faab4016678 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$27/0x00007faab40168b8 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$28/0x00007faab4016af0 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$29/0x00007faab4016d40 +org.apache.maven.surefire.junitplatform.TestMethodFilter +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$30/0x00007faab40171f0 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$31/0x00007faab4017430 +org.junit.platform.launcher.EngineFilter +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$32/0x00007faab40178c0 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$33/0x00007faab4017b00 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$34/0x00007faab4017d38 +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$35/0x00007faab4015000 +org.junit.platform.launcher.TestExecutionListener +org.apache.maven.surefire.report.RunModeSetter +org.apache.maven.surefire.junitplatform.RunListenerAdapter +org.apache.maven.surefire.api.report.OutputReportEntry +org.apache.maven.surefire.api.report.TestSetReportEntry +org.apache.maven.surefire.api.report.StackTraceWriter +org.apache.maven.surefire.report.ClassMethodIndexer +org.apache.maven.surefire.api.report.RunMode +org.apache.maven.surefire.api.report.ConsoleOutputCapture +org.apache.maven.surefire.api.report.ConsoleOutputCapture$ForwardingPrintStream +org.apache.maven.surefire.api.report.ConsoleOutputCapture$NullOutputStream +java.util.logging.Logger +java.util.logging.Handler +java.util.logging.Level +java.util.logging.Level$KnownLevel +java.util.logging.Level$KnownLevel$$Lambda$13/0x800000021 +java.util.logging.Level$KnownLevel$$Lambda$14/0x800000022 +java.util.logging.Logger$LoggerBundle +java.util.logging.Logger$ConfigurationData +java.util.logging.LogManager$1 +java.util.logging.LogManager$LoggerContext +java.util.logging.LogManager$SystemLoggerContext +java.util.logging.LogManager$LogNode +java.util.Collections$SynchronizedMap +java.util.logging.LogManager$Cleaner +java.util.logging.LoggingPermission +sun.util.logging.internal.LoggingProviderImpl$LogManagerAccess +java.util.logging.LogManager$LoggingProviderAccess +java.lang.System$LoggerFinder +jdk.internal.logger.DefaultLoggerFinder +sun.util.logging.internal.LoggingProviderImpl +java.util.logging.LogManager$2 +java.util.logging.LogManager$RootLogger +java.util.logging.LogManager$LoggerWeakRef +java.lang.invoke.MethodHandleImpl$AsVarargsCollector +java.lang.invoke.BoundMethodHandle$Species_LL +java.lang.invoke.LambdaForm$MH/0x00007faab401c000 +java.util.logging.LogManager$VisitedLoggers +java.util.logging.LogManager$LoggerContext$1 +java.util.concurrent.ConcurrentHashMap$KeySetView +java.util.Collections$3 +java.util.concurrent.ConcurrentHashMap$KeyIterator +java.util.Hashtable$Enumerator +java.util.logging.Level$$Lambda$12/0x800000010 +java.util.ArrayList$ArrayListSpliterator +java.util.logging.Level$KnownLevel$$Lambda$15/0x800000023 +java.util.stream.ReferencePipeline$7 +java.util.stream.FindOps +java.util.stream.FindOps$FindSink +java.util.stream.FindOps$FindSink$OfRef +java.util.stream.FindOps$FindOp +java.util.stream.FindOps$FindSink$OfRef$$Lambda$40/0x80000004b +java.util.stream.FindOps$FindSink$OfRef$$Lambda$38/0x800000049 +java.util.stream.FindOps$FindSink$OfRef$$Lambda$39/0x80000004a +java.util.stream.FindOps$FindSink$OfRef$$Lambda$37/0x800000048 +java.util.stream.ReferencePipeline$7$1 +java.util.stream.Streams$AbstractStreamBuilderImpl +java.util.stream.Stream$Builder +java.util.stream.Streams$StreamBuilderImpl +java.util.stream.Streams +java.util.IdentityHashMap$Values +java.lang.System$Logger +sun.util.logging.PlatformLogger$Bridge +sun.util.logging.PlatformLogger$ConfigurableBridge +jdk.internal.logger.BootstrapLogger +jdk.internal.logger.BootstrapLogger$DetectBackend +jdk.internal.logger.BootstrapLogger$DetectBackend$1 +jdk.internal.logger.BootstrapLogger$LoggingBackend +jdk.internal.logger.BootstrapLogger$RedirectedLoggers +jdk.internal.logger.BootstrapLogger$BootstrapExecutors +java.util.logging.LogManager$4 +java.util.logging.Logger$SystemLoggerHelper +java.util.logging.Logger$SystemLoggerHelper$1 +jdk.internal.logger.DefaultLoggerFinder$1 +org.apache.maven.surefire.junitplatform.TestPlanScannerFilter +org.apache.maven.surefire.api.util.DefaultScanResult +jdk.internal.loader.URLClassPath$FileLoader$1 +software.amazon.lambda.powertools.validation.internal.ValidationAspectTest +org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder +org.junit.platform.engine.ConfigurationParameters +org.junit.platform.engine.EngineDiscoveryRequest +org.junit.platform.launcher.LauncherDiscoveryRequest +org.junit.platform.engine.reporting.OutputDirectoryProvider +org.junit.platform.engine.DiscoverySelector +org.junit.platform.engine.discovery.DiscoverySelectors +org.junit.platform.commons.util.Preconditions +org.junit.platform.commons.util.StringUtils +org.junit.platform.commons.util.StringUtils$TwoPartSplitResult +java.util.regex.CharPredicates$$Lambda$44/0x00007faab405d900 +org.junit.platform.engine.discovery.ClassSelector +java.lang.invoke.LambdaForm$DMH/0x00007faab401c400 +org.junit.platform.commons.util.Preconditions$$Lambda$45/0x00007faab401a5e0 +org.junit.platform.commons.util.Preconditions$$Lambda$46/0x00007faab401a818 +java.lang.invoke.LambdaForm$DMH/0x00007faab401c800 +java.lang.invoke.DirectMethodHandle$Special +org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder$$Lambda$47/0x00007faab401aa50 +org.junit.platform.launcher.core.LauncherConfigurationParameters +org.junit.platform.commons.logging.LoggerFactory +org.junit.platform.commons.logging.Logger +org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger +org.junit.platform.launcher.core.LauncherConfigurationParameters$Builder +org.junit.platform.launcher.core.LauncherConfigurationParameters$Builder$$Lambda$48/0x00007faab401b790 +org.junit.platform.commons.util.CollectionUtils +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$2 +org.junit.platform.commons.util.ClassLoaderUtils +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$3 +org.junit.platform.launcher.core.LauncherConfigurationParameters$$Lambda$49/0x00007faab401e498 +org.junit.platform.launcher.core.LauncherConfigurationParameters$$Lambda$50/0x00007faab401e6e0 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners +org.junit.platform.engine.EngineDiscoveryListener +org.junit.platform.launcher.LauncherDiscoveryListener +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$LauncherDiscoveryListenerType +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$LauncherDiscoveryListenerType$$Lambda$51/0x00007faab401f180 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$LauncherDiscoveryListenerType$$Lambda$52/0x00007faab401f3a0 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$$Lambda$53/0x00007faab401f7b8 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$$Lambda$54/0x00007faab401fa10 +org.junit.platform.launcher.listeners.discovery.AbortOnFailureLauncherDiscoveryListener +org.junit.platform.engine.EngineDiscoveryListener$1 +org.junit.platform.launcher.LauncherDiscoveryListener$1 +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$$Lambda$55/0x00007faab401d4c0 +org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider +java.util.regex.Pattern$$Lambda$56/0x00007faab405df20 +java.util.regex.Pattern$CharPredicate$$Lambda$57/0x00007faab405e180 +java.util.regex.Pattern$CharPredicate$$Lambda$21/0x80000002d +org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder$$Lambda$59/0x00007faab401d928 +org.junit.platform.launcher.core.DefaultDiscoveryRequest +org.junit.platform.launcher.LauncherSession +org.junit.platform.launcher.core.LauncherFactory +org.junit.platform.launcher.core.LauncherConfig +org.junit.platform.launcher.core.LauncherConfig$Builder +org.junit.platform.launcher.core.DefaultLauncherConfig +org.junit.platform.launcher.core.DefaultLauncherSession +org.junit.platform.launcher.LauncherInterceptor +org.junit.platform.launcher.core.DefaultLauncherSession$1 +org.junit.platform.launcher.core.LauncherConfigurationParameters$$Lambda$60/0x00007faab4020dd0 +org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor +org.junit.platform.launcher.LauncherSessionListener +org.junit.platform.launcher.core.LauncherFactory$$Lambda$61/0x00007faab4021448 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore +org.junit.platform.launcher.core.LauncherFactory$$Lambda$62/0x00007faab4021898 +org.junit.platform.engine.support.store.NamespacedHierarchicalStoreException +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$CloseAction +java.lang.invoke.LambdaForm$DMH/0x00007faab4024000 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$CloseAction$$Lambda$63/0x00007faab4021f40 +java.util.stream.SliceOps +java.util.stream.ReferencePipeline$StatefulOp +java.util.stream.SliceOps$1 +org.junit.platform.launcher.core.DefaultLauncherSession$$Lambda$64/0x00007faab4022160 +java.util.stream.ReduceOps$1 +java.util.stream.ReduceOps$1ReducingSink +java.util.stream.SliceOps$1$1 +org.junit.platform.launcher.LauncherInterceptor$Invocation +java.lang.invoke.LambdaForm$DMH/0x00007faab4024400 +org.junit.platform.launcher.core.DefaultLauncherSession$$Lambda$65/0x00007faab40225a8 +org.junit.platform.launcher.core.ListenerRegistry +org.junit.platform.launcher.listeners.session.LauncherSessionListeners +org.junit.platform.launcher.core.ListenerRegistry$$Lambda$66/0x00007faab4022c08 +org.junit.platform.launcher.core.ServiceLoaderRegistry +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$67/0x00007faab4023050 +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$68/0x00007faab40232a0 +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$69/0x00007faab40234e8 +org.junit.platform.commons.util.ServiceLoaderUtils +java.util.ServiceLoader$ProviderSpliterator +org.junit.platform.commons.util.ServiceLoaderUtils$$Lambda$70/0x00007faab4023948 +org.junit.platform.commons.util.ServiceLoaderUtils$$Lambda$71/0x00007faab4023ba0 +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$72/0x00007faab4026000 +java.lang.invoke.LambdaForm$DMH/0x00007faab4024800 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$73/0x00007faab4026228 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$74/0x00007faab4026460 +org.junit.platform.launcher.LauncherSessionListener$1 +org.junit.platform.launcher.core.DelegatingLauncher +org.junit.platform.launcher.core.InterceptingLauncher +org.junit.platform.launcher.core.DefaultLauncherSession$$Lambda$75/0x00007faab4026db0 +org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry +org.junit.platform.engine.TestEngine +org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry$$Lambda$76/0x00007faab40271d8 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$77/0x00007faab4027400 +org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine +org.junit.jupiter.engine.JupiterTestEngine +org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService +org.junit.jupiter.engine.config.JupiterConfiguration +org.junit.platform.engine.TestDescriptor +org.junit.platform.engine.support.hierarchical.EngineExecutionContext +org.junit.platform.launcher.core.LauncherFactory$$Lambda$78/0x00007faab4025400 +org.junit.platform.launcher.core.DefaultLauncher +org.junit.platform.launcher.TestPlan +org.junit.platform.launcher.core.InternalTestPlan +org.junit.platform.launcher.core.LauncherListenerRegistry +org.junit.platform.launcher.core.ListenerRegistry$$Lambda$79/0x00007faab4024c00 +org.junit.platform.launcher.core.CompositeTestExecutionListener +org.junit.platform.launcher.core.ListenerRegistry$$Lambda$80/0x00007faab40282a0 +org.junit.platform.launcher.core.EngineExecutionOrchestrator +org.junit.platform.engine.EngineExecutionListener +org.junit.platform.launcher.TestPlan$Visitor +org.junit.platform.launcher.core.DiscoveryIssueException +org.junit.platform.launcher.core.DefaultLauncher$$Lambda$81/0x00007faab4028d68 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator +org.junit.platform.launcher.core.EngineDiscoveryResultValidator +org.junit.platform.launcher.core.EngineIdValidator +org.junit.platform.launcher.core.EngineIdValidator$$Lambda$82/0x00007faab40295d0 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$83/0x00007faab40297f8 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$84/0x00007faab4029a30 +org.junit.platform.commons.util.ClassNamePatternFilterUtils +org.junit.platform.launcher.core.LauncherFactory$$Lambda$85/0x00007faab4029e70 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$86/0x00007faab402a0b0 +java.lang.invoke.LambdaForm$DMH/0x00007faab402c000 +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$87/0x00007faab402a300 +org.junit.platform.launcher.core.ServiceLoaderRegistry$$Lambda$88/0x00007faab402a558 +org.junit.platform.launcher.listeners.UniqueIdTrackingListener +org.junit.platform.launcher.core.LauncherFactory$$Lambda$89/0x00007faab402aa40 +org.junit.platform.launcher.core.LauncherFactory$$Lambda$90/0x00007faab402ac78 +org.junit.platform.launcher.core.InterceptingLauncher$$Lambda$91/0x00007faab402aeb0 +org.junit.platform.launcher.core.LauncherPhase +org.junit.platform.engine.UniqueId +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$92/0x00007faab402b748 +org.junit.platform.launcher.core.DiscoveryIssueCollector +org.junit.platform.engine.TestSource +org.junit.platform.launcher.listeners.discovery.CompositeLauncherDiscoveryListener +org.junit.platform.launcher.core.DelegatingLauncherDiscoveryRequest +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$1 +org.junit.platform.launcher.listeners.discovery.CompositeLauncherDiscoveryListener$$Lambda$93/0x00007faab402e830 +org.junit.platform.launcher.core.EngineFilterer +org.junit.platform.engine.FilterResult +org.junit.platform.launcher.core.EngineFilterer$$Lambda$94/0x00007faab402eeb0 +java.lang.invoke.LambdaForm$DMH/0x00007faab402c400 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$95/0x00007faab402f0f8 +java.util.stream.MatchOps$MatchKind +java.util.stream.MatchOps +java.util.stream.MatchOps$MatchOp +java.util.stream.MatchOps$BooleanTerminalSink +java.util.stream.MatchOps$$Lambda$96/0x00007faab4060150 +java.util.stream.MatchOps$1MatchSink +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$97/0x00007faab402f348 +org.junit.platform.engine.UniqueIdFormat +java.io.UnsupportedEncodingException +java.util.Formatter +java.util.regex.Pattern$$Lambda$19/0x800000029 +java.util.regex.Pattern$BmpCharPredicate$$Lambda$20/0x80000002b +java.util.Locale$Category +java.util.Formatter$Conversion +java.util.Formatter$FormatString +java.util.Formatter$FormatSpecifier +java.util.Formatter$Flags +java.util.Formatter$FixedString +java.util.Formattable +java.util.regex.Pattern$$Lambda$100/0x00007faab4060cd8 +java.lang.invoke.LambdaForm$DMH/0x00007faab402c800 +org.junit.platform.engine.UniqueIdFormat$$Lambda$101/0x00007faab402f790 +java.net.URLEncoder +java.util.BitSet +java.io.CharArrayWriter +org.junit.platform.engine.UniqueIdFormat$$Lambda$102/0x00007faab402f9d0 +org.junit.platform.engine.UniqueIdFormat$$Lambda$103/0x00007faab402fc10 +org.junit.platform.engine.UniqueIdFormat$$Lambda$104/0x00007faab402d000 +org.junit.platform.engine.UniqueIdFormat$$Lambda$105/0x00007faab402d240 +org.junit.platform.engine.UniqueIdFormat$$Lambda$106/0x00007faab402d480 +org.junit.platform.engine.UniqueId$Segment +org.junit.platform.launcher.listeners.discovery.CompositeLauncherDiscoveryListener$$Lambda$107/0x00007faab402d8e0 +org.junit.jupiter.engine.config.CachingJupiterConfiguration +org.junit.jupiter.engine.config.DefaultJupiterConfiguration +org.junit.jupiter.api.parallel.ExecutionMode +org.junit.jupiter.api.TestInstance$Lifecycle +org.junit.jupiter.api.io.CleanupMode +org.junit.jupiter.api.extension.TestInstantiationAwareExtension$ExtensionContextScope +org.junit.jupiter.engine.config.EnumConfigurationParameterConverter +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter +org.junit.jupiter.api.DisplayNameGenerator +org.junit.jupiter.api.MethodOrderer +org.junit.jupiter.api.ClassOrderer +org.junit.jupiter.api.io.TempDirFactory +org.junit.platform.engine.support.hierarchical.Node +org.junit.platform.engine.support.descriptor.AbstractTestDescriptor +org.junit.platform.engine.support.descriptor.EngineDescriptor +org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor +org.junit.jupiter.engine.extension.ExtensionRegistry +org.junit.jupiter.api.extension.ExtensionContext +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver +org.junit.platform.engine.support.discovery.SelectorResolver +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$InitializationContext +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$Builder +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$108/0x00007faab40330b8 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$Builder$$Lambda$109/0x00007faab40332f8 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$110/0x00007faab4033540 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$111/0x00007faab4033780 +org.junit.platform.engine.TestDescriptor$Visitor +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$112/0x00007faab4033bc0 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter +org.junit.platform.engine.DiscoveryIssue +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$$Lambda$113/0x00007faab4034200 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$$Lambda$114/0x00007faab4034448 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$DefaultInitializationContext +org.junit.platform.engine.DiscoveryFilter +org.junit.platform.engine.discovery.ClassNameFilter +org.junit.platform.launcher.core.DefaultDiscoveryRequest$$Lambda$115/0x00007faab4034d00 +org.junit.platform.launcher.core.DefaultDiscoveryRequest$$Lambda$116/0x00007faab4034f58 +org.junit.platform.engine.discovery.PackageNameFilter +org.junit.platform.engine.CompositeFilter +org.junit.platform.engine.CompositeFilter$1 +org.junit.platform.engine.CompositeFilter$1$$Lambda$117/0x00007faab4035818 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$$Lambda$118/0x00007faab4035a68 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver$$Lambda$119/0x00007faab4035cb0 +java.util.stream.Collectors$$Lambda$120/0x00007faab4061750 +java.util.stream.Collectors$$Lambda$121/0x00007faab4061980 +org.junit.platform.engine.support.discovery.ClassContainerSelectorResolver +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$122/0x00007faab4036400 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$123/0x00007faab4036650 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$124/0x00007faab40368a0 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$125/0x00007faab4036af8 +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod +org.junit.jupiter.engine.discovery.predicates.IsTestMethod +org.junit.jupiter.api.Test +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$Condition +org.junit.jupiter.engine.discovery.predicates.IsTestMethod$$Lambda$126/0x00007faab4037620 +org.junit.platform.commons.support.ModifierSupport +java.lang.invoke.LambdaForm$DMH/0x00007faab4038000 +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod$$Lambda$127/0x00007faab4037a58 +java.lang.invoke.MethodHandle$1 +java.lang.invoke.LambdaForm$DMH/0x00007faab4038400 +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod$$Lambda$128/0x00007faab4037ca8 +java.lang.invoke.LambdaForm$DMH/0x00007faab4038800 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$$Lambda$129/0x00007faab403c000 +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod$$Lambda$130/0x00007faab403c258 +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod$$Lambda$131/0x00007faab403c4a8 +java.lang.invoke.LambdaForm$DMH/0x00007faab4038c00 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$Condition$$Lambda$132/0x00007faab403c6f0 +org.junit.platform.commons.util.ReflectionUtils +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod$$Lambda$133/0x00007faab403cb50 +org.junit.jupiter.engine.discovery.predicates.IsTestableMethod$$Lambda$134/0x00007faab403cda0 +org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod +org.junit.jupiter.api.DynamicNode +java.util.regex.MatchResult +java.util.regex.Matcher +java.util.regex.IntHashSet +org.junit.jupiter.api.TestFactory +org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod$$Lambda$135/0x00007faab403d670 +org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod$$Lambda$136/0x00007faab403d8a0 +org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod$$Lambda$137/0x00007faab403daf8 +java.util.function.Predicate$$Lambda$138/0x00007faab4062210 +org.junit.jupiter.engine.discovery.predicates.IsTestTemplateMethod +org.junit.jupiter.api.TestTemplate +org.junit.jupiter.engine.discovery.predicates.IsTestTemplateMethod$$Lambda$139/0x00007faab403e1a8 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$140/0x00007faab403e3d8 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$141/0x00007faab403e628 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$142/0x00007faab403e870 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$143/0x00007faab403eac0 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$144/0x00007faab403ed00 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$145/0x00007faab403ef50 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$146/0x00007faab403f190 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$147/0x00007faab403f3e0 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$148/0x00007faab403f620 +org.junit.jupiter.engine.discovery.predicates.TestClassPredicates$$Lambda$149/0x00007faab403f870 +org.junit.jupiter.engine.discovery.ClassSelectorResolver +org.junit.jupiter.engine.descriptor.ResourceLockAware +org.junit.jupiter.engine.descriptor.TestClassAware +org.junit.jupiter.engine.descriptor.Validatable +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor +org.junit.jupiter.engine.descriptor.ClassTestDescriptor +org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor +org.junit.jupiter.api.extension.ClassTemplateInvocationContext +org.junit.jupiter.engine.descriptor.Filterable +org.junit.jupiter.engine.descriptor.ClassTemplateTestDescriptor +org.junit.jupiter.engine.discovery.MethodSelectorResolver +org.junit.jupiter.engine.discovery.MethodFinder +java.util.regex.Pattern$$Lambda$150/0x00007faab4062468 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$TestDescriptorFactory +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor +org.junit.jupiter.engine.extension.ExtensionRegistrar +java.lang.invoke.LambdaForm$DMH/0x00007faab4084000 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$151/0x00007faab4080b00 +org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor +org.junit.jupiter.engine.descriptor.DynamicNodeTestDescriptor +org.junit.jupiter.engine.descriptor.DynamicContainerTestDescriptor +org.junit.jupiter.engine.descriptor.DynamicTestTestDescriptor +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$152/0x00007faab4082038 +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$153/0x00007faab40827f0 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor +org.junit.jupiter.engine.discovery.ClassOrderingVisitor +org.junit.jupiter.api.ClassOrdererContext +org.junit.platform.commons.util.LruCache +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$154/0x00007faab40836f0 +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter$$Lambda$155/0x00007faab4083938 +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter$$Lambda$156/0x00007faab4083b78 +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter$$Lambda$157/0x00007faab4086000 +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter$$Lambda$158/0x00007faab4083dc8 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$DescriptorWrapperOrderer +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$MessageGenerator +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$DescriptorWrapperOrderer$$Lambda$159/0x00007faab4086660 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$DescriptorWrapperOrderer$$Lambda$160/0x00007faab4086880 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$161/0x00007faab4086aa0 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$162/0x00007faab4086cf0 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor +org.junit.jupiter.api.MethodOrdererContext +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$163/0x00007faab4087378 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$164/0x00007faab40875c8 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$165/0x00007faab4087808 +java.lang.invoke.LambdaForm$MH/0x00007faab4084400 +java.util.Comparator$$Lambda$166/0x00007faab40628c0 +java.util.Collections$ReverseComparator +java.util.Comparators$NaturalOrderComparator +java.util.Collections$ReverseComparator2 +java.util.function.UnaryOperator +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$167/0x00007faab4087a50 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver$$Lambda$168/0x00007faab4087cb0 +org.junit.platform.engine.CompositeTestDescriptorVisitor +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution +org.junit.platform.engine.support.discovery.SelectorResolver$Context +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$DefaultContext +org.junit.platform.engine.support.discovery.SelectorResolver$Match +org.junit.platform.engine.support.discovery.SelectorResolver$Match$$Lambda$169/0x00007faab4085ab8 +org.junit.platform.engine.support.discovery.SelectorResolver$Match$Type +org.junit.platform.launcher.core.DefaultDiscoveryRequest$$Lambda$170/0x00007faab40849f8 +org.junit.platform.launcher.core.DefaultDiscoveryRequest$$Lambda$171/0x00007faab4084c50 +java.util.ArrayDeque$$Lambda$172/0x00007faab4063970 +org.junit.platform.engine.discovery.UniqueIdSelector +org.junit.platform.engine.support.discovery.SelectorResolver$Resolution +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$173/0x00007faab4088460 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$174/0x00007faab40886a8 +org.junit.platform.engine.discovery.ClasspathResourceSelector +org.junit.platform.engine.discovery.ClasspathRootSelector +org.junit.platform.commons.support.ReflectionSupport +org.junit.platform.commons.util.ClasspathScannerLoader +org.junit.platform.commons.support.scanning.ClasspathScanner +java.util.Spliterators$IteratorSpliterator +jdk.internal.misc.ScopedMemoryAccess$Scope +org.junit.platform.commons.support.scanning.DefaultClasspathScanner +java.nio.file.FileVisitor +org.junit.platform.commons.util.ClasspathScannerLoader$$Lambda$175/0x00007faab40895e8 +org.junit.platform.commons.function.Try +java.lang.invoke.LambdaForm$DMH/0x00007faab408c000 +org.junit.platform.commons.util.ClasspathScannerLoader$$Lambda$176/0x00007faab4089a58 +java.lang.invoke.LambdaForm$DMH/0x00007faab408c400 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$177/0x00007faab4089c88 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$178/0x00007faab4089ec0 +org.junit.platform.commons.function.Try$Failure +org.junit.platform.commons.function.Try$Success +org.junit.platform.commons.function.Try$$Lambda$179/0x00007faab408a598 +java.util.regex.Pattern$1 +org.junit.platform.engine.discovery.ClassSelector$$Lambda$180/0x00007faab408a7c0 +org.junit.jupiter.api.Nested +org.junit.platform.commons.support.AnnotationSupport +org.junit.platform.commons.util.AnnotationUtils +java.lang.annotation.Inherited +sun.reflect.generics.parser.SignatureParser +sun.reflect.generics.tree.Tree +sun.reflect.generics.tree.TypeTree +sun.reflect.generics.tree.TypeArgument +sun.reflect.generics.tree.ReturnType +sun.reflect.generics.tree.TypeSignature +sun.reflect.generics.tree.BaseType +sun.reflect.generics.tree.FieldTypeSignature +sun.reflect.generics.tree.SimpleClassTypeSignature +sun.reflect.generics.tree.ClassTypeSignature +sun.reflect.generics.scope.Scope +sun.reflect.generics.scope.AbstractScope +sun.reflect.generics.scope.ClassScope +sun.reflect.generics.factory.GenericsFactory +sun.reflect.generics.factory.CoreReflectionFactory +sun.reflect.generics.visitor.TypeTreeVisitor +sun.reflect.generics.visitor.Reifier +java.lang.annotation.Target +java.lang.reflect.GenericArrayType +sun.reflect.annotation.AnnotationType +sun.reflect.annotation.AnnotationType$1 +java.lang.annotation.ElementType +java.lang.annotation.Retention +java.lang.annotation.Documented +java.lang.annotation.RetentionPolicy +sun.reflect.annotation.ExceptionProxy +sun.reflect.annotation.AnnotationTypeMismatchExceptionProxy +sun.reflect.annotation.AnnotationParser$1 +java.lang.reflect.InvocationHandler +sun.reflect.annotation.AnnotationInvocationHandler +java.lang.reflect.Proxy +java.lang.ClassValue +java.lang.reflect.Proxy$1 +java.lang.ClassValue$Entry +java.lang.ClassValue$Identity +java.lang.ClassValue$Version +jdk.internal.loader.AbstractClassLoaderValue$Sub +java.lang.reflect.Proxy$$Lambda$181/0x00007faab406bf50 +java.lang.reflect.Proxy$ProxyBuilder +java.lang.PublicMethods +java.util.LinkedHashMap$LinkedValues +java.util.LinkedHashMap$LinkedValueIterator +java.lang.reflect.Proxy$ProxyBuilder$$Lambda$182/0x00007faab406cb68 +java.lang.module.ModuleDescriptor$Modifier +java.lang.module.ModuleDescriptor$Builder +jdk.internal.module.Checks +java.lang.module.ModuleDescriptor$Builder$$Lambda$1/0x800000002 +java.lang.reflect.Proxy$$Lambda$184/0x00007faab406cd98 +java.lang.reflect.ProxyGenerator +java.lang.reflect.ProxyGenerator$ProxyMethod +java.util.StringJoiner +java.lang.reflect.ProxyGenerator$$Lambda$185/0x00007faab406d4e8 +java.lang.reflect.ProxyGenerator$$Lambda$186/0x00007faab406d728 +java.lang.reflect.ProxyGenerator$PrimitiveTypeInfo +jdk.internal.org.objectweb.asm.Edge +jdk.proxy1.$Proxy0 +java.lang.reflect.Proxy$ProxyBuilder$1 +sun.reflect.annotation.AnnotationParser$$Lambda$187/0x00007faab406e218 +java.lang.invoke.LambdaForm$DMH/0x00007faab408c800 +jdk.proxy1.$Proxy1 +jdk.proxy1.$Proxy2 +org.apiguardian.api.API +org.apiguardian.api.API$Status +jdk.proxy2.$Proxy3 +java.lang.reflect.UndeclaredThrowableException +java.lang.Class$AnnotationData +org.junit.platform.commons.util.KotlinReflectionUtils +org.junit.platform.commons.function.Try$Transformer +org.junit.platform.commons.util.KotlinReflectionUtils$$Lambda$188/0x00007faab408bcc8 +org.junit.jupiter.api.ClassTemplate +jdk.proxy1.$Proxy4 +org.junit.platform.commons.annotation.Testable +jdk.proxy2.$Proxy5 +org.junit.platform.commons.util.ReflectionUtils$HierarchyTraversalMode +org.junit.platform.commons.util.ReflectionUtils$$Lambda$189/0x00007faab408ea90 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$190/0x00007faab408ece0 +com.amazonaws.services.lambda.runtime.events.SQSEvent +com.amazonaws.services.lambda.runtime.events.KinesisEvent +com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent +com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse +com.amazonaws.services.lambda.runtime.RequestHandler +software.amazon.lambda.powertools.validation.handlers.SQSWithWrongEnvelopeHandler +org.junit.platform.commons.util.ReflectionUtils$$Lambda$191/0x00007faab408fc88 +java.util.Arrays$LegacyMergeSort +java.util.TimSort +org.junit.jupiter.params.ParameterizedTest +org.junit.jupiter.params.ArgumentCountValidationMode +org.junit.jupiter.api.extension.ExtendWith +jdk.proxy2.$Proxy6 +com.amazonaws.services.lambda.runtime.tests.annotations.Event +org.junit.jupiter.params.provider.ArgumentsSource +jdk.proxy2.$Proxy7 +java.util.LinkedHashMap$LinkedEntrySet +java.util.LinkedHashMap$LinkedEntryIterator +jdk.proxy2.$Proxy8 +java.lang.annotation.Repeatable +sun.reflect.annotation.AnnotationParser$$Lambda$192/0x00007faab406f5a0 +org.junit.jupiter.api.extension.Extension +org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider +org.junit.jupiter.params.ParameterizedInvocationContextProvider +org.junit.jupiter.params.ParameterizedTestExtension +jdk.proxy2.$Proxy9 +jdk.internal.reflect.ClassFileConstants +jdk.internal.reflect.AccessorGenerator +jdk.internal.reflect.MethodAccessorGenerator +jdk.internal.reflect.ByteVectorFactory +jdk.internal.reflect.ByteVector +jdk.internal.reflect.ByteVectorImpl +jdk.internal.reflect.ClassFileAssembler +jdk.internal.reflect.UTF8 +jdk.internal.reflect.Label +jdk.internal.reflect.Label$PatchInfo +jdk.internal.reflect.MethodAccessorGenerator$1 +jdk.internal.reflect.ClassDefiner +jdk.internal.reflect.ClassDefiner$1 +jdk.internal.reflect.GeneratedConstructorAccessor1 +java.lang.Class$1 +jdk.internal.reflect.BootstrapConstructorAccessorImpl +org.junit.jupiter.api.extension.Extensions +jdk.proxy1.$Proxy10 +sun.reflect.annotation.AnnotationInvocationHandler$1 +org.junit.jupiter.params.provider.ArgumentsProvider +org.junit.jupiter.params.support.AnnotationConsumer +com.amazonaws.services.lambda.runtime.tests.EventArgumentsProvider +jdk.proxy2.$Proxy11 +org.junit.jupiter.params.provider.ArgumentsSources +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$193/0x00007faab40919f0 +org.junit.jupiter.api.extension.ExtensionConfigurationException +org.junit.jupiter.api.extension.TestInstanceFactoryContext +org.junit.jupiter.api.extension.TestInstantiationAwareExtension +org.junit.jupiter.api.extension.TestInstantiationException +org.junit.jupiter.engine.execution.ConditionEvaluator +org.junit.jupiter.engine.execution.ConditionEvaluationException +org.junit.jupiter.api.extension.ConditionEvaluationResult +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker +org.junit.jupiter.api.extension.ReflectiveInvocationContext +org.junit.jupiter.api.extension.InvocationInterceptor$Invocation +org.junit.jupiter.engine.execution.InvocationInterceptorChain +org.junit.jupiter.engine.descriptor.DisplayNameUtils +org.junit.jupiter.api.DisplayNameGenerator$Standard +org.junit.jupiter.api.DisplayNameGenerator$Simple +org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores +org.junit.jupiter.api.DisplayNameGenerator$IndicativeSentences +org.junit.jupiter.api.DisplayNameGenerator$IndicativeSentences$$Lambda$194/0x00007faab4096000 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$195/0x00007faab4096250 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$196/0x00007faab4096470 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$197/0x00007faab40966a8 +org.junit.platform.engine.support.descriptor.ClassSource +org.junit.jupiter.api.DisplayName +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$198/0x00007faab4096cf0 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$199/0x00007faab4096f30 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$200/0x00007faab4097180 +org.junit.jupiter.api.DisplayNameGeneration +java.util.AbstractList$Itr +java.util.AbstractList$ListItr +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$201/0x00007faab40975c0 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$202/0x00007faab4097800 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$203/0x00007faab4097a40 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$204/0x00007faab4097c88 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$205/0x00007faab4095000 +org.junit.jupiter.engine.config.DefaultJupiterConfiguration$$Lambda$206/0x00007faab4095248 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$ClassInfo +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$ClassInfo$$Lambda$207/0x00007faab4095678 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$ClassInfo$$Lambda$208/0x00007faab40958a0 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$ClassInfo$$Lambda$209/0x00007faab4095ac8 +org.junit.jupiter.api.Tag +org.junit.jupiter.api.Tags +org.junit.platform.commons.util.AnnotationUtils$$Lambda$210/0x00007faab4094a00 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$211/0x00007faab4094c28 +java.lang.invoke.LambdaForm$DMH/0x00007faab4094400 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$212/0x00007faab4098000 +org.junit.platform.engine.TestTag +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$213/0x00007faab4098468 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$214/0x00007faab40986a8 +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$$Lambda$215/0x00007faab40988c8 +java.lang.invoke.LambdaForm$DMH/0x00007faab409c000 +java.util.function.Function$$Lambda$216/0x00007faab40722d0 +org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils +org.junit.jupiter.api.TestInstance +jdk.internal.reflect.GeneratedConstructorAccessor2 +org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils$$Lambda$217/0x00007faab4098f10 +org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils$$Lambda$218/0x00007faab4099150 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$219/0x00007faab4099378 +java.lang.invoke.LambdaForm$DMH/0x00007faab409c800 +org.junit.jupiter.engine.config.EnumConfigurationParameterConverter$$Lambda$220/0x00007faab40997b8 +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$1 +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$DefaultExclusiveResourceCollector +org.junit.jupiter.api.parallel.ResourceLock +jdk.internal.reflect.GeneratedConstructorAccessor3 +org.junit.jupiter.api.parallel.ResourceLocks +jdk.internal.reflect.GeneratedConstructorAccessor4 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$LifecycleMethods +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$$Lambda$221/0x00007faab409a6a0 +java.lang.invoke.LambdaForm$DMH/0x00007faab409d400 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$$Lambda$222/0x00007faab409a8d8 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils +org.junit.jupiter.api.BeforeAll +org.junit.platform.commons.support.HierarchyTraversalMode +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$223/0x00007faab409b368 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$224/0x00007faab409b5b0 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$225/0x00007faab409b800 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$226/0x00007faab409ba48 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$227/0x00007faab409bca0 +java.util.function.IntFunction +org.junit.platform.commons.util.ReflectionUtils$$Lambda$228/0x00007faab409e000 +java.util.stream.Nodes +java.util.stream.Node +java.util.stream.Nodes$EmptyNode +java.util.stream.Nodes$EmptyNode$OfRef +java.util.stream.Node$OfPrimitive +java.util.stream.Node$OfInt +java.util.stream.Nodes$EmptyNode$OfInt +java.util.stream.Node$OfLong +java.util.stream.Nodes$EmptyNode$OfLong +java.util.stream.Node$OfDouble +java.util.stream.Nodes$EmptyNode$OfDouble +java.util.stream.Node$Builder +java.util.stream.AbstractSpinedBuffer +java.util.stream.SpinedBuffer +java.util.stream.Nodes$SpinedNodeBuilder +org.junit.platform.commons.util.ReflectionUtils$$Lambda$229/0x00007faab409e220 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$230/0x00007faab409e478 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$231/0x00007faab409e698 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$232/0x00007faab409e8f0 +java.util.stream.DistinctOps +java.util.stream.DistinctOps$1 +org.junit.platform.commons.util.CollectionUtils$$Lambda$233/0x00007faab409eb10 +java.util.stream.DistinctOps$1$2 +jdk.proxy2.$Proxy12 +org.junit.jupiter.params.provider.MethodSource +jdk.proxy2.$Proxy13 +org.junit.jupiter.params.provider.MethodSources +org.junit.jupiter.params.provider.AnnotationBasedArgumentsProvider +org.junit.jupiter.params.provider.MethodArgumentsProvider +software.amazon.lambda.powertools.validation.internal.ResponseEventsArgumentsProvider +org.junit.jupiter.api.BeforeEach +jdk.proxy2.$Proxy14 +software.amazon.lambda.powertools.validation.internal.HandledResponseEventsArgumentsProvider +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$234/0x00007faab409dc88 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$235/0x00007faab40a0000 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$236/0x00007faab40a0250 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$Condition$$Lambda$237/0x00007faab40a0498 +java.util.stream.ReferencePipeline$15 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$238/0x00007faab40a06d0 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$239/0x00007faab40a0918 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$240/0x00007faab40a0b68 +org.junit.platform.engine.support.discovery.DiscoveryIssueReporter$Condition$$Lambda$241/0x00007faab40a0db0 +java.util.stream.ReferencePipeline$15$1 +org.junit.jupiter.api.AfterAll +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$242/0x00007faab40a1208 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$243/0x00007faab40a1450 +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$244/0x00007faab40a16a0 +org.junit.jupiter.api.AfterEach +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$245/0x00007faab40a1ae8 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$246/0x00007faab40a1d30 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$247/0x00007faab40a1f58 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$248/0x00007faab40a2180 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$249/0x00007faab40a23c8 +org.junit.platform.engine.SelectorResolutionResult +org.junit.platform.engine.SelectorResolutionResult$Status +org.junit.platform.launcher.listeners.discovery.CompositeLauncherDiscoveryListener$$Lambda$250/0x00007faab40a2c60 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$251/0x00007faab40a2e98 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$252/0x00007faab40a30e8 +java.util.stream.ForEachOps +java.util.stream.ForEachOps$ForEachOp +java.util.stream.ForEachOps$ForEachOp$OfRef +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$253/0x00007faab40a3320 +org.junit.platform.commons.util.ReflectionUtils$CycleErrorHandling +org.junit.platform.commons.util.ReflectionUtils$CycleErrorHandling$1 +org.junit.platform.commons.util.ReflectionUtils$CycleErrorHandling$2 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$254/0x00007faab40a3e50 +java.lang.invoke.LambdaForm$DMH/0x00007faab40a8000 +java.util.function.Predicate$$Lambda$255/0x00007faab40754b8 +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$256/0x00007faab40a4088 +java.util.function.Predicate$$Lambda$257/0x00007faab4075710 +java.util.stream.Streams$ConcatSpliterator +java.util.stream.Streams$ConcatSpliterator$OfRef +java.util.stream.Streams$2 +org.junit.platform.engine.discovery.NestedClassSelector +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$258/0x00007faab40a4530 +java.util.stream.AbstractPipeline$$Lambda$259/0x00007faab40760d8 +java.util.stream.StreamSpliterators$AbstractWrappingSpliterator +java.util.stream.StreamSpliterators$WrappingSpliterator +org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda$260/0x00007faab40a4778 +java.util.stream.StreamSpliterators +java.util.stream.StreamSpliterators$WrappingSpliterator$$Lambda$261/0x00007faab4076a38 +org.junit.platform.engine.discovery.MethodSelector +org.junit.platform.engine.discovery.MethodSelector$$Lambda$262/0x00007faab40a4c08 +org.junit.platform.commons.util.ClassUtils +org.junit.platform.commons.util.ClassUtils$$Lambda$263/0x00007faab40a5050 +java.util.stream.Collectors$$Lambda$31/0x800000042 +java.util.stream.Collectors$$Lambda$23/0x80000003a +java.util.stream.Collectors$$Lambda$26/0x80000003d +java.util.stream.Collectors$$Lambda$28/0x80000003f +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$268/0x00007faab40a5298 +org.junit.platform.engine.discovery.IterationSelector +org.junit.platform.engine.discovery.DirectorySelector +org.junit.platform.engine.discovery.FileSelector +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$269/0x00007faab40a5ba8 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$270/0x00007faab40a5dd0 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$271/0x00007faab40a6000 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$272/0x00007faab40a6248 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$273/0x00007faab40a6498 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$274/0x00007faab40a66d8 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$275/0x00007faab40a6920 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$276/0x00007faab40a6b48 +org.junit.platform.commons.util.ClassUtils$$Lambda$277/0x00007faab40a6d90 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$$Lambda$278/0x00007faab40a6fd0 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$279/0x00007faab40a71f8 +org.junit.jupiter.api.DisplayNameGenerator$$Lambda$280/0x00007faab40a7430 +org.junit.platform.engine.support.descriptor.MethodSource +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$MethodInfo +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$MethodInfo$$Lambda$281/0x00007faab40a7aa8 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$MethodInfo$$Lambda$282/0x00007faab40a7cd0 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$MethodInfo$$Lambda$283/0x00007faab40ac000 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$284/0x00007faab40ac238 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$285/0x00007faab40ac478 +org.junit.platform.commons.util.AnnotationUtils$$Lambda$286/0x00007faab40ac6c8 +java.util.function.BiPredicate +org.junit.jupiter.engine.descriptor.DynamicDescendantFilter +org.junit.jupiter.engine.descriptor.DynamicDescendantFilter$WithoutIndexFiltering +org.junit.jupiter.engine.descriptor.DynamicDescendantFilter$Mode +org.junit.jupiter.engine.discovery.MethodSelectorResolver$$Lambda$287/0x00007faab40ad288 +java.util.HashMap$KeySpliterator +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall$VoidMethodInterceptorCall +org.junit.jupiter.api.extension.InvocationInterceptor +java.lang.invoke.LambdaForm$DMH/0x00007faab40a8400 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$288/0x00007faab40ad8b0 +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall$$Lambda$289/0x00007faab40adcd0 +org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution$$Lambda$290/0x00007faab40adef8 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$291/0x00007faab40ae130 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$292/0x00007faab40ae368 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$293/0x00007faab40ae5a8 +org.junit.jupiter.api.ClassDescriptor +org.junit.jupiter.engine.discovery.AbstractAnnotatedDescriptorWrapper +org.junit.jupiter.engine.discovery.DefaultClassDescriptor +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$294/0x00007faab40aee58 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$$Lambda$295/0x00007faab40af098 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$$Lambda$296/0x00007faab40af2f0 +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$$Lambda$297/0x00007faab40af538 +org.junit.jupiter.api.Order +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$298/0x00007faab40af970 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$299/0x00007faab40afba8 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$300/0x00007faab40aa000 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$301/0x00007faab40aa238 +org.junit.platform.engine.TestDescriptor$$Lambda$302/0x00007faab40aa478 +org.junit.jupiter.api.TestClassOrder +jdk.internal.reflect.GeneratedConstructorAccessor5 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$303/0x00007faab40aa6b0 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$304/0x00007faab40aa8f0 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$305/0x00007faab40aab30 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$306/0x00007faab40aad78 +org.junit.jupiter.engine.discovery.ClassOrderingVisitor$$Lambda$307/0x00007faab40aafa0 +org.junit.jupiter.api.TestMethodOrder +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$308/0x00007faab40ab3e0 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$309/0x00007faab40ab620 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$310/0x00007faab40ab860 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$311/0x00007faab40abaa0 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$312/0x00007faab40abcc8 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$313/0x00007faab40a9000 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$314/0x00007faab40a9248 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$315/0x00007faab40a9468 +org.junit.jupiter.api.MethodDescriptor +org.junit.jupiter.engine.discovery.DefaultMethodDescriptor +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$316/0x00007faab40a9af8 +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$317/0x00007faab40a9d38 +org.junit.platform.engine.support.hierarchical.Node$ExecutionMode +org.junit.jupiter.engine.discovery.MethodOrderingVisitor$$Lambda$318/0x00007faab40b0000 +org.junit.jupiter.engine.descriptor.Validatable$$Lambda$319/0x00007faab40b0238 +org.junit.jupiter.api.extension.ClassTemplateInvocationLifecycleMethod +org.junit.jupiter.engine.descriptor.LifecycleMethodUtils$$Lambda$320/0x00007faab40b0670 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$321/0x00007faab40b08a8 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$322/0x00007faab40b0ad0 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$323/0x00007faab40b0cf8 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$324/0x00007faab40b0f38 +org.junit.jupiter.engine.descriptor.DisplayNameUtils$$Lambda$325/0x00007faab40b1188 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$326/0x00007faab40b13c0 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$327/0x00007faab40b15e8 +org.junit.platform.launcher.core.EngineDiscoveryResultValidator$$Lambda$328/0x00007faab40b1810 +org.junit.platform.engine.TestDescriptor$Type +org.junit.platform.launcher.core.EngineDiscoveryResultValidator$$Lambda$329/0x00007faab40b1e78 +org.junit.platform.launcher.EngineDiscoveryResult +org.junit.platform.launcher.EngineDiscoveryResult$Status +org.junit.platform.launcher.listeners.discovery.CompositeLauncherDiscoveryListener$$Lambda$330/0x00007faab40b2700 +java.util.Collections$UnmodifiableList$1 +java.util.ArrayList$ListItr +org.junit.platform.commons.util.ExceptionUtils +java.io.StringWriter +org.junit.platform.launcher.listeners.discovery.AbortOnFailureLauncherDiscoveryListener$$Lambda$331/0x00007faab40b2b40 +org.junit.platform.launcher.core.DiscoveryIssueNotifier +org.junit.platform.engine.DiscoveryIssue$Severity +org.junit.platform.launcher.core.LauncherDiscoveryResult$EngineResultInfo +org.junit.platform.launcher.core.EngineFilterer$$Lambda$332/0x00007faab40b3420 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$333/0x00007faab40b3660 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$334/0x00007faab40b38b0 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$335/0x00007faab40b3af0 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$336/0x00007faab40b3d30 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$337/0x00007faab40b3f88 +org.junit.platform.launcher.core.EngineFilterer$$Lambda$338/0x00007faab40b41a8 +java.lang.invoke.LambdaForm$DMH/0x00007faab40b8000 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$339/0x00007faab40b43f8 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$340/0x00007faab40b4620 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$341/0x00007faab40b4858 +java.lang.invoke.LambdaForm$DMH/0x00007faab40b8400 +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator$$Lambda$342/0x00007faab40b4a88 +org.junit.platform.engine.TestDescriptor$$Lambda$343/0x00007faab40b4ca8 +org.junit.platform.launcher.core.LauncherDiscoveryResult +org.junit.platform.launcher.listeners.discovery.CompositeLauncherDiscoveryListener$$Lambda$344/0x00007faab40b5150 +org.junit.platform.launcher.core.LauncherPhase$$Lambda$345/0x00007faab40b5388 +org.junit.platform.engine.ConfigurationParameters$$Lambda$346/0x00007faab40b55c8 +org.junit.platform.launcher.core.LauncherDiscoveryResult$$Lambda$347/0x00007faab40b5810 +org.junit.platform.launcher.core.LauncherDiscoveryResult$$Lambda$348/0x00007faab40b5a60 +org.junit.platform.launcher.TestPlan$$Lambda$349/0x00007faab40b5ca0 +org.junit.platform.launcher.TestPlan$$Lambda$350/0x00007faab40b5ec8 +org.junit.platform.launcher.TestIdentifier +org.junit.platform.launcher.TestIdentifier$SerializedForm +java.io.ObjectStreamClass +java.io.ObjectStreamClass$Caches +java.io.ClassCache +java.io.ObjectStreamClass$Caches$1 +java.io.ClassCache$1 +java.io.ObjectStreamClass$Caches$2 +java.lang.ClassValue$ClassValueMap +java.io.Externalizable +java.io.ObjectStreamClass$2 +jdk.internal.reflect.UnsafeFieldAccessorFactory +jdk.internal.reflect.UnsafeQualifiedStaticFieldAccessorImpl +jdk.internal.reflect.UnsafeQualifiedStaticLongFieldAccessorImpl +java.util.ComparableTimSort +jdk.internal.reflect.SerializationConstructorAccessorImpl +jdk.internal.reflect.GeneratedSerializationConstructorAccessor1 +java.io.ObjectOutput +java.io.ObjectStreamConstants +java.io.ObjectOutputStream +java.io.ObjectInput +java.io.ObjectInputStream +java.lang.Class$$Lambda$351/0x00007faab407a758 +java.lang.CloneNotSupportedException +java.io.ClassCache$CacheRef +java.io.ObjectStreamClass$FieldReflectorKey +java.io.ObjectStreamClass$FieldReflector +org.junit.platform.launcher.TestIdentifier$$Lambda$352/0x00007faab40b6528 +org.junit.platform.launcher.TestPlan$$Lambda$353/0x00007faab40b6768 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$354/0x00007faab40b69a8 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$355/0x00007faab40b6be0 +software.amazon.lambda.powertools.validation.ValidationUtilsTest +com.fasterxml.jackson.core.TreeNode +com.fasterxml.jackson.databind.JsonSerializable +com.fasterxml.jackson.databind.JsonSerializable$Base +com.fasterxml.jackson.databind.JsonNode +software.amazon.lambda.powertools.validation.model.Product +software.amazon.lambda.powertools.validation.model.MyCustomEvent +jdk.internal.reflect.GeneratedConstructorAccessor6 +org.apache.maven.surefire.api.util.TestsToRun +org.apache.maven.surefire.api.util.DefaultRunOrderCalculator +java.util.random.RandomGenerator +java.util.Random +org.apache.maven.surefire.api.util.CloseableIterator +org.apache.maven.surefire.api.util.TestsToRun$ClassesIterator +java.util.NoSuchElementException +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider$$Lambda$356/0x00007faab40bcbd8 +org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder$$Lambda$357/0x00007faab40bce10 +org.junit.platform.launcher.core.InterceptingLauncher$$Lambda$358/0x00007faab40bd048 +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$359/0x00007faab40bd270 +org.junit.platform.launcher.core.CompositeTestExecutionListener$EagerTestExecutionListener +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$360/0x00007faab40bd6a8 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$361/0x00007faab40bd900 +org.junit.platform.engine.reporting.ReportEntry +java.lang.invoke.LambdaForm$DMH/0x00007faab40b9000 +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$362/0x00007faab40bdd58 +org.junit.platform.launcher.core.StreamInterceptingTestExecutionListener +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$363/0x00007faab40be268 +org.junit.platform.engine.EngineExecutionListener$1 +org.junit.platform.launcher.core.IterationOrder +org.junit.platform.launcher.core.IterationOrder$1 +org.junit.platform.launcher.core.IterationOrder$2 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$364/0x00007faab40bf000 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$365/0x00007faab40bf238 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$366/0x00007faab40bf460 +org.junit.platform.launcher.core.CompositeEngineExecutionListener +org.junit.platform.launcher.core.ListenerRegistry$$Lambda$367/0x00007faab40bf918 +org.junit.platform.launcher.core.ExecutionListenerAdapter +org.junit.platform.launcher.core.DelegatingEngineExecutionListener +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$368/0x00007faab40bfdd8 +org.junit.platform.launcher.core.OutcomeDelayingEngineExecutionListener +org.junit.platform.engine.ExecutionRequest +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$369/0x00007faab40ba9d8 +org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService +org.junit.jupiter.engine.execution.JupiterEngineExecutionContext +org.junit.jupiter.engine.descriptor.LauncherStoreFacade +org.junit.jupiter.api.extension.ExtensionContext$Store +org.junit.jupiter.engine.descriptor.LauncherStoreFacade$$Lambda$370/0x00007faab40bb528 +org.junit.jupiter.engine.execution.JupiterEngineExecutionContext$State +org.junit.platform.engine.support.hierarchical.ThrowableCollector$Factory +org.junit.platform.engine.support.hierarchical.ThrowableCollector +org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory +org.junit.jupiter.engine.support.OpenTest4JAndJUnit4AwareThrowableCollector +org.junit.jupiter.engine.JupiterTestEngine$$Lambda$371/0x00007faab40b9a40 +org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor +org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService$TestTask +org.junit.platform.engine.support.hierarchical.NodeTreeWalker +org.junit.platform.engine.support.hierarchical.LockManager +org.junit.platform.engine.support.hierarchical.ResourceLock +java.util.concurrent.locks.ReadWriteLock +org.junit.platform.engine.support.hierarchical.SingleLock +org.junit.platform.engine.support.hierarchical.ExclusiveResource +org.junit.platform.engine.support.hierarchical.ExclusiveResource$LockMode +org.junit.platform.engine.support.hierarchical.ExclusiveResource$$Lambda$372/0x00007faab40c0d38 +org.junit.platform.engine.support.hierarchical.ExclusiveResource$$Lambda$373/0x00007faab40c0f78 +java.util.Comparator$$Lambda$374/0x00007faab407b718 +java.lang.invoke.LambdaForm$DMH/0x00007faab40c4000 +java.util.Comparator$$Lambda$375/0x00007faab407b9b8 +org.junit.platform.engine.support.hierarchical.ExclusiveResource$$Lambda$376/0x00007faab40c11b8 +org.junit.platform.engine.support.hierarchical.LockManager$$Lambda$377/0x00007faab40c13f8 +java.util.concurrent.locks.ReentrantReadWriteLock +java.util.concurrent.locks.ReentrantReadWriteLock$Sync +java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync +java.util.concurrent.locks.ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter +java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock +java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock +org.junit.platform.commons.util.CollectionUtils$$Lambda$378/0x00007faab40c1638 +org.junit.platform.engine.support.hierarchical.NodeUtils +org.junit.platform.engine.support.hierarchical.NodeUtils$1 +org.junit.platform.engine.support.hierarchical.NodeExecutionAdvisor +org.junit.platform.engine.support.hierarchical.NodeTreeWalker$$Lambda$379/0x00007faab40c1f68 +org.junit.platform.engine.support.hierarchical.NopLock +org.junit.jupiter.api.parallel.ResourceLocksProvider +org.junit.jupiter.engine.descriptor.ClassTestDescriptor$$Lambda$380/0x00007faab40c2630 +org.junit.platform.engine.support.hierarchical.NodeTreeWalker$$Lambda$381/0x00007faab40c2878 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$382/0x00007faab40c2ab0 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$383/0x00007faab40c2cd8 +org.junit.jupiter.engine.descriptor.ResourceLockAware$1 +org.junit.jupiter.api.parallel.ResourceLockTarget +java.util.ArrayDeque$DeqSpliterator +org.junit.jupiter.engine.descriptor.ResourceLockAware$$Lambda$384/0x00007faab40c35a8 +org.junit.jupiter.engine.descriptor.ResourceLockAware$$Lambda$385/0x00007faab40c37e8 +org.junit.jupiter.engine.descriptor.ResourceLockAware$$Lambda$386/0x00007faab40c3a30 +org.junit.platform.engine.support.hierarchical.NodeTestTaskContext +org.junit.platform.engine.support.hierarchical.NodeTestTask +org.junit.platform.engine.support.hierarchical.Node$DynamicTestExecutor +java.lang.invoke.LambdaForm$DMH/0x00007faab40c4400 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$387/0x00007faab40c6458 +org.opentest4j.IncompleteExecutionException +org.opentest4j.TestAbortedException +org.junit.jupiter.engine.support.OpenTest4JAndJUnit4AwareThrowableCollector$$Lambda$388/0x00007faab40c6b38 +org.junit.platform.commons.util.UnrecoverableExceptions +org.junit.jupiter.engine.support.OpenTest4JAndJUnit4AwareThrowableCollector$$Lambda$389/0x00007faab40c6f98 +org.junit.platform.engine.support.hierarchical.ThrowableCollector$Executable +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$390/0x00007faab40c73b8 +org.junit.jupiter.engine.extension.MutableExtensionRegistry +org.junit.jupiter.engine.extension.MutableExtensionRegistry$Entry +org.junit.jupiter.api.extension.ExecutionCondition +org.junit.jupiter.engine.extension.DisabledCondition +org.junit.jupiter.api.extension.TestInstancePreDestroyCallback +org.junit.jupiter.api.extension.AfterAllCallback +org.junit.jupiter.engine.extension.AutoCloseExtension +org.junit.jupiter.api.extension.BeforeAllCallback +org.junit.jupiter.api.extension.BeforeEachCallback +org.junit.jupiter.engine.extension.TimeoutExtension +org.junit.jupiter.api.Timeout +org.junit.jupiter.api.extension.ExtensionContext$Namespace +org.junit.jupiter.engine.extension.RepeatedTestExtension +org.junit.jupiter.api.extension.TestTemplateInvocationContext +org.junit.jupiter.api.extension.ParameterResolver +org.junit.jupiter.engine.extension.TestInfoParameterResolver +org.junit.jupiter.api.TestInfo +org.junit.jupiter.engine.extension.TestReporterParameterResolver +org.junit.jupiter.api.TestReporter +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$391/0x00007faab40c8cc0 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$392/0x00007faab40c8ef8 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$393/0x00007faab40c9130 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$Entry$$Lambda$394/0x00007faab40c9358 +org.junit.jupiter.engine.extension.TempDirectory +org.junit.jupiter.api.extension.AnnotatedElementContext +org.junit.jupiter.engine.extension.TempDirectory$Scope +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$395/0x00007faab40c9c68 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$396/0x00007faab40c9eb0 +org.junit.jupiter.engine.extension.ExtensionContextInternal +org.junit.jupiter.engine.descriptor.AbstractExtensionContext +org.junit.jupiter.engine.descriptor.JupiterEngineExtensionContext +org.junit.jupiter.api.extension.ExecutableInvoker +org.junit.jupiter.engine.execution.DefaultExecutableInvoker +org.junit.jupiter.engine.descriptor.AbstractExtensionContext$$Lambda$397/0x00007faab40cb118 +org.junit.jupiter.engine.descriptor.AbstractExtensionContext$$Lambda$398/0x00007faab40cb358 +org.junit.jupiter.engine.descriptor.AbstractExtensionContext$$Lambda$399/0x00007faab40cb578 +org.junit.jupiter.engine.execution.NamespaceAwareStore +org.junit.jupiter.api.extension.ExtensionContextException +org.junit.platform.engine.support.store.Namespace +java.lang.invoke.LambdaForm$DMH/0x00007faab40cc000 +org.junit.jupiter.engine.descriptor.AbstractExtensionContext$$Lambda$400/0x00007faab40ce000 +org.junit.jupiter.engine.execution.JupiterEngineExecutionContext$Builder +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$401/0x00007faab40ce460 +org.junit.platform.engine.support.hierarchical.Node$SkipResult +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$402/0x00007faab40ce8a8 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$403/0x00007faab40ceae0 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$404/0x00007faab40ced08 +org.junit.platform.launcher.TestPlan$$Lambda$405/0x00007faab40cef40 +org.junit.platform.launcher.TestPlan$$Lambda$406/0x00007faab40cf160 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$407/0x00007faab40cf388 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$408/0x00007faab40cf5c0 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$409/0x00007faab40cf7e8 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$410/0x00007faab40cfa20 +org.junit.platform.engine.UniqueIdFormat$$Lambda$411/0x00007faab40cfc48 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$412/0x00007faab40cd000 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$413/0x00007faab40cd258 +org.junit.platform.engine.support.hierarchical.Node$Invocation +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$414/0x00007faab40cd680 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$415/0x00007faab40cd8a8 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$416/0x00007faab40cdad0 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$417/0x00007faab40cdd18 +org.junit.platform.engine.support.hierarchical.NodeTestTask$DefaultDynamicTestExecutor +java.util.concurrent.CancellationException +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$418/0x00007faab40cca50 +org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService$$Lambda$419/0x00007faab40ccc88 +org.junit.jupiter.engine.descriptor.ExtensionUtils +java.util.function.ToIntFunction +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$420/0x00007faab40d0000 +java.util.Comparator$$Lambda$421/0x00007faab407d3d8 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$422/0x00007faab40d0220 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$423/0x00007faab40d0470 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$424/0x00007faab40d06b0 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$LateInitEntry +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$425/0x00007faab40d0b38 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$426/0x00007faab40d0d70 +software.amazon.lambda.powertools.validation.Validation +org.aspectj.lang.Signature +com.amazonaws.services.lambda.runtime.Context +org.aspectj.lang.JoinPoint +org.aspectj.lang.ProceedingJoinPoint +software.amazon.lambda.powertools.validation.internal.ValidationAspect +org.junit.platform.commons.util.ReflectionUtils$$Lambda$427/0x00007faab40d1bd8 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$428/0x00007faab40d1e70 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$429/0x00007faab40d20c0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$430/0x00007faab40d22e0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$431/0x00007faab40d2538 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$432/0x00007faab40d2758 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$433/0x00007faab40d29b0 +java.util.stream.SortedOps +java.util.stream.SortedOps$OfRef +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$434/0x00007faab40d2bd0 +java.util.stream.SortedOps$AbstractRefSortingSink +java.util.stream.SortedOps$RefSortingSink +java.util.stream.SortedOps$RefSortingSink$$Lambda$435/0x00007faab407e2f8 +org.junit.jupiter.api.extension.TestInstanceFactory +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$436/0x00007faab40d3008 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$437/0x00007faab40d3248 +org.junit.jupiter.engine.extension.MutableExtensionRegistry$$Lambda$438/0x00007faab40d34a0 +org.junit.jupiter.engine.extension.ExtensionRegistry$$Lambda$439/0x00007faab40d36e8 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$440/0x00007faab40d3908 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$441/0x00007faab40d3b58 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$442/0x00007faab40d3d78 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$443/0x00007faab40d3fa0 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$444/0x00007faab40d41e8 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$445/0x00007faab40d4428 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$446/0x00007faab40d4660 +org.junit.jupiter.engine.execution.BeforeEachMethodAdapter +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$447/0x00007faab40d4a98 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$448/0x00007faab40d4ce0 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$449/0x00007faab40d4f18 +org.junit.jupiter.engine.execution.AfterEachMethodAdapter +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$450/0x00007faab40d5340 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$451/0x00007faab40d5588 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$452/0x00007faab40d57c0 +org.junit.jupiter.engine.descriptor.ExtensionUtils$$Lambda$453/0x00007faab40d5a10 +org.junit.jupiter.api.extension.RegisterExtension +org.mockito.Mock +org.mockito.stubbing.Answer +org.mockito.Answers +org.mockito.Mock$Strictness +org.mockito.invocation.InvocationOnMock +org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer +org.mockito.internal.stubbing.defaultanswers.ReturnsSmartNulls +org.mockito.internal.stubbing.defaultanswers.RetrieveGenericsForDefaultAnswers$AnswerCallback +org.mockito.internal.stubbing.defaultanswers.ReturnsMoreEmptyValues +org.mockito.internal.stubbing.defaultanswers.ReturnsEmptyValues +org.mockito.internal.stubbing.defaultanswers.ReturnsMocks +org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs +org.mockito.invocation.DescribedInvocation +org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs$ReturnsDeepStubsSerializationFallback +org.mockito.stubbing.ValidableAnswer +org.mockito.internal.stubbing.answers.CallsRealMethods +org.mockito.exceptions.base.MockitoException +org.mockito.internal.stubbing.defaultanswers.TriesToReturnSelf +jdk.proxy2.$Proxy15 +org.junit.jupiter.engine.descriptor.ClassExtensionContext +org.junit.jupiter.engine.execution.TestInstancesProvider +org.junit.jupiter.api.extension.TestInstances +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$454/0x00007faab40d9650 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$455/0x00007faab40d9888 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$456/0x00007faab40d9ad0 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$FilterType +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$457/0x00007faab40da150 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$458/0x00007faab40da3a0 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$459/0x00007faab40da5e0 +org.junit.platform.commons.util.ClassNamePatternFilterUtils$$Lambda$460/0x00007faab40da828 +org.junit.jupiter.engine.execution.ConditionEvaluator$$Lambda$461/0x00007faab40daa78 +org.junit.jupiter.engine.execution.ConditionEvaluator$$Lambda$462/0x00007faab40dacc0 +org.junit.jupiter.api.Disabled +org.junit.jupiter.engine.extension.DisabledCondition$$Lambda$463/0x00007faab40db110 +org.junit.jupiter.engine.execution.ConditionEvaluator$$Lambda$464/0x00007faab40db358 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$465/0x00007faab40db580 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$466/0x00007faab40db7d8 +org.junit.platform.launcher.TestIdentifier$$Lambda$467/0x00007faab40dba30 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$468/0x00007faab40dbc70 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$469/0x00007faab40dbeb8 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$470/0x00007faab40dc100 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$471/0x00007faab40dc320 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$472/0x00007faab40dc570 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$473/0x00007faab40dc7b0 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$474/0x00007faab40dca08 +org.apache.maven.surefire.api.report.SimpleReportEntry +org.apache.maven.surefire.api.util.internal.ClassMethod +org.apache.maven.surefire.report.ClassMethodIndexer$$Lambda$475/0x00007faab40dd160 +org.apache.maven.surefire.api.util.internal.ImmutableMap +org.apache.maven.surefire.booter.spi.EventChannelEncoder$StackTrace +java.lang.StrictMath +java.nio.StringCharBuffer +org.junit.jupiter.engine.descriptor.CallbackSupport$CallbackInvoker +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$476/0x00007faab40ddd50 +org.junit.jupiter.engine.descriptor.CallbackSupport +org.junit.jupiter.engine.descriptor.CallbackSupport$$Lambda$477/0x00007faab40de178 +org.junit.jupiter.engine.extension.TimeoutDuration +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$478/0x00007faab40de5c8 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$479/0x00007faab40de808 +org.junit.jupiter.api.Timeout$ThreadMode +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$480/0x00007faab40dec88 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$481/0x00007faab40deec8 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$482/0x00007faab40df100 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$483/0x00007faab40df358 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$484/0x00007faab40df590 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$485/0x00007faab40df7e0 +org.junit.jupiter.engine.execution.NamespaceAwareStore$$Lambda$486/0x00007faab40dfa28 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$CompositeKey +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$StoredValue +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$487/0x00007faab40e0210 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$MemoizingSupplier +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$488/0x00007faab40e0688 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$489/0x00007faab40e08b0 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$MemoizingSupplier$Failure +org.junit.jupiter.api.io.TempDir +org.junit.platform.commons.util.AnnotationUtils$$Lambda$490/0x00007faab40e1100 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$491/0x00007faab40e1358 +org.junit.jupiter.engine.descriptor.ClassExtensionContext$$Lambda$492/0x00007faab40e1590 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$493/0x00007faab40e17d0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$494/0x00007faab40e1a10 +java.util.stream.Nodes$ArrayNode +java.util.stream.Nodes$FixedNodeBuilder +org.junit.jupiter.api.extension.TemplateInvocationValidationException +org.junit.jupiter.params.ParameterizedDeclarationContext +org.junit.jupiter.engine.descriptor.TestTemplateExtensionContext +org.junit.jupiter.engine.descriptor.TemplateExecutor +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor$TestTemplateExecutor +java.lang.invoke.LambdaForm$DMH/0x00007faab40e4000 +org.junit.jupiter.engine.descriptor.TemplateExecutor$$Lambda$495/0x00007faab40e2a60 +org.junit.jupiter.api.RepeatedTest +org.junit.jupiter.params.ParameterizedTestContext +org.junit.jupiter.params.ResolverFacade +org.junit.jupiter.params.support.ParameterDeclaration +org.junit.jupiter.params.support.ParameterDeclarations +org.junit.jupiter.params.ResolverFacade$ResolvableParameterDeclaration +org.junit.jupiter.params.support.FieldContext +org.junit.jupiter.params.ResolverFacade$FieldParameterDeclaration +org.junit.jupiter.params.ResolverFacade$Resolver +org.junit.jupiter.api.extension.ParameterResolutionException +org.junit.jupiter.params.ResolverFacade$ExecutableParameterDeclaration +org.junit.jupiter.api.extension.ParameterContext +org.junit.jupiter.params.aggregator.ArgumentsAccessor +org.junit.jupiter.params.aggregator.AggregateWith +java.util.TreeMap$Values +java.util.TreeMap$ValueIterator +org.junit.jupiter.params.ResolverFacade$DefaultParameterDeclarations +org.junit.jupiter.engine.execution.NamespaceAwareStore$$Lambda$496/0x00007faab40e6f48 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$497/0x00007faab40e7170 +org.junit.jupiter.engine.descriptor.TemplateExecutor$$Lambda$498/0x00007faab40e7398 +org.junit.jupiter.engine.execution.NamespaceAwareStore$$Lambda$499/0x00007faab40e75c0 +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$500/0x00007faab40e77e8 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$PartialFormatter +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$501/0x00007faab40e5000 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$502/0x00007faab40e5228 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$CachingByArgumentsLengthPartialFormatter +java.lang.invoke.LambdaForm$DMH/0x00007faab40e4400 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$503/0x00007faab40e5698 +java.lang.invoke.LambdaForm$DMH/0x00007faab40e4800 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$ArgumentSetNameFormatter +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$PartialFormatters +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$ArgumentsContext +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$PartialFormatter$$Lambda$504/0x00007faab40e4c00 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$505/0x00007faab40e8000 +java.lang.invoke.LambdaForm$DMH/0x00007faab40ec000 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$506/0x00007faab40e8228 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$507/0x00007faab40e8468 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$PlaceholderPosition +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$508/0x00007faab40e88a0 +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$509/0x00007faab40e8cc0 +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$510/0x00007faab40e8f00 +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$511/0x00007faab40e9148 +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$512/0x00007faab40e9390 +org.junit.jupiter.params.provider.Arguments +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$513/0x00007faab40e97d8 +org.junit.jupiter.params.ParameterizedInvocationContextProvider$$Lambda$514/0x00007faab40e9a20 +org.junit.jupiter.engine.descriptor.TemplateExecutor$$Lambda$515/0x00007faab40e9c48 +org.junit.jupiter.params.ParameterizedTestSpiInstantiator +org.junit.jupiter.params.ParameterizedTestSpiInstantiator$$Lambda$516/0x00007faab40ea088 +org.junit.jupiter.engine.execution.ParameterResolutionUtils +org.junit.jupiter.engine.execution.ExtensionContextSupplier +org.junit.jupiter.engine.execution.ParameterResolutionUtils$$Lambda$517/0x00007faab40ea6b8 +org.junit.jupiter.params.support.AnnotationConsumerInitializer +org.junit.jupiter.params.support.AnnotationConsumerInitializer$AnnotationConsumingMethodSignature +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$518/0x00007faab40eaf00 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$519/0x00007faab40eb140 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$520/0x00007faab40eb390 +java.lang.invoke.LambdaForm$DMH/0x00007faab40ec400 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$521/0x00007faab40eb5d8 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$522/0x00007faab40eb830 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$523/0x00007faab40eba70 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$524/0x00007faab40ebcb0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$525/0x00007faab40ee000 +java.lang.reflect.Executable$$Lambda$526/0x00007faab407f5c8 +java.lang.reflect.Executable$$Lambda$527/0x00007faab407f808 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$528/0x00007faab40ee220 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$529/0x00007faab40ee470 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$530/0x00007faab40ee690 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$531/0x00007faab40ee8e8 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$532/0x00007faab40eeb08 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$533/0x00007faab40eed60 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$534/0x00007faab40eefa0 +org.junit.jupiter.params.support.AnnotationConsumerInitializer$$Lambda$535/0x00007faab40ef1e0 +com.amazonaws.services.lambda.runtime.tests.EventLoader +com.amazonaws.services.lambda.runtime.tests.EventLoadingException +com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers +com.amazonaws.services.lambda.runtime.serialization.util.ReflectUtil$ReflectException +com.amazonaws.services.lambda.runtime.serialization.PojoSerializer +java.util.AbstractMap$SimpleEntry +com.amazonaws.services.lambda.runtime.serialization.events.serializers.OrgJsonSerializer +com.amazonaws.services.lambda.runtime.serialization.events.serializers.S3EventSerializer +com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers$$Lambda$536/0x00007faab40ed4a8 +com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers$$Lambda$537/0x00007faab40ed6e8 +java.util.stream.Collectors$$Lambda$538/0x00007faab40f0000 +java.util.stream.Collectors$$Lambda$539/0x00007faab40f0220 +java.util.stream.Collectors$$Lambda$540/0x00007faab40f0458 +com.amazonaws.services.lambda.runtime.serialization.events.mixins.CloudFormationCustomResourceEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.CloudFrontEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.CloudWatchLogsEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.CodeCommitEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.CodeCommitEventMixin$RecordMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.ConnectEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.ConnectEventMixin$DetailsMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.ConnectEventMixin$ContactDataMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.ConnectEventMixin$CustomerEndpointMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.ConnectEventMixin$QueueMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.ConnectEventMixin$SystemEndpointMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.DynamodbEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.DynamodbEventMixin$DynamodbStreamRecordMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.DynamodbEventMixin$StreamRecordMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.DynamodbEventMixin$AttributeValueMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.DynamodbTimeWindowEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.KinesisEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.KinesisEventMixin$RecordMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.KinesisTimeWindowEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.ScheduledEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.SecretsManagerRotationEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.SNSEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.SNSEventMixin$SNSRecordMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.SQSEventMixin +com.amazonaws.services.lambda.runtime.serialization.events.mixins.SQSEventMixin$SQSMessageMixin +com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers$$Lambda$541/0x00007faab4102a30 +com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers$$Lambda$542/0x00007faab4102c70 +com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers$NestedClass +com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers$AlternateNestedClass +com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers$$Lambda$543/0x00007faab41036d0 +com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers$$Lambda$544/0x00007faab4103910 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.PropertyNamingStrategy +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.PropertyNamingStrategy$PropertyNamingStrategyBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.PropertyNamingStrategy$UpperCamelCaseStrategy +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.PropertyNamingStrategy$PascalCaseStrategy +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.PropertyNamingStrategy$SnakeCaseStrategy +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.PropertyNamingStrategy$LowerCaseStrategy +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.PropertyNamingStrategy$KebabCaseStrategy +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.PropertyNamingStrategy$LowerDotCaseStrategy +com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers$$Lambda$545/0x00007faab4104cc8 +com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers$$Lambda$546/0x00007faab4104f08 +com.amazonaws.services.lambda.runtime.serialization.factories.PojoSerializerFactory +com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.Versioned +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.Module +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.module.SimpleModule +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.Jdk8Module +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.TokenStreamFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.DataOutputAsStream +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.SerializableString +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.TSFBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonFactoryBuilder +java.io.CharArrayReader +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.base.ParserMinimalBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.base.ParserBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.ReaderBasedJsonParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.UTF8DataInputJsonParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.async.NonBlockingInputFeeder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.async.ByteArrayFeeder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.async.NonBlockingJsonParserBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.async.NonBlockingUtf8JsonParserBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.async.NonBlockingJsonParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.async.ByteBufferFeeder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.async.NonBlockingByteBufferJsonParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.base.GeneratorBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.JsonGeneratorImpl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.UTF8JsonGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.UTF8Writer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.WriterBasedJsonGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.JacksonFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonFactory$Feature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonParser$Feature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonGenerator$Feature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.PrettyPrinter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.Instantiatable +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.DefaultPrettyPrinter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.DefaultPrettyPrinter$Indenter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.SerializedString +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.JsonStringEncoder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.CharTypes +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.FormatFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.JsonReadFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.JsonWriteFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer$TableInfo +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer$Bucket +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer$TableInfo +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.TreeCodec +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.ObjectCodec +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectMapper +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.json.JsonMapper +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.MappingJsonFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.SubtypeResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.impl.StdSubtypeResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DatabindContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.SerializerProvider +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.DefaultSerializerProvider +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.DefaultSerializerProvider$Impl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.DeserializerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BasicDeserializerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BeanDeserializerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DeserializationContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.DefaultDeserializationContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.DefaultDeserializationContext$Impl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.SerializerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BasicSerializerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BeanSerializerFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.AnnotationIntrospector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy$Provider +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy$Provider +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator +java.text.Format +java.text.DateFormat +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.StdDateFormat +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JacksonException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonProcessingException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DatabindException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonMappingException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.TreeNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonSerializable +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonSerializable$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.BaseJsonNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ValueNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.NullNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.Module$SetupContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.TokenBuffer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.ClassIntrospector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.BasicClassIntrospector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.VisibilityChecker +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.TreeTraversingParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.SegmentedStringWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.ByteArrayBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.type.ResolvedType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JavaType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.ArrayType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.CollectionLikeType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.CollectionType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.MapLikeType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.MapType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.MismatchedInputException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.Annotated +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.TypeResolutionContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedClass +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedMember +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.VirtualAnnotatedMember +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.Named +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.SimpleBeanPropertyDefinition +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.BeanProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.ConcreteBeanPropertyBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.PropertyWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BeanPropertyWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.AttributePropertyWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedWithParams +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedMethod +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JsonSerialize +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonView +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonFormat +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonTypeInfo +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonRawValue +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonUnwrapped +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonBackReference +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonManagedReference +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JsonDeserialize +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonMerge +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7Support +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.ClassUtil +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.ClassUtil$Ctor +java.beans.Transient +java.beans.ConstructorProperties +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.LookupCache +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.LRUMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$Builder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap +java.io.ObjectStreamException +java.io.InvalidObjectException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.Linked +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.LinkedDeque +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$2 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$3 +java.util.concurrent.atomic.AtomicLongArray +java.lang.invoke.VarHandleLongs$Array +java.util.concurrent.atomic.AtomicReferenceArray +java.lang.invoke.VarHandleReferences$Array +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.BaseSettings +java.util.TimeZone +sun.util.calendar.ZoneInfo +sun.util.calendar.ZoneInfoFile +sun.util.calendar.ZoneInfoFile$1 +java.io.DataInputStream +sun.util.calendar.ZoneInfoFile$ZoneOffsetTransitionRule +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.SimpleType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.ResolvedRecursiveType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.PlaceholderForType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.ReferenceType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeBindings +java.text.SimpleDateFormat +java.util.Calendar +java.util.GregorianCalendar +java.text.ParseException +java.text.AttributedCharacterIterator$Attribute +java.text.Format$Field +java.text.DateFormat$Field +sun.util.calendar.ZoneInfoFile$Checksum +java.util.spi.LocaleServiceProvider +sun.util.spi.CalendarProvider +sun.util.locale.provider.LocaleProviderAdapter +sun.util.locale.provider.LocaleProviderAdapter$Type +sun.util.locale.provider.LocaleProviderAdapter$1 +sun.util.locale.provider.ResourceBundleBasedAdapter +sun.util.locale.provider.JRELocaleProviderAdapter +sun.util.cldr.CLDRLocaleProviderAdapter +sun.util.locale.provider.LocaleDataMetaInfo +sun.util.cldr.CLDRBaseLocaleDataMetaInfo +sun.util.locale.LanguageTag +sun.util.locale.ParseStatus +sun.util.locale.StringTokenIterator +sun.util.locale.InternalLocaleBuilder +sun.util.locale.InternalLocaleBuilder$CaseInsensitiveChar +sun.util.locale.BaseLocale$Key +sun.util.locale.LocaleObjectCache +sun.util.locale.BaseLocale$Cache +sun.util.locale.LocaleObjectCache$CacheEntry +java.util.Locale$Cache +sun.util.cldr.CLDRLocaleProviderAdapter$$Lambda$57/0x80000005e +jdk.internal.module.ModulePatcher$PatchedModuleReader +sun.net.www.protocol.jrt.Handler +sun.util.resources.cldr.provider.CLDRLocaleDataMetaInfo +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$59/0x800000060 +sun.util.locale.provider.AvailableLanguageTags +sun.util.locale.provider.CalendarProviderImpl +java.util.Calendar$Builder +sun.util.calendar.CalendarSystem +sun.util.calendar.CalendarSystem$GregorianHolder +sun.util.calendar.AbstractCalendar +sun.util.calendar.BaseCalendar +sun.util.calendar.Gregorian +sun.util.locale.provider.CalendarDataUtility +java.util.Locale$Builder +java.util.spi.CalendarDataProvider +sun.util.locale.provider.LocaleServiceProviderPool +java.text.spi.BreakIteratorProvider +java.text.spi.CollatorProvider +java.text.spi.DateFormatProvider +java.text.spi.DateFormatSymbolsProvider +java.text.spi.DecimalFormatSymbolsProvider +java.text.spi.NumberFormatProvider +java.util.spi.CurrencyNameProvider +java.util.spi.LocaleNameProvider +java.util.spi.TimeZoneNameProvider +sun.util.locale.provider.LocaleServiceProviderPool$LocalizedObjectGetter +sun.util.locale.provider.CalendarDataUtility$CalendarWeekParameterGetter +java.util.ResourceBundle$Control +java.util.ResourceBundle +java.util.ResourceBundle$Control$CandidateListCache +java.util.ResourceBundle$SingleFormatControl +java.util.ResourceBundle$NoFallbackControl +sun.util.cldr.CLDRLocaleProviderAdapter$$Lambda$58/0x80000005f +sun.util.locale.provider.CalendarDataProviderImpl +sun.util.cldr.CLDRCalendarDataProviderImpl +sun.util.locale.provider.LocaleResources +sun.util.resources.LocaleData +sun.util.resources.LocaleData$1 +sun.util.resources.Bundles$Strategy +sun.util.resources.LocaleData$LocaleDataStrategy +sun.util.resources.Bundles +sun.util.resources.Bundles$1 +jdk.internal.access.JavaUtilResourceBundleAccess +java.util.ResourceBundle$1 +java.util.ResourceBundle$2 +sun.util.resources.Bundles$CacheKey +java.util.ListResourceBundle +sun.util.resources.cldr.CalendarData +java.util.ResourceBundle$ResourceBundleProviderHelper +java.util.ResourceBundle$ResourceBundleProviderHelper$$Lambda$11/0x80000000f +sun.util.resources.Bundles$CacheKeyReference +sun.util.resources.Bundles$BundleReference +sun.util.locale.provider.LocaleResources$ResourceReference +sun.util.calendar.CalendarDate +sun.util.calendar.BaseCalendar$Date +sun.util.calendar.Gregorian$Date +sun.util.calendar.CalendarUtils +java.text.DateFormatSymbols +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$61/0x800000062 +sun.util.locale.provider.DateFormatSymbolsProviderImpl +sun.text.resources.cldr.FormatData +sun.text.resources.cldr.FormatData_en +java.text.NumberFormat +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$63/0x800000064 +sun.util.locale.provider.NumberFormatProviderImpl +java.text.DecimalFormatSymbols +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$62/0x800000063 +sun.util.locale.provider.DecimalFormatSymbolsProviderImpl +java.lang.StringLatin1$CharsSpliterator +java.util.stream.IntStream +java.util.stream.IntPipeline +java.util.stream.IntPipeline$Head +java.util.function.IntPredicate +java.text.DecimalFormatSymbols$$Lambda$7/0x80000000b +java.util.stream.IntPipeline$StatelessOp +java.util.stream.IntPipeline$10 +java.util.function.IntConsumer +java.util.stream.Sink$OfInt +java.util.stream.FindOps$FindSink$OfInt +java.util.OptionalInt +java.util.stream.FindOps$FindSink$OfInt$$Lambda$36/0x800000047 +java.util.stream.FindOps$FindSink$OfInt$$Lambda$34/0x800000045 +java.util.stream.FindOps$FindSink$OfInt$$Lambda$35/0x800000046 +java.util.stream.FindOps$FindSink$OfInt$$Lambda$33/0x800000044 +java.util.stream.Sink$ChainedInt +java.util.stream.IntPipeline$10$1 +java.lang.StringUTF16$CharsSpliterator +java.lang.CharacterData00 +java.text.DecimalFormat +java.text.FieldPosition +java.text.DigitList +java.math.RoundingMode +java.util.Date +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.Base64Variants +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.Base64Variant +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.Base64Variant$PaddingReadBehaviour +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy$RecordNaming +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.MapperBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.json.JsonMapper$Builder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.RootNameLookup +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.ClassIntrospector$MixInResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.SimpleMixInResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.MapperConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.MapperConfigBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DeserializationConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.BeanDescription +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.BasicBeanDescription +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.SerializationConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.Annotations +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationCollector$EmptyCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationCollector$NoAnnotations +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedClass$Creators +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedConstructor +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedParameter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ConfigOverrides +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JacksonAnnotationValue +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonInclude$Value +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonInclude$Include +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonSetter$Value +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.Nulls +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.VisibilityChecker$Std +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.CoercionConfigs +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.LogicalType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.CoercionAction +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.CoercionConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.MutableCoercionConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.CoercionInputShape +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.DefaultBaseTypeLimitingValidator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonFormat$Value +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonFormat$Shape +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonFormat$Features +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ConfigOverride +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ConfigOverride$Empty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ConfigFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.MapperFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.DefaultPrettyPrinter$NopIndenter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.DefaultPrettyPrinter$FixedSpaceIndenter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.DefaultIndenter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.Separators +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.SerializationFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.DatatypeFeatures +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.DatatypeFeatures$DefaultHolder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.DatatypeFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.EnumFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.JsonNodeFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ContextAttributes +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ContextAttributes$Impl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DeserializationFeature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.JsonNodeCreator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.JsonNodeFactory +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.MissingNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.BooleanNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.NumericNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.DecimalNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ShortNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.IntNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.LongNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.DoubleNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.FloatNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.BigIntegerNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.TextNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.BinaryNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.POJONode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsonschema.SchemaAware +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NullSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.FailingSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ToEmptyObjectSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.UnknownSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.ContextualSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.InvalidDefinitionException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.InvalidTypeIdException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ContainerNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ObjectNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.ResolvableSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.SerializerCache +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.NullValueProvider +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.PropertyBindingException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.InvalidFormatException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.ValueInstantiationException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.UnresolvedForwardReference +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ContextualDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiator$Gettable +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.EnumSetDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.AbstractDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.CollectionDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.ArrayBlockingQueueDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StringCollectionDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ResolvableDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.EnumMapDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.MapDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StringDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.MapEntryDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.TokenBufferDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.EnumDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.ReferenceTypeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.AtomicReferenceDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.SettableBeanProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.CreatorProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.ErrorThrowingDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.ObjectIdGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.ObjectIdGenerators$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.ObjectIdGenerators$PropertyGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.MethodProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.FieldProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.SetterlessProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BeanDeserializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BeanDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.ThrowableDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.UnsupportedTypeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.PropertyName +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BeanDeserializerModifier +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.AbstractTypeResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiators +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.KeyDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.KeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$DelegatingKD +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$EnumKD +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringCtorKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringFactoryKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.DeserializerCache +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdDelegatingDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.JsonValueSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ToStringSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ToStringSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdScalarSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.EnumSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.ContainerSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.MapEntrySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.IteratorSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.IterableSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.MapSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StaticListSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.IndexedStringListSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.StringCollectionSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.CollectionSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ArraySerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.StringArraySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.EnumSetSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ReferenceTypeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.SerializableSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.DateTimeSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.CalendarSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.DateSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ByteBufferSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.InetAddressSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.InetSocketAddressSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.TimeZoneSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.BeanSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BeanSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedField +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.PropertyBasedObjectIdGenerator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StringSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$IntegerSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonParser$NumberType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$LongSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$IntLikeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$ShortSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$DoubleSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$FloatSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.BooleanSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.BooleanSerializer$AsNumber +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializer$BigDecimalAsStringSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdJdkSerializers +java.util.Currency +java.util.UUID +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.UUIDSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicBooleanSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicIntegerSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicLongSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.FileSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.ClassSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.TokenBufferSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BeanSerializerModifier +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.module.SimpleAbstractTypeResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.JSR310DeserializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.JSR310DateTimeDeserializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.MonthDayDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.OffsetTimeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.YearDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.YearMonthDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.JSR310SerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.JSR310FormattedSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.DurationSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.MonthDaySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.OffsetDateTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.OffsetTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.YearSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.YearMonthSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.ZonedDateTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.ZoneIdSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.key.ZonedDateTimeKeySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.Jsr310KeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.DurationKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.InstantKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.LocalDateTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.LocalDateKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.LocalTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.MonthDayKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.OffsetDateTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.OffsetTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.PeriodKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.YearKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.YearMonthKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.ZonedDateTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.ZoneIdKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.key.ZoneOffsetKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.PackageVersion +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.VersionUtil +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.Version +java.time.temporal.TemporalAccessor +java.time.temporal.Temporal +java.time.temporal.TemporalAdjuster +java.time.Instant +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.exc.StreamReadException +java.time.DateTimeException +java.util.regex.Pattern$BnM +java.util.regex.Pattern$Pos +java.time.format.DateTimeFormatter +java.time.format.DateTimeFormatterBuilder +java.time.temporal.TemporalQuery +java.time.ZoneId +java.time.format.DateTimeFormatterBuilder$$Lambda$10/0x80000000e +java.time.temporal.TemporalField +java.time.temporal.ChronoField +java.time.temporal.ValueRange +java.time.temporal.IsoFields +java.time.temporal.IsoFields$Field +java.time.temporal.IsoFields$Field$1 +java.time.temporal.IsoFields$Field$2 +java.time.temporal.IsoFields$Field$3 +java.time.temporal.IsoFields$Field$4 +java.time.temporal.IsoFields$Unit +java.time.temporal.JulianFields +java.time.temporal.JulianFields$Field +java.time.format.SignStyle +java.time.format.DateTimeFormatterBuilder$DateTimePrinterParser +java.time.format.DateTimeFormatterBuilder$NumberPrinterParser +java.time.format.DateTimeFormatterBuilder$CharLiteralPrinterParser +java.time.format.ResolverStyle +java.time.chrono.Chronology +java.time.chrono.AbstractChronology +java.time.chrono.IsoChronology +java.time.format.DateTimeFormatterBuilder$CompositePrinterParser +java.time.format.DecimalStyle +java.time.format.DateTimeFormatterBuilder$SettingsParser +java.time.format.DateTimeFormatterBuilder$OffsetIdPrinterParser +java.time.format.DateTimeFormatterBuilder$FractionPrinterParser +java.time.format.DateTimeFormatterBuilder$ZoneIdPrinterParser +java.time.format.DateTimeFormatterBuilder$StringLiteralPrinterParser +java.time.format.DateTimeFormatterBuilder$InstantPrinterParser +java.time.format.TextStyle +java.time.format.DateTimeTextProvider$LocaleStore +java.util.AbstractMap$SimpleImmutableEntry +java.time.format.DateTimeTextProvider +java.time.format.DateTimeTextProvider$1 +java.time.format.DateTimeFormatterBuilder$1 +java.time.format.DateTimeFormatterBuilder$TextPrinterParser +java.time.chrono.ChronoPeriod +java.time.Period +java.time.format.DateTimeFormatter$$Lambda$8/0x80000000c +java.time.format.DateTimeFormatter$$Lambda$9/0x80000000d +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$562/0x00007faab41634e8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$FromIntegerArguments +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$563/0x00007faab4163938 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$FromDecimalArguments +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$564/0x00007faab4163d88 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$565/0x00007faab4163fc8 +java.time.OffsetDateTime +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$566/0x00007faab41641f8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$567/0x00007faab4164438 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$568/0x00007faab4164678 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$569/0x00007faab41648b8 +java.time.chrono.ChronoZonedDateTime +java.time.ZonedDateTime +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$570/0x00007faab4164ae8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$571/0x00007faab4164d28 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$572/0x00007faab4164f68 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer$$Lambda$573/0x00007faab41651a8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.module.SimpleDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.ClassKey +java.time.chrono.ChronoLocalDateTime +java.time.LocalDateTime +java.time.chrono.ChronoLocalDate +java.time.LocalDate +java.time.LocalTime +java.time.MonthDay +java.time.OffsetTime +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.deser.JSR310StringParsableDeserializer +java.time.ZoneOffset +java.time.Year +java.time.YearMonth +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.module.SimpleSerializers +java.util.function.ToLongFunction +java.lang.invoke.LambdaForm$DMH/0x00007faab4168000 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer$$Lambda$574/0x00007faab41665f8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer$$Lambda$575/0x00007faab4166818 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer$$Lambda$576/0x00007faab4166a38 +java.lang.invoke.LambdaForm$DMH/0x00007faab4168400 +java.lang.invoke.LambdaForm$DMH/0x00007faab4168800 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.OffsetDateTimeSerializer$$Lambda$577/0x00007faab4166c58 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.OffsetDateTimeSerializer$$Lambda$578/0x00007faab4166e78 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.OffsetDateTimeSerializer$$Lambda$579/0x00007faab4167098 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.ZonedDateTimeSerializer$$Lambda$580/0x00007faab41672b8 +java.lang.invoke.LambdaForm$DMH/0x00007faab4168c00 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.ZonedDateTimeSerializer$$Lambda$581/0x00007faab41674d8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.ser.ZonedDateTimeSerializer$$Lambda$582/0x00007faab41676f8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.module.SimpleKeyDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectMapper$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.ArrayBuilders +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiators$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jsr310.JavaTimeModule$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeModifier +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.Jdk8TypeModifier +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.Jdk8BeanSerializerModifier +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.PackageVersion +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.Jdk8Serializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalIntSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalLongSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalDoubleSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.LongStreamSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.IntStreamSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.DoubleStreamSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.StreamSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.Jdk8Deserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.BaseScalarOptionalDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalIntDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalLongDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.jdk8.OptionalDoubleDeserializer +com.amazonaws.services.lambda.runtime.serialization.util.SerializeUtil +com.amazonaws.services.lambda.runtime.serialization.util.ReflectUtil +java.lang.ExceptionInInitializerError +java.lang.InstantiationException +com.amazonaws.services.lambda.runtime.serialization.util.Functions$R0 +com.amazonaws.services.lambda.runtime.serialization.util.Functions$R1 +com.amazonaws.services.lambda.runtime.serialization.util.Functions$R9 +com.amazonaws.services.lambda.runtime.serialization.util.Functions$R4 +com.amazonaws.services.lambda.runtime.serialization.util.Functions$R3 +com.amazonaws.services.lambda.runtime.serialization.util.Functions$R5 +com.amazonaws.services.lambda.runtime.serialization.util.Functions$R2 +java.lang.ClassFormatError +com.amazonaws.services.lambda.runtime.serialization.util.Functions$V2 +com.amazonaws.services.lambda.runtime.serialization.util.Functions$V1 +com.amazonaws.services.lambda.runtime.events.models.kinesis.Record +com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record +com.amazonaws.services.lambda.runtime.serialization.events.modules.DateModule +com.amazonaws.services.lambda.runtime.serialization.events.modules.DateModule$Serializer +com.amazonaws.services.lambda.runtime.serialization.events.modules.DateModule$Deserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.PackageVersion +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.JodaModule +com.amazonaws.services.lambda.runtime.serialization.events.modules.DateTimeModule +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.JodaDeserializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.DateTimeZoneDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.DurationDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.JodaDateDeserializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.InstantDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.LocalDateTimeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.LocalDateDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.LocalTimeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.PeriodDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.IntervalDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.MonthDayDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.YearMonthDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.JodaSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.JodaDateSerializerBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.DateTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.DateTimeZoneSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.DurationSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.InstantSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.LocalDateTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.LocalDateSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.LocalTimeSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.PeriodSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.IntervalSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.MonthDaySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.YearMonthSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.key.JodaKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.key.DateTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.key.LocalTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.key.LocalDateKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.key.LocalDateTimeKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.key.DurationKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.key.PeriodKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.DateMidnightDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.ser.DateMidnightSerializer +com.amazonaws.services.lambda.runtime.serialization.events.modules.DateTimeModule$1 +com.amazonaws.services.lambda.runtime.serialization.events.modules.DateTimeModule$2 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.PackageVersion +com.amazonaws.lambda.thirdparty.org.joda.time.ReadableInstant +com.amazonaws.lambda.thirdparty.org.joda.time.ReadableDateTime +com.amazonaws.lambda.thirdparty.org.joda.time.base.AbstractInstant +com.amazonaws.lambda.thirdparty.org.joda.time.base.AbstractDateTime +com.amazonaws.lambda.thirdparty.org.joda.time.base.BaseDateTime +com.amazonaws.lambda.thirdparty.org.joda.time.DateTime +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.deser.DateTimeDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.cfg.FormatConfig +com.amazonaws.lambda.thirdparty.org.joda.time.DateTimeZone +com.amazonaws.lambda.thirdparty.org.joda.time.IllegalInstantException +com.amazonaws.lambda.thirdparty.org.joda.time.JodaTimePermission +com.amazonaws.lambda.thirdparty.org.joda.time.tz.NameProvider +com.amazonaws.lambda.thirdparty.org.joda.time.tz.FixedDateTimeZone +com.amazonaws.lambda.thirdparty.org.joda.time.tz.Provider +com.amazonaws.lambda.thirdparty.org.joda.time.UTCDateTimeZone +java.util.SimpleTimeZone +com.amazonaws.lambda.thirdparty.org.joda.time.tz.ZoneInfoProvider +java.lang.ArrayIndexOutOfBoundsException +com.amazonaws.lambda.thirdparty.org.joda.time.tz.ZoneInfoProvider$1 +java.util.Collections$UnmodifiableSortedSet +com.amazonaws.lambda.thirdparty.org.joda.time.tz.DateTimeZoneBuilder +com.amazonaws.lambda.thirdparty.org.joda.time.tz.DateTimeZoneBuilder$PrecalculatedZone +com.amazonaws.lambda.thirdparty.org.joda.time.tz.CachedDateTimeZone +com.amazonaws.lambda.thirdparty.org.joda.time.tz.DateTimeZoneBuilder$DSTZone +com.amazonaws.lambda.thirdparty.org.joda.time.Chronology +com.amazonaws.lambda.thirdparty.org.joda.time.chrono.BaseChronology +com.amazonaws.lambda.thirdparty.org.joda.time.chrono.AssembledChronology +com.amazonaws.lambda.thirdparty.org.joda.time.chrono.ISOChronology +com.amazonaws.lambda.thirdparty.org.joda.time.tz.CachedDateTimeZone$Info +com.amazonaws.lambda.thirdparty.org.joda.time.format.ISODateTimeFormat +com.amazonaws.lambda.thirdparty.org.joda.time.format.ISODateTimeFormat$Constants +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeFormatterBuilder +com.amazonaws.lambda.thirdparty.org.joda.time.format.InternalPrinter +com.amazonaws.lambda.thirdparty.org.joda.time.format.InternalParser +com.amazonaws.lambda.thirdparty.org.joda.time.DateTimeFieldType +com.amazonaws.lambda.thirdparty.org.joda.time.DateTimeFieldType$StandardDateTimeFieldType +com.amazonaws.lambda.thirdparty.org.joda.time.DurationFieldType +com.amazonaws.lambda.thirdparty.org.joda.time.DurationFieldType$StandardDurationFieldType +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeFormatterBuilder$NumberFormatter +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeFormatterBuilder$PaddedNumber +com.amazonaws.lambda.thirdparty.org.joda.time.DateTimeField +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeFormatterBuilder$Composite +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeFormatter +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeFormatterBuilder$CharacterLiteral +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeFormatterBuilder$StringLiteral +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeFormatterBuilder$UnpaddedNumber +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeFormatterBuilder$Fraction +com.amazonaws.lambda.thirdparty.org.joda.time.field.BaseDateTimeField +com.amazonaws.lambda.thirdparty.org.joda.time.field.PreciseDurationDateTimeField +com.amazonaws.lambda.thirdparty.org.joda.time.field.PreciseDateTimeField +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeFormatterBuilder$TimeZoneOffset +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeFormatterBuilder$FixedNumber +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeParser +com.amazonaws.lambda.thirdparty.org.joda.time.format.InternalParserDateTimeParser +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeParserInternalParser +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimeFormatterBuilder$MatchingParser +com.amazonaws.lambda.thirdparty.org.joda.time.format.DateTimePrinterInternalPrinter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.cfg.JacksonJodaFormatBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.cfg.JacksonJodaDateFormat +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.datatype.joda.cfg.JacksonJodaPeriodFormat +com.amazonaws.lambda.thirdparty.org.joda.time.format.ISOPeriodFormat +com.amazonaws.lambda.thirdparty.org.joda.time.format.PeriodFormatterBuilder +com.amazonaws.lambda.thirdparty.org.joda.time.format.PeriodPrinter +com.amazonaws.lambda.thirdparty.org.joda.time.format.PeriodParser +com.amazonaws.lambda.thirdparty.org.joda.time.format.PeriodFormatterBuilder$PeriodFieldAffix +com.amazonaws.lambda.thirdparty.org.joda.time.format.PeriodFormatterBuilder$FieldFormatter +com.amazonaws.lambda.thirdparty.org.joda.time.format.PeriodFormatterBuilder$Literal +com.amazonaws.lambda.thirdparty.org.joda.time.format.PeriodFormatterBuilder$IgnorableAffix +com.amazonaws.lambda.thirdparty.org.joda.time.format.PeriodFormatterBuilder$SimpleAffix +com.amazonaws.lambda.thirdparty.org.joda.time.format.PeriodFormatterBuilder$Separator +com.amazonaws.lambda.thirdparty.org.joda.time.format.PeriodFormatterBuilder$Composite +com.amazonaws.lambda.thirdparty.org.joda.time.format.PeriodFormatter +com.amazonaws.lambda.thirdparty.org.joda.time.ReadablePeriod +com.amazonaws.lambda.thirdparty.org.joda.time.ReadWritablePeriod +com.amazonaws.lambda.thirdparty.org.joda.time.ReadableDuration +com.amazonaws.lambda.thirdparty.org.joda.time.base.AbstractDuration +com.amazonaws.lambda.thirdparty.org.joda.time.base.BaseDuration +com.amazonaws.lambda.thirdparty.org.joda.time.Duration +com.amazonaws.lambda.thirdparty.org.joda.time.Instant +com.amazonaws.lambda.thirdparty.org.joda.time.ReadablePartial +com.amazonaws.lambda.thirdparty.org.joda.time.base.AbstractPartial +com.amazonaws.lambda.thirdparty.org.joda.time.base.BaseLocal +com.amazonaws.lambda.thirdparty.org.joda.time.LocalDateTime +com.amazonaws.lambda.thirdparty.org.joda.time.LocalDate +com.amazonaws.lambda.thirdparty.org.joda.time.LocalTime +com.amazonaws.lambda.thirdparty.org.joda.time.base.AbstractPeriod +com.amazonaws.lambda.thirdparty.org.joda.time.base.BasePeriod +com.amazonaws.lambda.thirdparty.org.joda.time.Period +com.amazonaws.lambda.thirdparty.org.joda.time.ReadableInterval +com.amazonaws.lambda.thirdparty.org.joda.time.base.AbstractInterval +com.amazonaws.lambda.thirdparty.org.joda.time.base.BaseInterval +com.amazonaws.lambda.thirdparty.org.joda.time.Interval +com.amazonaws.lambda.thirdparty.org.joda.time.base.BasePartial +com.amazonaws.lambda.thirdparty.org.joda.time.MonthDay +com.amazonaws.lambda.thirdparty.org.joda.time.YearMonth +com.amazonaws.lambda.thirdparty.org.joda.time.DateMidnight +org.joda.time.ReadableInstant +org.joda.time.ReadableDateTime +org.joda.time.base.AbstractInstant +org.joda.time.base.AbstractDateTime +org.joda.time.base.BaseDateTime +org.joda.time.DateTime +org.joda.time.Chronology +org.joda.time.chrono.BaseChronology +org.joda.time.chrono.AssembledChronology +org.joda.time.chrono.ISOChronology +com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory$InternalSerializer +com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory$ClassSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.ClassStack +sun.reflect.generics.repository.AbstractRepository +sun.reflect.generics.repository.GenericDeclRepository +sun.reflect.generics.repository.ClassRepository +java.lang.reflect.TypeVariable +sun.reflect.generics.tree.FormalTypeParameter +sun.reflect.generics.tree.Signature +sun.reflect.generics.tree.ClassSignature +java.util.OptionalLong +java.util.OptionalDouble +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$WeightedValue +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$Node +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$AddTask +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectReader +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.filter.TokenFilter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.filter.JsonPointerBasedFilter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ArrayNode +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.JsonParserDelegate +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.filter.FilteringParserDelegate +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonParseException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationCollector$OneCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonAutoDetect +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIdentityInfo +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.ArrayIterator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.OptionalHandlerFactory +org.w3c.dom.Node +org.w3c.dom.Document +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7Handlers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.NioPathSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.NioPathDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.JdkDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.FromStringDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.UUIDDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.AtomicBooleanDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.AtomicIntegerDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.AtomicLongDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.ByteBufferDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NullifyingDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$StringBuilderDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$StringBufferDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$Std +java.net.InetAddress +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.jsontype.impl.SubTypeValidator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.BeanUtil +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JsonValueInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ValueInstantiator$Base +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.JsonLocationInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$ArrayListInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$ConstantValueInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$LinkedHashMapInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$HashMapInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonLocation +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ConstructorDetector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.cfg.ConstructorDetector$SingleArgConstructor +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.CreatorCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdValueInstantiator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.CollectorBase +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.TypeResolutionContext$Basic +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector$FieldBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonKey +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonValue +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonAnyGetter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonAnySetter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.InternCache +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonSetter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonAutoDetect$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.PropertyAccessor +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIgnore +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$WithMember +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.AnnotationIntrospector$ReferenceProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.AnnotationIntrospector$ReferenceProperty$Type +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$Linked +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedMethodCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.MemberKey +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonProperty$Access +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JacksonAnnotation +jdk.proxy2.$Proxy16 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedMethodCollector$MethodBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotationCollector$NCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JacksonAnnotationsInside +jdk.proxy2.$Proxy17 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedMethodMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonGetter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.AnnotatedCreatorCollector +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$5 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$6 +java.util.LinkedList$ListItr +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JacksonInject +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JsonNaming +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonPropertyOrder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonPropertyDescription +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.PropertyMetadata +sun.reflect.generics.scope.MethodScope +sun.reflect.generics.repository.ConstructorRepository +sun.reflect.generics.repository.MethodRepository +sun.reflect.generics.tree.VoidDescriptor +sun.reflect.generics.tree.MethodTypeSignature +com.amazonaws.services.lambda.runtime.events.KinesisEvent$KinesisEventRecord +java.lang.reflect.ParameterizedType +sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl +sun.reflect.generics.tree.TypeVariableSignature +sun.reflect.generics.reflectiveObjects.LazyReflectiveObjectGenerator +sun.reflect.generics.reflectiveObjects.TypeVariableImpl +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeBindings$TypeParamStash +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.type.TypeBindings$AsKey +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BasicDeserializerFactory$CreatorCollectionState +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIgnoreProperties +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIgnoreProperties$Value +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIncludeProperties +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIncludeProperties$Value +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.IgnorePropertiesUtil +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonIgnoreType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JsonTypeResolver +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.FailingDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.AccessPattern +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$2 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$4 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonAlias +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonFormat$Feature +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.IgnoredPropertyException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.SettableBeanProperty$Delegating +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.ManagedReferenceProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.ObjectIdReferenceProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.MergingSettableBeanProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.InnerClassProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.ReadableObjectId$Referring +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BeanDeserializer$BeanReferring +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.impl.BeanAsArrayDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.BasicDeserializerFactory$ContainerDefaultMappings +java.util.concurrent.ConcurrentNavigableMap +java.util.concurrent.ConcurrentSkipListMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.LinkedNode +com.amazonaws.services.lambda.runtime.events.models.kinesis.EncryptionType +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$PrimitiveOrWrapperDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$IntegerDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BooleanDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$LongDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$DoubleDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$CharacterDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$ByteDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$ShortDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$FloatDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$NumberDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BigDecimalDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BigIntegerDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.DateDeserializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateBasedDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.DateDeserializers$CalendarDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.ByteBufferBackedOutputStream +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.MinimalPrettyPrinter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectWriter$GeneratorSettings +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectWriter$Prefetch +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.RuntimeJsonMappingException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.ContentReference +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.IOContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.BufferRecyclers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.BufferRecycler +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.UTF8StreamJsonParser +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.MergedStream +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.UTF32Reader +java.io.CharConversionException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonEncoding +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.exc.InputCoercionException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.JsonEOFException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonStreamContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.JsonReadContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.StreamReadCapability +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.JacksonFeatureSet +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.util.TextBuffer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonToken +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.NumberInput +org.junit.jupiter.params.provider.Arguments$$Lambda$583/0x00007faab41aefd8 +org.junit.jupiter.params.ParameterizedInvocationContext +org.junit.jupiter.params.ParameterizedTestInvocationContext +java.util.function.IntUnaryOperator +java.lang.invoke.LambdaForm$DMH/0x00007faab41b0000 +org.junit.jupiter.params.ParameterizedInvocationContext$$Lambda$584/0x00007faab41af670 +org.junit.jupiter.params.EvaluatedArgumentSet +org.junit.jupiter.params.EvaluatedArgumentSet$$Lambda$585/0x00007faab41afb00 +java.lang.invoke.LambdaForm$DMH/0x00007faab41b0400 +org.junit.jupiter.params.provider.Arguments$ArgumentSet +org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor +org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor$$Lambda$586/0x00007faab41b45b0 +org.junit.jupiter.api.Named +org.junit.jupiter.params.EvaluatedArgumentSet$$Lambda$587/0x00007faab41b49d0 +org.junit.jupiter.params.EvaluatedArgumentSet$$Lambda$588/0x00007faab41b4c10 +java.util.stream.ReferencePipeline$$Lambda$589/0x00007faab40fc2b8 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$MessageFormatPartialFormatter +java.util.stream.Streams$RangeIntSpliterator +java.lang.invoke.LambdaForm$DMH/0x00007faab41b0800 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$590/0x00007faab41b5088 +java.util.stream.IntPipeline$1 +java.util.stream.IntPipeline$1$1 +org.junit.jupiter.params.ResolverFacade$$Lambda$591/0x00007faab41b52b0 +org.junit.jupiter.params.ParameterizedInvocationNameFormatter$$Lambda$592/0x00007faab41b54f0 +java.text.MessageFormat +sun.util.locale.provider.TimeZoneNameUtility +sun.util.locale.provider.TimeZoneNameUtility$TimeZoneNameGetter +sun.util.cldr.CLDRLocaleProviderAdapter$$Lambda$593/0x00007faab40fded0 +sun.util.locale.provider.TimeZoneNameProviderImpl +sun.util.cldr.CLDRTimeZoneNameProviderImpl +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$594/0x00007faab40fe578 +sun.util.locale.provider.BaseLocaleDataMetaInfo +sun.util.locale.provider.JRELocaleProviderAdapter$$Lambda$595/0x00007faab40fe9f8 +sun.util.resources.provider.NonBaseLocaleDataMetaInfo +sun.util.resources.OpenListResourceBundle +sun.util.resources.TimeZoneNamesBundle +sun.util.resources.cldr.TimeZoneNames +sun.util.resources.cldr.TimeZoneNames_en +sun.util.cldr.CLDRBaseLocaleDataMetaInfo$TZCanonicalIDMapHolder +java.time.ZoneRegion +java.time.zone.ZoneRulesProvider +java.time.zone.ZoneRulesProvider$1 +java.time.zone.TzdbZoneRulesProvider +java.time.zone.Ser +java.time.zone.ZoneRules +java.time.zone.ZoneOffsetTransitionRule +java.time.zone.ZoneOffsetTransition +sun.util.resources.TimeZoneNames +sun.util.resources.TimeZoneNames_en +java.text.MessageFormat$Field +org.junit.jupiter.engine.descriptor.TemplateExecutor$$Lambda$596/0x00007faab41b5730 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$597/0x00007faab41b5968 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$598/0x00007faab41b5ba0 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$599/0x00007faab41b5dc8 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$600/0x00007faab41b6000 +org.junit.platform.engine.support.hierarchical.NodeTestTask$DefaultDynamicTestExecutor$$Lambda$601/0x00007faab41b6228 +org.junit.platform.engine.support.hierarchical.NodeTestTask$DynamicTaskState +org.junit.platform.engine.support.hierarchical.NodeTestTask$DynamicTaskState$$Lambda$602/0x00007faab41b6650 +org.junit.jupiter.params.ParameterizedInvocationParameterResolver +org.junit.jupiter.params.ParameterizedTestMethodParameterResolver +org.junit.jupiter.params.ResolutionCache +org.junit.jupiter.params.ResolutionCache$$Lambda$603/0x00007faab41b6f10 +org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor$$Lambda$604/0x00007faab41b7130 +org.junit.jupiter.engine.descriptor.MethodExtensionContext +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$605/0x00007faab41b7848 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$606/0x00007faab41b7a70 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$607/0x00007faab41b7c98 +org.junit.jupiter.engine.execution.ExtensionContextSupplier$ScopeBasedExtensionContextSupplier +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$608/0x00007faab41b2428 +org.junit.jupiter.engine.descriptor.DefaultTestInstanceFactoryContext +org.junit.jupiter.api.extension.TestInstancePreConstructCallback +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$609/0x00007faab41b2aa8 +java.lang.invoke.LambdaForm$DMH/0x00007faab41b0c00 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$610/0x00007faab41b2ce0 +org.junit.jupiter.engine.execution.ConstructorInvocation +org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptorCall +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$$Lambda$611/0x00007faab41b3398 +org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation +org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation +org.aspectj.lang.NoAspectBoundException +software.amazon.lambda.powertools.validation.ValidationException +org.slf4j.LoggerFactory +org.slf4j.spi.SLF4JServiceProvider +java.util.ServiceConfigurationError +org.slf4j.event.LoggingEvent +org.slf4j.helpers.SubstituteServiceProvider +org.slf4j.IMarkerFactory +org.slf4j.spi.MDCAdapter +org.slf4j.ILoggerFactory +org.slf4j.helpers.SubstituteLoggerFactory +org.slf4j.Logger +java.util.concurrent.LinkedBlockingQueue +java.util.concurrent.LinkedBlockingQueue$Node +org.slf4j.helpers.BasicMarkerFactory +org.slf4j.Marker +org.slf4j.helpers.BasicMDCAdapter +java.lang.InheritableThreadLocal +org.slf4j.helpers.BasicMDCAdapter$1 +org.slf4j.helpers.ThreadLocalMapOfStacks +org.slf4j.helpers.NOP_FallbackServiceProvider +org.slf4j.helpers.NOPLoggerFactory +org.slf4j.helpers.NOPMDCAdapter +org.slf4j.helpers.Util +org.slf4j.simple.SimpleServiceProvider +org.slf4j.MDC +org.slf4j.simple.SimpleLoggerFactory +org.slf4j.helpers.AbstractLogger +org.slf4j.helpers.LegacyAbstractLogger +org.slf4j.simple.SimpleLogger +org.slf4j.spi.LoggingEventBuilder +org.slf4j.simple.SimpleLoggerConfiguration +org.slf4j.simple.SimpleLoggerConfiguration$$Lambda$612/0x00007faab41bb830 +org.slf4j.simple.OutputChoice +org.slf4j.simple.OutputChoice$OutputChoiceType +org.slf4j.helpers.Reporter +org.slf4j.helpers.Reporter$TargetChoice +org.slf4j.helpers.Reporter$Level +org.slf4j.simple.SimpleLoggerFactory$$Lambda$613/0x00007faab41bcb30 +org.junit.jupiter.engine.execution.DefaultTestInstances +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$614/0x00007faab41bcfd8 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$615/0x00007faab41bd220 +org.junit.jupiter.api.extension.TestInstancePostProcessor +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$616/0x00007faab41bd648 +org.junit.jupiter.params.EvaluatedArgumentSet$$Lambda$617/0x00007faab41bd880 +org.junit.jupiter.params.ParameterizedInvocationContext$$Lambda$618/0x00007faab41bdac0 +org.junit.jupiter.params.ParameterizedInvocationContext$$Lambda$619/0x00007faab41bdd18 +org.junit.jupiter.api.extension.ExtensionContext$Store$CloseableResource +org.junit.jupiter.params.ParameterizedInvocationContext$CloseableArgument +org.junit.jupiter.params.ParameterizedInvocationContext$$Lambda$620/0x00007faab41be3a8 +org.junit.jupiter.params.ParameterizedInvocationContext$$Lambda$621/0x00007faab41be5e8 +org.junit.jupiter.params.ArgumentCountValidator +org.junit.jupiter.params.ArgumentCountValidator$$Lambda$622/0x00007faab41bea38 +org.junit.jupiter.params.ArgumentCountValidator$1 +org.junit.jupiter.params.EvaluatedArgumentSet$$Lambda$623/0x00007faab41bee88 +org.junit.jupiter.params.aggregator.DefaultArgumentsAccessor +org.junit.jupiter.params.aggregator.ArgumentAccessException +org.junit.jupiter.params.aggregator.DefaultArgumentsAccessor$$Lambda$624/0x00007faab41bf638 +org.junit.jupiter.params.aggregator.DefaultArgumentsAccessor$$Lambda$625/0x00007faab41bf870 +org.junit.jupiter.params.support.ParameterInfo +org.junit.jupiter.params.DefaultParameterInfo +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$626/0x00007faab4200000 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$627/0x00007faab4200248 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$628/0x00007faab4200498 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$629/0x00007faab42006e0 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$630/0x00007faab4200928 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$631/0x00007faab4200b68 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$632/0x00007faab4200db8 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$633/0x00007faab4200ff8 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$634/0x00007faab4201250 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$635/0x00007faab42014a0 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$636/0x00007faab42016e0 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$637/0x00007faab4201930 +org.junit.jupiter.engine.extension.TempDirectory$FailureTracker +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$638/0x00007faab4201d98 +org.junit.jupiter.engine.extension.TempDirectory$$Lambda$639/0x00007faab4201fd0 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$640/0x00007faab4202220 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$641/0x00007faab4202448 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$642/0x00007faab4202668 +org.junit.jupiter.engine.execution.MethodInvocation +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$$Lambda$643/0x00007faab4202b28 +org.junit.jupiter.engine.extension.TimeoutExtension$TimeoutProvider +org.junit.jupiter.engine.extension.TimeoutConfiguration +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$644/0x00007faab42031a0 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$645/0x00007faab42033f8 +org.junit.jupiter.engine.extension.TimeoutDurationParser +java.time.format.DateTimeParseException +java.util.regex.Pattern$$Lambda$646/0x00007faab41c2be0 +java.util.regex.Pattern$$Lambda$647/0x00007faab41c2e40 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$648/0x00007faab4203850 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$649/0x00007faab4203a78 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$650/0x00007faab4203cc0 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$651/0x00007faab4203f08 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$652/0x00007faab4204130 +org.mockito.MockitoAnnotations +org.mockito.configuration.IMockitoConfiguration +org.mockito.internal.configuration.GlobalConfiguration +org.mockito.configuration.DefaultMockitoConfiguration +org.mockito.internal.configuration.ClassPathLoader +org.mockito.exceptions.misusing.MockitoConfigurationException +org.mockito.internal.configuration.plugins.Plugins +org.mockito.plugins.MockitoPlugins +org.mockito.internal.configuration.plugins.PluginRegistry +org.mockito.plugins.PluginSwitch +org.mockito.internal.configuration.plugins.PluginLoader +org.mockito.internal.configuration.plugins.DefaultPluginSwitch +org.mockito.internal.configuration.plugins.DefaultMockitoPlugins +org.mockito.plugins.MockMaker +org.mockito.plugins.StackTraceCleanerProvider +org.mockito.plugins.InstantiatorProvider2 +org.mockito.plugins.AnnotationEngine +org.mockito.plugins.MockitoLogger +org.mockito.plugins.MemberAccessor +org.mockito.plugins.DoNotMockEnforcerWithType +org.mockito.internal.configuration.plugins.PluginInitializer +java.lang.invoke.LambdaForm$MH/0x00007faab4208000 +org.mockito.internal.configuration.plugins.PluginFinder +org.mockito.internal.util.collections.Iterables +org.mockito.internal.creation.bytebuddy.ClassCreatingMockMaker +org.mockito.plugins.InlineMockMaker +org.mockito.creation.instance.Instantiator +org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker +org.mockito.exceptions.base.MockitoInitializationException +org.mockito.internal.creation.bytebuddy.BytecodeGenerator +org.mockito.creation.instance.InstantiationException +org.mockito.plugins.MockMaker$TypeMockability +org.mockito.plugins.MockMaker$StaticMockControl +org.mockito.plugins.MockMaker$ConstructionMockControl +org.mockito.internal.PremainAttachAccess +org.mockito.internal.PremainAttach +java.lang.instrument.Instrumentation +net.bytebuddy.agent.Installer +java.io.Console +java.lang.Deprecated +jdk.proxy1.$Proxy18 +net.bytebuddy.ClassFileVersion +net.bytebuddy.ClassFileVersion$VersionLocator$Resolver +net.bytebuddy.ClassFileVersion$VersionLocator +net.bytebuddy.ClassFileVersion$VersionLocator$Resolved +java.lang.Process +net.bytebuddy.agent.ByteBuddyAgent +net.bytebuddy.agent.ByteBuddyAgent$AgentProvider +net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider +net.bytebuddy.agent.ByteBuddyAgent$AttachmentTypeEvaluator$InstallationAction +net.bytebuddy.agent.ByteBuddyAgent$AttachmentTypeEvaluator +java.security.PrivilegedActionException +com.sun.proxy.jdk.proxy1.$Proxy19 +java.security.DomainCombiner +net.bytebuddy.agent.ByteBuddyAgent$AttachmentTypeEvaluator$ForJava9CapableVm +java.lang.ProcessHandle +java.lang.ProcessHandle$Info +java.util.concurrent.CompletionStage +java.util.concurrent.CompletableFuture +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$Compound +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$Accessor +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$ForModularizedVm +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$ForJ9Vm +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$ForStandardToolsJarVm +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$ForUserDefinedToolsJar +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$ForEmulatedAttachment +net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm +net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm$ForJava9CapableVm +java.lang.reflect.AnnotatedType +java.lang.ProcessHandleImpl +java.lang.ProcessHandleImpl$$Lambda$653/0x00007faab41c5a00 +java.util.concurrent.ThreadLocalRandom +jdk.internal.util.random.RandomSupport +java.lang.invoke.LambdaForm$DMH/0x00007faab4208400 +java.lang.invoke.LambdaForm$DMH/0x00007faab4208800 +java.lang.ProcessHandleImpl$$Lambda$654/0x00007faab41c5c20 +java.lang.invoke.LambdaForm$DMH/0x00007faab4208c00 +java.lang.invoke.LambdaForm$MH/0x00007faab4209000 +java.util.concurrent.SynchronousQueue +java.util.concurrent.SynchronousQueue$Transferer +java.util.concurrent.SynchronousQueue$TransferStack +java.util.concurrent.SynchronousQueue$TransferStack$SNode +net.bytebuddy.agent.ByteBuddyAgent$AgentProvider$ForByteBuddyAgent +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$Accessor$Simple +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$Accessor$Simple$WithExternalAttachment +com.sun.tools.attach.VirtualMachine +net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$Accessor$ExternalAttachment +java.util.zip.ZipInputStream +java.util.jar.JarInputStream +java.io.PushbackInputStream +sun.security.util.ManifestEntryVerifier +net.bytebuddy.agent.Attacher +java.lang.ProcessBuilder +java.lang.ProcessImpl +java.lang.ProcessImpl$Platform +java.lang.ProcessImpl$LaunchMechanism +java.lang.ProcessImpl$Platform$$Lambda$655/0x00007faab41c83f0 +java.lang.ProcessImpl$$Lambda$656/0x00007faab41c8618 +java.lang.invoke.LambdaForm$DMH/0x00007faab4210000 +java.lang.invoke.LambdaForm$DMH/0x00007faab4210400 +java.lang.invoke.LambdaForm$MH/0x00007faab4210800 +java.lang.ProcessImpl$1 +java.lang.ProcessImpl$ProcessPipeOutputStream +java.lang.ProcessImpl$ProcessPipeInputStream +java.lang.Process$PipeInputStream +java.lang.ProcessHandleImpl$ExitCompletion +java.util.concurrent.CompletableFuture$AltResult +java.util.concurrent.ForkJoinPool +java.lang.invoke.VarHandleLongs$FieldInstanceReadOnly +java.lang.invoke.VarHandleLongs$FieldInstanceReadWrite +java.lang.invoke.VarHandleInts$FieldStaticReadOnly +java.lang.invoke.VarHandleInts$FieldStaticReadWrite +java.util.concurrent.ForkJoinPool$ForkJoinWorkerThreadFactory +java.util.concurrent.ForkJoinPool$DefaultForkJoinWorkerThreadFactory +java.util.concurrent.ForkJoinPool$1 +java.util.concurrent.ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory +java.util.concurrent.ForkJoinPool$WorkQueue +java.util.concurrent.CompletableFuture$AsynchronousCompletionTask +java.util.concurrent.ForkJoinTask +java.util.concurrent.CompletableFuture$Completion +java.lang.ProcessHandleImpl$1 +java.lang.ProcessImpl$$Lambda$657/0x00007faab41ca448 +java.util.concurrent.CompletableFuture$UniCompletion +java.util.concurrent.CompletableFuture$UniHandle +java.util.concurrent.ForkJoinTask$Aux +jdk.internal.event.Event +jdk.internal.event.ProcessStartEvent +sun.instrument.InstrumentationImpl +sun.instrument.TransformerManager +sun.instrument.TransformerManager$TransformerInfo +java.lang.ProcessBuilder$NullInputStream +java.io.FileOutputStream$1 +java.lang.ProcessBuilder$NullOutputStream +java.io.File$TempDirectory +java.security.SecureRandom +sun.security.jca.Providers +sun.security.jca.ProviderList +sun.security.jca.ProviderConfig +java.security.Provider +sun.security.jca.ProviderList$3 +sun.security.jca.ProviderList$1 +java.security.Provider$ServiceKey +java.security.Provider$EngineDescription +java.security.SecureRandomParameters +java.security.cert.CertStoreParameters +java.security.Policy$Parameters +javax.security.auth.login.Configuration$Parameters +sun.security.jca.ProviderList$2 +sun.security.provider.Sun +sun.security.util.SecurityConstants +java.net.NetPermission +java.security.SecurityPermission +java.net.SocketPermission +sun.security.provider.SunEntries +sun.security.provider.SunEntries$1 +java.security.SecureRandomSpi +sun.security.provider.NativePRNG +sun.security.provider.NativePRNG$Variant +sun.security.provider.NativePRNG$1 +sun.security.provider.NativePRNG$2 +sun.security.provider.NativePRNG$RandomIO +sun.security.provider.FileInputStreamPool +sun.security.provider.FileInputStreamPool$UnclosableInputStream +sun.security.provider.FileInputStreamPool$StreamRef +java.security.Provider$Service +java.security.Provider$UString +sun.security.provider.NativePRNG$Blocking +sun.security.provider.NativePRNG$NonBlocking +sun.security.util.SecurityProviderConstants +sun.security.util.KnownOIDs +sun.security.util.KnownOIDs$1 +sun.security.util.KnownOIDs$2 +sun.security.util.KnownOIDs$3 +sun.security.util.KnownOIDs$4 +sun.security.util.KnownOIDs$5 +sun.security.util.KnownOIDs$6 +sun.security.util.KnownOIDs$7 +sun.security.util.KnownOIDs$8 +sun.security.util.KnownOIDs$9 +sun.security.util.KnownOIDs$10 +jdk.internal.event.SecurityProviderServiceEvent +sun.security.provider.SecureRandom +java.security.MessageDigestSpi +java.security.MessageDigest +sun.security.jca.GetInstance +sun.security.provider.DigestBase +sun.security.provider.SHA +sun.security.jca.GetInstance$Instance +sun.security.util.MessageDigestSpi2 +java.security.MessageDigest$Delegate +java.security.MessageDigest$Delegate$CloneableDelegate +sun.security.provider.ByteArrayAccess +sun.security.provider.ByteArrayAccess$BE +java.lang.invoke.VarHandleByteArrayAsInts$ByteArrayViewVarHandle +java.lang.invoke.VarHandleByteArrayAsInts$ArrayHandle +java.lang.invoke.VarHandleByteArrayBase +java.lang.invoke.VarHandleByteArrayAsInts +java.lang.invoke.VarHandleByteArrayAsInts$ArrayHandle$$Lambda$658/0x00007faab41d7338 +java.lang.invoke.VarHandleByteArrayAsLongs$ByteArrayViewVarHandle +java.lang.invoke.VarHandleByteArrayAsLongs$ArrayHandle +java.lang.invoke.VarHandleByteArrayAsLongs +java.lang.invoke.VarHandleByteArrayAsLongs$ArrayHandle$$Lambda$659/0x00007faab41d7ca0 +java.lang.invoke.VarHandle$TypesAndInvokers +java.lang.invoke.VarHandle$2 +java.lang.invoke.VarHandle$VarHandleDesc$Kind +java.lang.constant.ConstantDescs +java.lang.constant.ClassDesc +java.lang.constant.ConstantUtils +java.lang.constant.ReferenceClassDescImpl +java.lang.constant.DirectMethodHandleDesc$Kind +java.lang.constant.MethodTypeDesc +java.lang.constant.MethodTypeDescImpl +java.lang.constant.MethodHandleDesc +java.lang.constant.MethodHandleDesc$1 +java.lang.constant.DirectMethodHandleDesc +java.lang.constant.DirectMethodHandleDescImpl +java.lang.constant.DirectMethodHandleDescImpl$1 +java.lang.constant.DirectMethodHandleDesc$1 +java.lang.constant.DynamicConstantDesc +java.lang.constant.PrimitiveClassDescImpl +java.lang.constant.DynamicConstantDesc$AnonymousDynamicConstantDesc +java.io.DeleteOnExitHook +java.io.DeleteOnExitHook$1 +java.util.zip.DeflaterOutputStream +java.util.zip.ZipOutputStream +java.util.jar.JarOutputStream +java.util.zip.Deflater +java.util.zip.Deflater$DeflaterZStreamRef +java.util.zip.ZipOutputStream$XEntry +java.util.Vector$Itr +opened:/tmp/mockitoboot5452617940860437443.jar +org.mockito.internal.creation.bytebuddy.inject.MockMethodDispatcher +org.mockito.internal.util.concurrent.WeakConcurrentMap +org.mockito.internal.util.concurrent.DetachedThreadLocal +org.mockito.internal.util.concurrent.DetachedThreadLocal$1 +org.mockito.internal.util.concurrent.WeakConcurrentMap$WithInlinedExpunction +org.mockito.internal.util.concurrent.DetachedThreadLocal$2 +org.mockito.internal.util.concurrent.DetachedThreadLocal$Cleaner +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker$$Lambda$660/0x00007faab4215060 +java.lang.ThreadLocal$SuppliedThreadLocal +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker$$Lambda$661/0x00007faab4215280 +org.mockito.internal.creation.bytebuddy.StackWalkerChecker +java.lang.StackWalker$Option +java.lang.invoke.LambdaForm$DMH/0x00007faab4210c00 +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker$$Lambda$662/0x00007faab4215708 +org.mockito.internal.creation.bytebuddy.ConstructionCallback +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker$$Lambda$663/0x00007faab4215b60 +org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator +net.bytebuddy.matcher.ElementMatcher +net.bytebuddy.TypeCache +net.bytebuddy.TypeCache$WithInlineExpunction +java.lang.instrument.ClassFileTransformer +org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator +net.bytebuddy.implementation.Implementation$Context$Factory +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler +net.bytebuddy.dynamic.scaffold.InstrumentedType$Prepareable +net.bytebuddy.implementation.Implementation +net.bytebuddy.asm.AsmVisitorWrapper +org.mockito.internal.creation.bytebuddy.MockMethodAdvice +java.lang.instrument.UnmodifiableClassException +net.bytebuddy.ByteBuddy +net.bytebuddy.NamingStrategy +net.bytebuddy.implementation.auxiliary.AuxiliaryType$NamingStrategy +net.bytebuddy.matcher.LatentMatcher +net.bytebuddy.utility.AsmClassWriter$Factory +net.bytebuddy.utility.AsmClassReader$Factory +net.bytebuddy.dynamic.VisibilityBridgeStrategy +net.bytebuddy.dynamic.scaffold.InstrumentedType$Factory +net.bytebuddy.implementation.attribute.AnnotationValueFilter$Factory +net.bytebuddy.NamingStrategy$Suffixing$BaseNameResolver +net.bytebuddy.dynamic.DynamicType$Builder +net.bytebuddy.description.NamedElement +net.bytebuddy.description.ModifierReviewable +net.bytebuddy.description.ModifierReviewable$OfByteCodeElement +net.bytebuddy.description.ModifierReviewable$OfAbstraction +net.bytebuddy.description.ModifierReviewable$OfEnumeration +net.bytebuddy.description.ModifierReviewable$ForTypeDefinition +net.bytebuddy.description.type.TypeDefinition +net.bytebuddy.matcher.FilterableList +net.bytebuddy.description.type.TypeList$Generic +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy +net.bytebuddy.description.NamedElement$WithRuntimeName +net.bytebuddy.description.annotation.AnnotationSource +net.bytebuddy.description.type.PackageDescription +net.bytebuddy.description.NamedElement$WithDescriptor +net.bytebuddy.description.DeclaredByType +net.bytebuddy.description.ByteCodeElement +net.bytebuddy.description.TypeVariableSource +net.bytebuddy.description.type.TypeDescription +net.bytebuddy.utility.privilege.GetSystemPropertyAction +net.bytebuddy.dynamic.scaffold.TypeValidation +net.bytebuddy.utility.GraalImageCode +net.bytebuddy.NamingStrategy$AbstractBase +net.bytebuddy.NamingStrategy$Suffixing +net.bytebuddy.NamingStrategy$SuffixingRandom +net.bytebuddy.NamingStrategy$Suffixing$BaseNameResolver$ForUnnamedType +net.bytebuddy.utility.RandomString +net.bytebuddy.implementation.auxiliary.AuxiliaryType$NamingStrategy$SuffixingRandom +net.bytebuddy.implementation.attribute.AnnotationValueFilter +net.bytebuddy.implementation.attribute.AnnotationValueFilter$Default +net.bytebuddy.implementation.attribute.AnnotationValueFilter$Default$1 +net.bytebuddy.implementation.attribute.AnnotationValueFilter$Default$2 +net.bytebuddy.implementation.attribute.AnnotationRetention +net.bytebuddy.implementation.Implementation$Context$Default$Factory +net.bytebuddy.implementation.MethodAccessorFactory +net.bytebuddy.implementation.Implementation$Context +net.bytebuddy.implementation.Implementation$Context$ExtractableView +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$AbstractBase +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default +net.bytebuddy.dynamic.scaffold.MethodGraph +net.bytebuddy.dynamic.scaffold.MethodGraph$Linked +net.bytebuddy.description.type.TypeDescription$Generic$Visitor +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Merger +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Harmonizer +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Harmonizer$ForJavaMethod +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Merger$Directional +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Reifying +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Reifying$1 +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Reifying$2 +net.bytebuddy.description.type.TypeDescription$Generic +net.bytebuddy.matcher.ElementMatchers +net.bytebuddy.matcher.ElementMatcher$Junction +net.bytebuddy.description.ModifierReviewable$ForFieldDescription +net.bytebuddy.description.DeclaredByType$WithMandatoryDeclaration +net.bytebuddy.description.NamedElement$WithGenericName +net.bytebuddy.description.ByteCodeElement$Member +net.bytebuddy.description.ByteCodeElement$TypeDependant +net.bytebuddy.description.field.FieldDescription +net.bytebuddy.description.field.FieldDescription$InDefinedShape +net.bytebuddy.description.ModifierReviewable$ForMethodDescription +net.bytebuddy.description.method.MethodDescription +net.bytebuddy.description.method.MethodDescription$InDefinedShape +net.bytebuddy.matcher.ElementMatcher$Junction$AbstractBase +net.bytebuddy.matcher.BooleanMatcher +net.bytebuddy.dynamic.scaffold.InstrumentedType$Factory$Default +net.bytebuddy.dynamic.scaffold.InstrumentedType$Factory$Default$1 +net.bytebuddy.dynamic.scaffold.InstrumentedType$Factory$Default$2 +net.bytebuddy.implementation.LoadedTypeInitializer +net.bytebuddy.implementation.bytecode.ByteCodeAppender +net.bytebuddy.dynamic.scaffold.TypeInitializer +net.bytebuddy.dynamic.scaffold.InstrumentedType +net.bytebuddy.dynamic.scaffold.InstrumentedType$WithFlexibleName +net.bytebuddy.dynamic.VisibilityBridgeStrategy$Default +net.bytebuddy.dynamic.VisibilityBridgeStrategy$Default$1 +net.bytebuddy.dynamic.VisibilityBridgeStrategy$Default$2 +net.bytebuddy.dynamic.VisibilityBridgeStrategy$Default$3 +net.bytebuddy.utility.AsmClassReader$Factory$Default +net.bytebuddy.utility.AsmClassReader$Factory$Default$1 +net.bytebuddy.utility.AsmClassReader$Factory$Default$2 +net.bytebuddy.utility.AsmClassReader$Factory$Default$3 +net.bytebuddy.utility.AsmClassReader$Factory$Default$4 +net.bytebuddy.utility.AsmClassReader$Factory$Default$5 +net.bytebuddy.utility.AsmClassReader +net.bytebuddy.utility.AsmClassWriter$Factory$Default +net.bytebuddy.utility.AsmClassWriter$Factory$Default$1 +net.bytebuddy.utility.AsmClassWriter$Factory$Default$2 +net.bytebuddy.utility.AsmClassWriter$Factory$Default$3 +net.bytebuddy.utility.AsmClassWriter$Factory$Default$4 +net.bytebuddy.utility.AsmClassWriter$Factory$Default$5 +net.bytebuddy.pool.TypePool +net.bytebuddy.jar.asm.ClassVisitor +net.bytebuddy.jar.asm.ClassWriter +net.bytebuddy.utility.AsmClassWriter$FrameComputingClassWriter +net.bytebuddy.utility.AsmClassWriter +net.bytebuddy.matcher.LatentMatcher$Resolved +net.bytebuddy.matcher.ModifierMatcher$Mode +net.bytebuddy.matcher.ElementMatcher$Junction$ForNonNullValues +net.bytebuddy.matcher.ModifierMatcher +net.bytebuddy.matcher.NameMatcher +net.bytebuddy.matcher.StringMatcher +net.bytebuddy.matcher.StringMatcher$Mode +net.bytebuddy.matcher.StringMatcher$Mode$1 +net.bytebuddy.matcher.StringMatcher$Mode$2 +net.bytebuddy.matcher.StringMatcher$Mode$3 +net.bytebuddy.matcher.StringMatcher$Mode$4 +net.bytebuddy.matcher.StringMatcher$Mode$5 +net.bytebuddy.matcher.StringMatcher$Mode$6 +net.bytebuddy.matcher.StringMatcher$Mode$7 +net.bytebuddy.matcher.StringMatcher$Mode$8 +net.bytebuddy.matcher.StringMatcher$Mode$9 +net.bytebuddy.matcher.MethodParametersMatcher +net.bytebuddy.matcher.CollectionSizeMatcher +net.bytebuddy.matcher.ElementMatcher$Junction$Conjunction +net.bytebuddy.description.ModifierReviewable$ForParameterDescription +net.bytebuddy.description.ModifierReviewable$AbstractBase +net.bytebuddy.description.TypeVariableSource$AbstractBase +net.bytebuddy.description.type.TypeDescription$AbstractBase +net.bytebuddy.description.type.TypeDescription$ForLoadedType +java.lang.reflect.GenericSignatureFormatError +net.bytebuddy.description.annotation.AnnotationList +net.bytebuddy.description.field.FieldList +net.bytebuddy.description.type.RecordComponentList +net.bytebuddy.matcher.FilterableList$Empty +net.bytebuddy.description.type.RecordComponentList$Empty +net.bytebuddy.matcher.FilterableList$AbstractBase +net.bytebuddy.description.type.RecordComponentList$AbstractBase +net.bytebuddy.description.type.RecordComponentList$ForLoadedRecordComponents +net.bytebuddy.description.method.MethodList +net.bytebuddy.description.type.TypeList +net.bytebuddy.description.type.TypeList$Empty +net.bytebuddy.description.type.TypeList$AbstractBase +net.bytebuddy.description.type.TypeList$ForLoadedTypes +net.bytebuddy.description.type.TypeDescription$ForLoadedType$Dispatcher +net.bytebuddy.utility.dispatcher.JavaDispatcher +net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher +net.bytebuddy.utility.dispatcher.JavaDispatcher$DynamicClassLoader$Resolver$CreationAction +net.bytebuddy.utility.dispatcher.JavaDispatcher$DynamicClassLoader$Resolver +net.bytebuddy.utility.dispatcher.JavaDispatcher$DynamicClassLoader$Resolver$ForModuleSystem +net.bytebuddy.utility.dispatcher.JavaDispatcher$InvokerCreationAction +net.bytebuddy.utility.dispatcher.JavaDispatcher$DynamicClassLoader +net.bytebuddy.utility.Invoker +net.bytebuddy.jar.asm.ClassTooLargeException +net.bytebuddy.jar.asm.FieldVisitor +net.bytebuddy.jar.asm.FieldWriter +net.bytebuddy.jar.asm.AnnotationVisitor +net.bytebuddy.jar.asm.AnnotationWriter +net.bytebuddy.jar.asm.MethodVisitor +net.bytebuddy.jar.asm.MethodWriter +net.bytebuddy.jar.asm.ModuleVisitor +net.bytebuddy.jar.asm.ModuleWriter +net.bytebuddy.jar.asm.RecordComponentVisitor +net.bytebuddy.jar.asm.RecordComponentWriter +java.lang.TypeNotPresentException +net.bytebuddy.jar.asm.SymbolTable +net.bytebuddy.jar.asm.Symbol +net.bytebuddy.jar.asm.SymbolTable$Entry +net.bytebuddy.jar.asm.ByteVector +net.bytebuddy.jar.asm.Type +net.bytebuddy.utility.MethodComparator +net.bytebuddy.jar.asm.Frame +net.bytebuddy.jar.asm.CurrentFrame +net.bytebuddy.jar.asm.MethodTooLargeException +net.bytebuddy.jar.asm.Handler +net.bytebuddy.jar.asm.Attribute +net.bytebuddy.utility.Invoker$Dispatcher +net.bytebuddy.utility.dispatcher.JavaDispatcher$Proxied +net.bytebuddy.utility.dispatcher.JavaDispatcher$Defaults +jdk.proxy2.$Proxy20 +jdk.proxy2.$Proxy21 +net.bytebuddy.utility.dispatcher.JavaDispatcher$Instance +net.bytebuddy.utility.dispatcher.JavaDispatcher$Container +net.bytebuddy.utility.dispatcher.JavaDispatcher$IsStatic +net.bytebuddy.utility.dispatcher.JavaDispatcher$IsConstructor +net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher$ForNonStaticMethod +net.bytebuddy.utility.nullability.MaybeNull +jdk.proxy2.$Proxy22 +net.bytebuddy.utility.dispatcher.JavaDispatcher$ProxiedInvocationHandler +net.bytebuddy.description.type.$Proxy23 +net.bytebuddy.dynamic.TargetType +net.bytebuddy.matcher.EqualityMatcher +net.bytebuddy.matcher.ErasureMatcher +net.bytebuddy.matcher.MethodReturnTypeMatcher +net.bytebuddy.matcher.DeclaringTypeMatcher +net.bytebuddy.matcher.ElementMatcher$Junction$Disjunction +net.bytebuddy.implementation.Implementation$Context$Disabled$Factory +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$ForDeclaredMethods +net.bytebuddy.matcher.MethodSortMatcher$Sort +net.bytebuddy.matcher.MethodSortMatcher$Sort$1 +net.bytebuddy.matcher.MethodSortMatcher$Sort$2 +net.bytebuddy.matcher.MethodSortMatcher$Sort$3 +net.bytebuddy.matcher.MethodSortMatcher$Sort$4 +net.bytebuddy.matcher.MethodSortMatcher$Sort$5 +net.bytebuddy.matcher.MethodSortMatcher +net.bytebuddy.matcher.NegatingMatcher +org.mockito.internal.util.concurrent.WeakConcurrentSet +org.mockito.internal.util.concurrent.WeakConcurrentSet$Cleaner +org.mockito.internal.creation.bytebuddy.SubclassBytecodeGenerator +net.bytebuddy.implementation.attribute.MethodAttributeAppender$Factory +net.bytebuddy.dynamic.loading.ClassLoadingStrategy +org.mockito.internal.creation.bytebuddy.ModuleHandler +org.mockito.internal.creation.bytebuddy.ModuleHandler$ModuleSystemFound +org.mockito.internal.creation.bytebuddy.ModuleHandler$1 +org.mockito.internal.creation.bytebuddy.ModuleHandler$NoModuleSystemFound +org.mockito.internal.creation.bytebuddy.ModuleHandler$2 +org.mockito.internal.creation.bytebuddy.ModuleHandler$3 +java.lang.instrument.ClassDefinition +org.mockito.internal.creation.bytebuddy.ModuleHandler$MockitoMockClassLoader +jdk.internal.vm.annotation.ForceInline +com.sun.proxy.jdk.proxy1.$Proxy24 +net.bytebuddy.implementation.Implementation$Composable +net.bytebuddy.implementation.MethodDelegation +net.bytebuddy.implementation.bind.MethodDelegationBinder$TerminationHandler +net.bytebuddy.implementation.bind.MethodDelegationBinder$Record +net.bytebuddy.implementation.bind.MethodDelegationBinder$AmbiguityResolver +net.bytebuddy.implementation.MethodDelegation$WithCustomProperties +net.bytebuddy.implementation.bind.MethodDelegationBinder$BindingResolver +net.bytebuddy.implementation.MethodDelegation$ImplementationDelegate +net.bytebuddy.dynamic.scaffold.FieldLocator$Factory +net.bytebuddy.implementation.bind.MethodDelegationBinder$AmbiguityResolver$Compound +net.bytebuddy.implementation.bind.annotation.BindingPriority$Resolver +net.bytebuddy.implementation.bind.annotation.BindingPriority +net.bytebuddy.description.method.MethodList$AbstractBase +net.bytebuddy.description.method.MethodList$ForLoadedMethods +net.bytebuddy.description.method.MethodDescription$AbstractBase +net.bytebuddy.description.method.MethodDescription$InDefinedShape$AbstractBase +net.bytebuddy.description.method.MethodDescription$InDefinedShape$AbstractBase$ForLoadedExecutable +net.bytebuddy.description.method.ParameterDescription$ForLoadedParameter$ParameterAnnotationSource +net.bytebuddy.description.method.MethodDescription$ForLoadedConstructor +net.bytebuddy.description.method.MethodDescription$ForLoadedMethod +net.bytebuddy.utility.ConstructorComparator +net.bytebuddy.description.ByteCodeElement$Token +net.bytebuddy.description.method.MethodDescription$InDefinedShape$AbstractBase$Executable +net.bytebuddy.description.method.$Proxy25 +net.bytebuddy.implementation.bind.DeclaringTypeResolver +net.bytebuddy.implementation.bind.ArgumentTypeResolver +net.bytebuddy.implementation.bind.MethodNameEqualityResolver +net.bytebuddy.implementation.bind.ParameterLengthResolver +net.bytebuddy.implementation.bind.MethodDelegationBinder$AmbiguityResolver$NoOp +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$ParameterBinder +net.bytebuddy.implementation.bind.annotation.Argument$Binder +net.bytebuddy.implementation.bytecode.StackManipulation +net.bytebuddy.implementation.bind.MethodDelegationBinder$ParameterBinding +net.bytebuddy.implementation.bind.annotation.Argument +net.bytebuddy.implementation.bind.annotation.Argument$BindingMechanic +net.bytebuddy.description.method.MethodList$Explicit +net.bytebuddy.implementation.bind.annotation.AllArguments$Binder +net.bytebuddy.implementation.bind.annotation.AllArguments +net.bytebuddy.implementation.bind.annotation.AllArguments$Assignment +net.bytebuddy.implementation.bind.annotation.Origin$Binder +net.bytebuddy.implementation.bind.annotation.Origin +net.bytebuddy.implementation.bind.annotation.This$Binder +net.bytebuddy.implementation.bind.annotation.This +net.bytebuddy.implementation.bind.annotation.Super$Binder +net.bytebuddy.implementation.bind.annotation.Super +net.bytebuddy.implementation.bind.annotation.Super$Instantiation +net.bytebuddy.implementation.bind.annotation.Default$Binder +net.bytebuddy.implementation.bind.annotation.Default +net.bytebuddy.implementation.bind.annotation.SuperCall$Binder +net.bytebuddy.implementation.bind.annotation.SuperCall +net.bytebuddy.implementation.bind.annotation.SuperCallHandle$Binder +net.bytebuddy.implementation.bind.annotation.SuperCallHandle +net.bytebuddy.implementation.bind.annotation.DefaultCall$Binder +net.bytebuddy.implementation.bind.annotation.DefaultCall$Binder$DefaultMethodLocator +net.bytebuddy.implementation.bind.annotation.DefaultCall +net.bytebuddy.implementation.bind.annotation.DefaultCallHandle$Binder +net.bytebuddy.implementation.bind.annotation.DefaultCallHandle$Binder$DefaultMethodLocator +net.bytebuddy.implementation.bind.annotation.DefaultCallHandle +net.bytebuddy.implementation.bind.annotation.SuperMethod$Binder +net.bytebuddy.implementation.bind.annotation.SuperMethod +net.bytebuddy.implementation.bind.annotation.SuperMethodHandle$Binder +net.bytebuddy.implementation.bind.annotation.SuperMethodHandle +net.bytebuddy.implementation.bind.annotation.Handle$Binder +net.bytebuddy.utility.ConstantValue +net.bytebuddy.utility.JavaConstant +net.bytebuddy.implementation.bind.annotation.Handle +net.bytebuddy.utility.JavaConstant$MethodHandle$HandleType +net.bytebuddy.implementation.bind.annotation.DynamicConstant$Binder +net.bytebuddy.implementation.bind.annotation.DynamicConstant +net.bytebuddy.implementation.bind.annotation.DefaultMethod$Binder +net.bytebuddy.implementation.bind.annotation.DefaultMethod$Binder$MethodLocator +net.bytebuddy.implementation.bind.annotation.DefaultMethod +net.bytebuddy.implementation.bind.annotation.DefaultMethodHandle$Binder +net.bytebuddy.implementation.bind.annotation.DefaultMethodHandle$Binder$MethodLocator +net.bytebuddy.implementation.bind.annotation.DefaultMethodHandle +net.bytebuddy.implementation.bind.annotation.FieldValue$Binder +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$ParameterBinder$ForFieldBinding +net.bytebuddy.implementation.bind.annotation.FieldValue$Binder$Delegate +net.bytebuddy.dynamic.scaffold.FieldLocator +net.bytebuddy.dynamic.scaffold.FieldLocator$AbstractBase +net.bytebuddy.dynamic.scaffold.FieldLocator$ForClassHierarchy +net.bytebuddy.dynamic.scaffold.FieldLocator$ForExactType +net.bytebuddy.implementation.bind.annotation.FieldValue +net.bytebuddy.implementation.bind.annotation.FieldGetterHandle$Binder +net.bytebuddy.implementation.bind.annotation.FieldGetterHandle$Binder$Delegate +net.bytebuddy.implementation.bind.annotation.FieldGetterHandle +net.bytebuddy.implementation.bind.annotation.FieldSetterHandle$Binder +net.bytebuddy.implementation.bind.annotation.FieldSetterHandle$Binder$Delegate +net.bytebuddy.implementation.bind.annotation.FieldSetterHandle +net.bytebuddy.implementation.bind.annotation.StubValue$Binder +net.bytebuddy.implementation.bind.annotation.Empty$Binder +net.bytebuddy.implementation.bind.MethodDelegationBinder$BindingResolver$Default +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$Identifier +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$ParameterBinder$ForFixedValue +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$ParameterBinder$ForFixedValue$OfConstant +net.bytebuddy.utility.CompoundList +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$ForReadObject +org.mockito.internal.creation.bytebuddy.access.MockAccess +net.bytebuddy.implementation.bind.MethodDelegationBinder +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$DelegationProcessor +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$DelegationProcessor$Handler +net.bytebuddy.implementation.bind.annotation.StubValue +net.bytebuddy.implementation.bind.annotation.Empty +net.bytebuddy.implementation.MethodDelegation$ImplementationDelegate$ForStaticMethod +net.bytebuddy.implementation.MethodDelegation$ImplementationDelegate$Compiled +net.bytebuddy.implementation.bind.annotation.IgnoreForBinding$Verifier +net.bytebuddy.description.annotation.AnnotationList$AbstractBase +net.bytebuddy.description.annotation.AnnotationList$ForLoadedAnnotations +net.bytebuddy.description.annotation.AnnotationDescription +net.bytebuddy.implementation.bind.annotation.IgnoreForBinding +net.bytebuddy.description.method.ParameterList +net.bytebuddy.description.method.ParameterList$AbstractBase +net.bytebuddy.description.method.ParameterList$ForLoadedExecutable +net.bytebuddy.description.method.ParameterList$ForLoadedExecutable$OfMethod +net.bytebuddy.description.method.ParameterList$ForLoadedExecutable$OfLegacyVmMethod +net.bytebuddy.description.method.ParameterList$ForLoadedExecutable$OfConstructor +net.bytebuddy.description.method.ParameterList$ForLoadedExecutable$OfLegacyVmConstructor +net.bytebuddy.description.method.ParameterList$ForLoadedExecutable$Executable +jdk.proxy2.$Proxy26 +net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher$ForInstanceCheck +jdk.internal.reflect.GeneratedConstructorAccessor7 +net.bytebuddy.description.method.$Proxy27 +net.bytebuddy.description.NamedElement$WithOptionalName +net.bytebuddy.description.method.ParameterDescription +net.bytebuddy.description.method.ParameterDescription$InDefinedShape +net.bytebuddy.description.method.ParameterDescription$AbstractBase +net.bytebuddy.description.method.ParameterDescription$InDefinedShape$AbstractBase +net.bytebuddy.description.method.ParameterDescription$ForLoadedParameter +net.bytebuddy.description.method.ParameterDescription$ForLoadedParameter$OfMethod +net.bytebuddy.description.method.ParameterDescription$ForLoadedParameter$Parameter +net.bytebuddy.description.method.$Proxy28 +net.bytebuddy.implementation.bind.annotation.RuntimeType$Verifier +org.mockito.internal.creation.bytebuddy.$Proxy29 +jdk.proxy2.$Proxy30 +net.bytebuddy.implementation.bind.annotation.Argument$BindingMechanic$1 +net.bytebuddy.implementation.bind.annotation.Argument$BindingMechanic$2 +jdk.proxy2.$Proxy31 +net.bytebuddy.implementation.bind.annotation.RuntimeType +net.bytebuddy.description.annotation.AnnotationDescription$Loadable +net.bytebuddy.description.annotation.AnnotationDescription$AbstractBase +net.bytebuddy.description.annotation.AnnotationDescription$ForLoadedAnnotation +net.bytebuddy.description.annotation.AnnotationValue +net.bytebuddy.description.enumeration.EnumerationDescription +net.bytebuddy.description.type.TypeDefinition$Sort +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader +net.bytebuddy.description.type.TypeDefinition$Sort$AnnotatedType +net.bytebuddy.description.type.$Proxy32 +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$NoOp +net.bytebuddy.description.type.TypeDescription$Generic$AbstractBase +net.bytebuddy.description.type.TypeDescription$Generic$OfNonGenericType +net.bytebuddy.description.type.TypeDescription$Generic$OfNonGenericType$ForLoadedType +net.bytebuddy.implementation.bytecode.assign.Assigner$Typing +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$DelegationProcessor$Handler$Unbound +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$DelegationProcessor$Handler$Bound +net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder$Record +net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodBinding +net.bytebuddy.implementation.bind.MethodDelegationBinder$TerminationHandler$Default +net.bytebuddy.implementation.bind.MethodDelegationBinder$TerminationHandler$Default$1 +net.bytebuddy.implementation.bind.MethodDelegationBinder$TerminationHandler$Default$2 +net.bytebuddy.implementation.bytecode.assign.Assigner +net.bytebuddy.implementation.bytecode.assign.primitive.VoidAwareAssigner +net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveTypeAwareAssigner +net.bytebuddy.implementation.bytecode.assign.reference.ReferenceTypeAwareAssigner +net.bytebuddy.implementation.bytecode.StackManipulation$Trivial +net.bytebuddy.implementation.bytecode.StackManipulation$Illegal +net.bytebuddy.implementation.bytecode.assign.reference.GenericTypeAwareAssigner +org.mockito.internal.creation.bytebuddy.access.MockMethodInterceptor$DispatcherDefaultingToRealMethod +org.mockito.internal.invocation.RealMethod +org.mockito.internal.creation.bytebuddy.access.MockMethodInterceptor +jdk.proxy2.$Proxy33 +jdk.proxy2.$Proxy34 +jdk.proxy2.$Proxy35 +jdk.proxy2.$Proxy36 +jdk.proxy2.$Proxy37 +jdk.proxy2.$Proxy38 +jdk.proxy2.$Proxy39 +org.mockito.internal.creation.bytebuddy.access.MockMethodInterceptor$ForHashCode +org.mockito.internal.creation.bytebuddy.access.MockMethodInterceptor$ForEquals +org.mockito.internal.creation.bytebuddy.access.MockMethodInterceptor$ForWriteReplace +net.bytebuddy.TypeCache$Sort +net.bytebuddy.TypeCache$Sort$1 +net.bytebuddy.TypeCache$Sort$2 +net.bytebuddy.TypeCache$Sort$3 +org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator$TypeCachingLock +net.bytebuddy.asm.AsmVisitorWrapper$ForDeclaredMethods +net.bytebuddy.asm.AsmVisitorWrapper$ForDeclaredMethods$DispatchingVisitor +net.bytebuddy.matcher.CollectionOneToOneMatcher +net.bytebuddy.matcher.CollectionErasureMatcher +net.bytebuddy.matcher.MethodParameterTypesMatcher +net.bytebuddy.matcher.AnnotationTypeMatcher +net.bytebuddy.matcher.DeclaringAnnotationMatcher +net.bytebuddy.matcher.CollectionItemMatcher +net.bytebuddy.asm.AsmVisitorWrapper$ForDeclaredMethods$MethodVisitorWrapper +net.bytebuddy.asm.Advice +net.bytebuddy.asm.Advice$ExceptionHandler +net.bytebuddy.asm.Advice$Dispatcher +net.bytebuddy.asm.Advice$Dispatcher$Unresolved +net.bytebuddy.dynamic.ClassFileLocator +net.bytebuddy.asm.Advice$Delegator$Factory +net.bytebuddy.asm.Advice$PostProcessor$Factory +net.bytebuddy.utility.visitor.ExceptionTableSensitiveMethodVisitor +net.bytebuddy.utility.visitor.LineNumberPrependingMethodVisitor +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$Relocation +net.bytebuddy.asm.Advice$AdviceVisitor +net.bytebuddy.asm.Advice$AdviceVisitor$WithoutExitAdvice +net.bytebuddy.asm.Advice$AdviceVisitor$WithExitAdvice +net.bytebuddy.asm.Advice$AdviceVisitor$WithExitAdvice$WithoutExceptionHandling +net.bytebuddy.asm.Advice$AdviceVisitor$WithExitAdvice$WithExceptionHandling +net.bytebuddy.asm.Advice$OnMethodEnter +net.bytebuddy.asm.Advice$OnMethodExit +net.bytebuddy.asm.Advice$WithCustomMapping +net.bytebuddy.asm.Advice$OffsetMapping$Factory +net.bytebuddy.asm.Advice$BootstrapArgumentResolver$Factory +net.bytebuddy.asm.Advice$PostProcessor +net.bytebuddy.asm.Advice$PostProcessor$NoOp +net.bytebuddy.asm.Advice$Delegator$ForRegularInvocation$Factory +net.bytebuddy.asm.Advice$Delegator +net.bytebuddy.asm.Advice$OffsetMapping$ForStackManipulation$Factory +net.bytebuddy.asm.Advice$OffsetMapping +net.bytebuddy.utility.ConstantValue$Simple +net.bytebuddy.utility.JavaConstant$Simple +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher +jdk.proxy2.$Proxy40 +net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher$ForContainerCreation +net.bytebuddy.utility.$Proxy41 +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher$OfClassDesc +jdk.proxy2.$Proxy42 +net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher$ForStaticMethod +jdk.proxy2.$Proxy43 +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher$OfMethodTypeDesc +jdk.proxy2.$Proxy44 +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher$OfMethodHandleDesc +jdk.proxy2.$Proxy45 +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher$OfDirectMethodHandleDesc +jdk.proxy2.$Proxy46 +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher$OfDirectMethodHandleDesc$ForKind +jdk.proxy2.$Proxy47 +net.bytebuddy.utility.JavaConstant$Simple$Dispatcher$OfDynamicConstantDesc +jdk.proxy2.$Proxy48 +net.bytebuddy.utility.JavaConstant$Simple$OfTrivialValue +net.bytebuddy.utility.JavaConstant$Simple$OfTrivialValue$ForString +net.bytebuddy.implementation.bytecode.StackManipulation$AbstractBase +net.bytebuddy.implementation.bytecode.constant.TextConstant +net.bytebuddy.dynamic.ClassFileLocator$ForClassLoader +net.bytebuddy.dynamic.ClassFileLocator$Resolution +net.bytebuddy.dynamic.ClassFileLocator$ForClassLoader$BootLoaderProxyCreationAction +net.bytebuddy.asm.Advice$Dispatcher$Resolved +net.bytebuddy.asm.Advice$Dispatcher$Resolved$ForMethodEnter +net.bytebuddy.asm.Advice$Dispatcher$Resolved$ForMethodExit +net.bytebuddy.asm.Advice$Dispatcher$Bound +net.bytebuddy.asm.Advice$Dispatcher$Inactive +net.bytebuddy.asm.Advice$NoExceptionHandler +jdk.proxy2.$Proxy49 +net.bytebuddy.description.annotation.AnnotationValue$AbstractBase +net.bytebuddy.description.annotation.AnnotationValue$ForConstant +net.bytebuddy.description.annotation.AnnotationValue$Loaded +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$1 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$2 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$3 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$4 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$5 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$6 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$7 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$8 +net.bytebuddy.description.annotation.AnnotationValue$ForConstant$PropertyDelegate$ForNonArrayType$9 +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithEagerNavigation +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithEagerNavigation$OfAnnotatedElement +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$ForLoadedReturnType +net.bytebuddy.asm.Advice$Dispatcher$Inlining +net.bytebuddy.asm.Advice$Return +jdk.proxy2.$Proxy50 +net.bytebuddy.asm.Advice$Enter +jdk.proxy2.$Proxy51 +net.bytebuddy.asm.Advice$Local +net.bytebuddy.asm.Advice$OnNonDefaultValue +jdk.proxy2.$Proxy52 +net.bytebuddy.asm.Advice$This +jdk.proxy2.$Proxy53 +net.bytebuddy.asm.Advice$Origin +jdk.proxy2.$Proxy54 +net.bytebuddy.asm.Advice$AllArguments +jdk.proxy2.$Proxy55 +net.bytebuddy.dynamic.ClassFileLocator$Resolution$Explicit +net.bytebuddy.utility.StreamDrainer +net.bytebuddy.utility.OpenedClassReader +net.bytebuddy.utility.AsmClassReader$ForAsm +net.bytebuddy.jar.asm.ClassReader +net.bytebuddy.asm.Advice$Dispatcher$Resolved$AbstractBase +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$ForMethodEnter +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$ForMethodEnter$WithRetainedEnterType +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$ForMethodEnter$WithDiscardedEnterType +net.bytebuddy.asm.Advice$ArgumentHandler +net.bytebuddy.asm.Advice$Dispatcher$Inlining$CodeTranslationVisitor +net.bytebuddy.asm.Advice$OffsetMapping$ForArgument$Unresolved$Factory +net.bytebuddy.asm.Advice$Argument +net.bytebuddy.asm.Advice$OffsetMapping$ForAllArguments$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForThisReference$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForField$Unresolved$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForField +net.bytebuddy.asm.Advice$OffsetMapping$ForField$Unresolved +net.bytebuddy.asm.Advice$OffsetMapping$ForField$Unresolved$WithImplicitType +net.bytebuddy.asm.Advice$OffsetMapping$ForField$Unresolved$WithExplicitType +net.bytebuddy.asm.Advice$OffsetMapping$ForFieldHandle$Unresolved$ReaderFactory +net.bytebuddy.asm.Advice$OffsetMapping$ForFieldHandle +net.bytebuddy.asm.Advice$OffsetMapping$ForFieldHandle$Unresolved +net.bytebuddy.asm.Advice$OffsetMapping$ForFieldHandle$Unresolved$WithImplicitType +net.bytebuddy.asm.Advice$OffsetMapping$ForFieldHandle$Unresolved$WithExplicitType +net.bytebuddy.asm.Advice$FieldGetterHandle +net.bytebuddy.asm.Advice$OffsetMapping$ForFieldHandle$Unresolved$WriterFactory +net.bytebuddy.asm.Advice$FieldSetterHandle +net.bytebuddy.asm.Advice$OffsetMapping$ForOrigin$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForSelfCallHandle$Factory +net.bytebuddy.asm.Advice$SelfCallHandle +net.bytebuddy.asm.Advice$OffsetMapping$ForHandle$Factory +net.bytebuddy.asm.Advice$Handle +net.bytebuddy.asm.Advice$OffsetMapping$ForDynamicConstant$Factory +net.bytebuddy.asm.Advice$DynamicConstant +net.bytebuddy.asm.Advice$OffsetMapping$ForUnusedValue$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForStubValue +net.bytebuddy.asm.Advice$OffsetMapping$Target +net.bytebuddy.asm.Advice$OffsetMapping$ForThrowable$Factory +net.bytebuddy.asm.Advice$Thrown +net.bytebuddy.asm.Advice$OffsetMapping$ForExitValue$Factory +net.bytebuddy.asm.Advice$Exit +net.bytebuddy.asm.Advice$OffsetMapping$Factory$Illegal +net.bytebuddy.asm.Advice$OffsetMapping$ForLocalValue$Factory +net.bytebuddy.description.annotation.AnnotationValue$ForTypeDescription +net.bytebuddy.description.annotation.AnnotationValue$ForMismatchedType +net.bytebuddy.asm.Advice$OffsetMapping$Factory$AdviceType +net.bytebuddy.asm.Advice$FieldValue +net.bytebuddy.asm.Advice$Unused +net.bytebuddy.asm.Advice$StubValue +net.bytebuddy.asm.Advice$OffsetMapping$ForStackManipulation +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$OfMethodParameter +net.bytebuddy.description.type.TypeList$Generic$AbstractBase +net.bytebuddy.description.type.TypeList$Generic$Explicit +net.bytebuddy.description.type.TypeList$Explicit +net.bytebuddy.implementation.bytecode.StackSize +net.bytebuddy.asm.Advice$OffsetMapping$ForThisReference +net.bytebuddy.asm.Advice$OffsetMapping$Target$ForDefaultValue +net.bytebuddy.asm.Advice$OffsetMapping$Target$ForDefaultValue$ReadOnly +net.bytebuddy.asm.Advice$OffsetMapping$Target$ForDefaultValue$ReadWrite +net.bytebuddy.description.enumeration.EnumerationDescription$AbstractBase +net.bytebuddy.description.enumeration.EnumerationDescription$ForLoadedEnumeration +net.bytebuddy.description.annotation.AnnotationValue$ForEnumerationDescription +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedMethod +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedMethod$1 +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedMethod$2 +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedMethod$3 +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedMethod$4 +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedMethod$5 +sun.reflect.generics.tree.ArrayTypeSignature +sun.reflect.generics.tree.BottomSignature +sun.reflect.generics.tree.Wildcard +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedExecutableParameterType +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedExecutableParameterType$Dispatcher +jdk.internal.reflect.GeneratedMethodAccessor1 +net.bytebuddy.description.type.$Proxy56 +net.bytebuddy.asm.Advice$OffsetMapping$ForAllArguments +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$Chained +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$ForComponentType +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$ForComponentType$AnnotatedParameterizedType +java.lang.reflect.AnnotatedArrayType +net.bytebuddy.description.type.$Proxy57 +net.bytebuddy.asm.Advice$Dispatcher$SuppressionHandler +net.bytebuddy.asm.Advice$Dispatcher$SuppressionHandler$Suppressing +net.bytebuddy.asm.Advice$Dispatcher$SuppressionHandler$Bound +net.bytebuddy.asm.Advice$Dispatcher$SuppressionHandler$NoOp +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForType +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$Bound +net.bytebuddy.asm.Advice$OnDefaultValue +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$1 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$2 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$3 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$4 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$5 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$6 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$7 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$8 +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$9 +java.lang.reflect.WildcardType +sun.reflect.generics.reflectiveObjects.WildcardTypeImpl +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedMethodReturnType +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedMethodReturnType$Dispatcher +net.bytebuddy.description.type.$Proxy58 +net.bytebuddy.description.type.TypeDescription$Generic$OfParameterizedType +net.bytebuddy.description.type.TypeDescription$Generic$OfParameterizedType$ForLoadedType +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$OfNonDefault +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$ForMethodExit +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$ForMethodExit$WithoutExceptionHandler +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$ForMethodExit$WithExceptionHandler +net.bytebuddy.asm.Advice$OffsetMapping$ForEnterValue$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForReturnValue$Factory +net.bytebuddy.asm.Advice$OffsetMapping$ForReturnValue +net.bytebuddy.asm.Advice$OffsetMapping$ForEnterValue +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$Disabled +net.bytebuddy.asm.Advice$ExceptionHandler$Default +net.bytebuddy.asm.Advice$ExceptionHandler$Default$1 +net.bytebuddy.asm.Advice$ExceptionHandler$Default$2 +net.bytebuddy.asm.Advice$ExceptionHandler$Default$3 +net.bytebuddy.implementation.SuperMethodCall +net.bytebuddy.asm.AsmVisitorWrapper$ForDeclaredMethods$Entry +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$ForStatic +jdk.internal.reflect.GeneratedMethodAccessor2 +net.bytebuddy.asm.Advice$OffsetMapping$ForInstrumentedType +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$ConstructorShortcut +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$ConstructorShortcut$1 +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$ForHashCode +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$ForEquals +jdk.proxy2.$Proxy59 +net.bytebuddy.asm.Advice$OffsetMapping$ForArgument +net.bytebuddy.asm.Advice$OffsetMapping$ForArgument$Unresolved +org.mockito.internal.creation.bytebuddy.MockMethodAdvice$SelfCallInfo +org.mockito.internal.util.reflection.ModuleMemberAccessor +org.mockito.internal.util.reflection.InstrumentationMemberAccessor +net.bytebuddy.dynamic.loading.InjectionClassLoader +net.bytebuddy.dynamic.loading.ByteArrayClassLoader +net.bytebuddy.implementation.MethodCall +net.bytebuddy.implementation.MethodCall$WithoutSpecifiedTarget +net.bytebuddy.dynamic.loading.ClassFilePostProcessor +net.bytebuddy.dynamic.loading.PackageDefinitionStrategy +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$PackageLookupStrategy$CreationAction +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$PackageLookupStrategy +net.bytebuddy.utility.JavaModule +net.bytebuddy.utility.JavaModule$Resolver +net.bytebuddy.utility.$Proxy60 +net.bytebuddy.utility.JavaModule$Module +net.bytebuddy.utility.$Proxy61 +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$PackageLookupStrategy$ForJava9CapableVm +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$SynchronizationStrategy$CreationAction +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$SynchronizationStrategy$Initializable +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$SynchronizationStrategy +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$SynchronizationStrategy$ForJava8CapableVm +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$PersistenceHandler +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$PersistenceHandler$1 +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$PersistenceHandler$2 +net.bytebuddy.dynamic.loading.PackageDefinitionStrategy$Trivial +net.bytebuddy.dynamic.loading.PackageDefinitionStrategy$Definition +net.bytebuddy.dynamic.loading.ClassFilePostProcessor$NoOp +org.mockito.internal.util.reflection.InstrumentationMemberAccessor$Dispatcher +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy$Default +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy$Default$1 +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy$Default$2 +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy$Default$3 +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy$Default$4 +net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy$Default$5 +net.bytebuddy.dynamic.scaffold.MethodRegistry$Handler +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$UsingTypeWriter +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter +net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder +net.bytebuddy.dynamic.TypeResolutionStrategy +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ImplementationDefinition +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$TypeVariableDefinition +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ExceptionDefinition +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition$Optional +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition$Valuable +net.bytebuddy.implementation.attribute.TypeAttributeAppender +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Delegator +net.bytebuddy.dynamic.DynamicType$Builder$InnerTypeDefinition +net.bytebuddy.dynamic.DynamicType$Builder$InnerTypeDefinition$ForType +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$InnerTypeDefinitionForTypeAdapter +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$InnerTypeDefinitionForMethodAdapter +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ImplementationDefinition$Optional +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Simple +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Initial +net.bytebuddy.dynamic.DynamicType$Builder$TypeVariableDefinition +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition$Optional$Valuable +net.bytebuddy.dynamic.DynamicType$Builder$RecordComponentDefinition +net.bytebuddy.dynamic.DynamicType$Builder$RecordComponentDefinition$Optional +net.bytebuddy.dynamic.scaffold.RecordComponentRegistry +net.bytebuddy.dynamic.scaffold.MethodRegistry +net.bytebuddy.dynamic.scaffold.FieldRegistry +net.bytebuddy.implementation.Implementation$Target$Factory +net.bytebuddy.dynamic.scaffold.TypeWriter$RecordComponentPool +net.bytebuddy.dynamic.scaffold.TypeWriter$FieldPool +net.bytebuddy.description.modifier.ModifierContributor +net.bytebuddy.description.modifier.ModifierContributor$ForType +net.bytebuddy.description.modifier.ModifierContributor$ForMethod +net.bytebuddy.description.modifier.ModifierContributor$ForField +net.bytebuddy.description.modifier.Visibility +net.bytebuddy.description.modifier.TypeManifestation +net.bytebuddy.description.modifier.ModifierContributor$Resolver +net.bytebuddy.description.type.TypeDescription$AbstractBase$OfSimpleType +net.bytebuddy.dynamic.scaffold.InstrumentedType$Default +net.bytebuddy.dynamic.scaffold.TypeInitializer$None +net.bytebuddy.implementation.LoadedTypeInitializer$NoOp +net.bytebuddy.description.type.TypeDescription$LazyProxy +net.bytebuddy.description.modifier.Ownership +net.bytebuddy.description.modifier.ModifierContributor$ForParameter +net.bytebuddy.description.modifier.SyntheticState +net.bytebuddy.description.modifier.EnumerationState +net.bytebuddy.description.TypeVariableSource$Visitor +jdk.proxy2.$Proxy62 +net.bytebuddy.description.type.TypeList$Generic$ForLoadedTypes +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Substitutor +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Substitutor$ForDetachment +net.bytebuddy.dynamic.scaffold.FieldRegistry$Default +net.bytebuddy.dynamic.scaffold.FieldRegistry$Compiled +net.bytebuddy.dynamic.scaffold.MethodRegistry$Default +net.bytebuddy.dynamic.scaffold.MethodRegistry$Prepared +net.bytebuddy.dynamic.scaffold.RecordComponentRegistry$Default +net.bytebuddy.dynamic.scaffold.RecordComponentRegistry$Compiled +net.bytebuddy.implementation.attribute.TypeAttributeAppender$ForInstrumentedType +net.bytebuddy.implementation.attribute.AnnotationAppender$Target +net.bytebuddy.implementation.attribute.AnnotationAppender +net.bytebuddy.asm.AsmVisitorWrapper$NoOp +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ImplementationDefinition$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$MethodMatchAdapter +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ReceiverTypeDefinition +net.bytebuddy.implementation.MethodCall$MethodLocator$Factory +net.bytebuddy.implementation.MethodCall$TerminationHandler$Factory +net.bytebuddy.implementation.MethodCall$MethodInvoker$Factory +net.bytebuddy.implementation.MethodCall$TargetHandler$Factory +net.bytebuddy.implementation.MethodCall$ArgumentLoader$Factory +net.bytebuddy.implementation.MethodCall$MethodLocator +net.bytebuddy.implementation.MethodCall$MethodLocator$ForExplicitMethod +net.bytebuddy.implementation.MethodCall$TargetHandler$ForField$Location +net.bytebuddy.implementation.MethodCall$TargetHandler$ForSelfOrStaticInvocation$Factory +net.bytebuddy.implementation.MethodCall$TargetHandler +net.bytebuddy.implementation.MethodCall$MethodInvoker$ForContextualInvocation$Factory +net.bytebuddy.implementation.MethodCall$MethodInvoker +net.bytebuddy.implementation.MethodCall$TerminationHandler +net.bytebuddy.implementation.MethodCall$TerminationHandler$Simple +net.bytebuddy.implementation.MethodCall$TerminationHandler$Simple$1 +net.bytebuddy.implementation.MethodCall$TerminationHandler$Simple$2 +net.bytebuddy.implementation.MethodCall$TerminationHandler$Simple$3 +net.bytebuddy.dynamic.scaffold.MethodRegistry$Handler$ForImplementation +net.bytebuddy.dynamic.scaffold.MethodRegistry$Handler$Compiled +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ReceiverTypeDefinition$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$AbstractBase$Adapter +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$MethodMatchAdapter$AnnotationAdapter +net.bytebuddy.dynamic.Transformer +net.bytebuddy.implementation.attribute.MethodAttributeAppender +net.bytebuddy.implementation.attribute.MethodAttributeAppender$NoOp +net.bytebuddy.dynamic.Transformer$NoOp +net.bytebuddy.dynamic.scaffold.MethodRegistry$Default$Entry +net.bytebuddy.implementation.MethodCall$TargetHandler$ForMethodCall$Factory +net.bytebuddy.implementation.MethodCall$MethodInvoker$ForVirtualInvocation$WithImplicitType +net.bytebuddy.implementation.MethodCall$TargetHandler$ForMethodParameter +net.bytebuddy.implementation.MethodCall$TargetHandler$Resolved +net.bytebuddy.implementation.MethodCall$ArgumentLoader$ArgumentProvider +net.bytebuddy.implementation.MethodCall$ArgumentLoader$ForMethodParameter$Factory +net.bytebuddy.dynamic.TypeResolutionStrategy$Resolved +net.bytebuddy.dynamic.TypeResolutionStrategy$Passive +net.bytebuddy.pool.TypePool$AbstractBase +net.bytebuddy.pool.TypePool$AbstractBase$Hierarchical +net.bytebuddy.pool.TypePool$ClassLoading +net.bytebuddy.pool.TypePool$Resolution +net.bytebuddy.pool.TypePool$CacheProvider +net.bytebuddy.pool.TypePool$Empty +net.bytebuddy.pool.TypePool$CacheProvider$Simple +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithResolvedErasure +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Substitutor$ForAttachment +net.bytebuddy.description.method.MethodList$TypeSubstituting +net.bytebuddy.description.method.MethodDescription$InGenericShape +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$ForRawType +net.bytebuddy.matcher.VisibilityMatcher +net.bytebuddy.description.method.MethodDescription$TypeSubstituting +net.bytebuddy.description.type.TypeDescription$Generic$OfParameterizedType$ForGenerifiedErasure +net.bytebuddy.description.type.TypeDescription$Generic$OfNonGenericType$ForErasure +net.bytebuddy.description.type.TypeList$Generic$ForLoadedTypes$OfTypeVariables +net.bytebuddy.description.method.MethodDescription$Token +net.bytebuddy.matcher.TypeSortMatcher +net.bytebuddy.description.ByteCodeElement$Token$TokenList +net.bytebuddy.description.method.ParameterList$TypeSubstituting +net.bytebuddy.description.method.ParameterDescription$InGenericShape +net.bytebuddy.description.type.TypeList$Generic$ForDetachedTypes +net.bytebuddy.description.type.TypeList$Generic$OfConstructorExceptionTypes +jdk.internal.vm.annotation.IntrinsicCandidate +com.sun.proxy.jdk.proxy1.$Proxy63 +net.bytebuddy.description.annotation.AnnotationList$Explicit +net.bytebuddy.description.type.TypeDescription$Generic$LazyProxy +jdk.proxy2.$Proxy64 +net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder$InstrumentableMatcher +net.bytebuddy.description.method.MethodList$ForTokens +net.bytebuddy.description.method.MethodDescription$Latent +net.bytebuddy.description.method.ParameterList$ForTokens +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithLazyNavigation +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithLazyNavigation$OfAnnotatedElement +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$ForLoadedSuperClass +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Store +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Store$Entry +net.bytebuddy.description.type.TypeList$Generic$ForDetachedTypes$WithResolvedErasure +net.bytebuddy.description.type.TypeList$Generic$OfLoadedInterfaceTypes +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Harmonized +net.bytebuddy.description.method.MethodDescription$TypeToken +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Harmonizer$ForJavaMethod$Token +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Store$Entry$Initial +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Store$Entry$Resolved +net.bytebuddy.dynamic.scaffold.MethodGraph$Node +net.bytebuddy.description.method.ParameterDescription$TypeSubstituting +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Store$Entry$Resolved$Node +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Detached +net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default$Key$Store$Graph +net.bytebuddy.dynamic.scaffold.MethodGraph$Linked$Delegation +net.bytebuddy.matcher.MethodParameterTypeMatcher +net.bytebuddy.matcher.FailSafeMatcher +net.bytebuddy.dynamic.scaffold.MethodGraph$NodeList +net.bytebuddy.dynamic.scaffold.MethodGraph$Node$Sort +net.bytebuddy.dynamic.scaffold.MethodRegistry$Default$Prepared$Entry +net.bytebuddy.description.method.MethodDescription$Latent$TypeInitializer +net.bytebuddy.dynamic.scaffold.MethodRegistry$Default$Prepared +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool +net.bytebuddy.dynamic.scaffold.MethodRegistry$Compiled +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator$1 +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator$2 +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator$3 +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator$ForTypeAnnotations +net.bytebuddy.description.annotation.AnnotationList$Empty +net.bytebuddy.description.type.TypeList$Generic$ForDetachedTypes$OfTypeVariables +net.bytebuddy.description.type.PackageDescription$AbstractBase +net.bytebuddy.description.type.PackageDescription$Simple +net.bytebuddy.description.field.FieldList$AbstractBase +net.bytebuddy.description.field.FieldList$ForTokens +net.bytebuddy.description.method.MethodDescription$SignatureToken +net.bytebuddy.description.annotation.AnnotationValue$ForDescriptionArray +net.bytebuddy.description.annotation.AnnotationValue$Sort +net.bytebuddy.description.annotation.AnnotationValue$State +net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget$Factory +net.bytebuddy.implementation.Implementation$Target +net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget$OriginTypeResolver +net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget$OriginTypeResolver$1 +net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget$OriginTypeResolver$2 +net.bytebuddy.implementation.Implementation$Target$AbstractBase +net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget +net.bytebuddy.implementation.Implementation$SpecialMethodInvocation +net.bytebuddy.implementation.Implementation$Target$AbstractBase$DefaultMethodInvocation +net.bytebuddy.implementation.Implementation$Target$AbstractBase$DefaultMethodInvocation$1 +net.bytebuddy.implementation.Implementation$Target$AbstractBase$DefaultMethodInvocation$2 +net.bytebuddy.dynamic.scaffold.MethodRegistry$Handler$ForImplementation$Compiled +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record +net.bytebuddy.implementation.MethodCall$Appender +net.bytebuddy.implementation.MethodCall$TargetHandler$ForMethodCall +net.bytebuddy.implementation.MethodCall$MethodInvoker$ForContextualInvocation +net.bytebuddy.implementation.MethodCall$TargetHandler$ForSelfOrStaticInvocation +net.bytebuddy.dynamic.scaffold.MethodRegistry$Default$Compiled$Entry +net.bytebuddy.implementation.SuperMethodCall$Appender +net.bytebuddy.implementation.SuperMethodCall$Appender$TerminationHandler +net.bytebuddy.implementation.SuperMethodCall$Appender$TerminationHandler$1 +net.bytebuddy.implementation.SuperMethodCall$Appender$TerminationHandler$2 +net.bytebuddy.dynamic.scaffold.MethodRegistry$Default$Compiled +net.bytebuddy.dynamic.scaffold.FieldRegistry$Default$Compiled +net.bytebuddy.dynamic.scaffold.TypeWriter$FieldPool$Record +net.bytebuddy.dynamic.scaffold.RecordComponentRegistry$Default$Compiled +net.bytebuddy.dynamic.scaffold.TypeWriter$RecordComponentPool$Record +net.bytebuddy.pool.TypePool$Explicit +net.bytebuddy.pool.TypePool$CacheProvider$NoOp +net.bytebuddy.dynamic.scaffold.TypeWriter +net.bytebuddy.dynamic.scaffold.TypeWriter$Default +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ClassDumpAction$Dispatcher +net.bytebuddy.dynamic.scaffold.inline.MethodRebaseResolver +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForCreation +net.bytebuddy.utility.visitor.MetadataAwareClassVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForCreation$CreationClassVisitor +net.bytebuddy.utility.visitor.ContextClassVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForCreation$ImplementationContextClassVisitor +net.bytebuddy.dynamic.scaffold.TypeInitializer$Drain +net.bytebuddy.description.type.RecordComponentList$ForTokens +net.bytebuddy.description.type.RecordComponentDescription +net.bytebuddy.description.type.RecordComponentDescription$InDefinedShape +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ClassDumpAction$Dispatcher$Disabled +net.bytebuddy.utility.AsmClassWriter$Factory$Default$EmptyAsmClassReader +net.bytebuddy.utility.AsmClassWriter$ForAsm +net.bytebuddy.implementation.Implementation$Context$FrameGeneration +net.bytebuddy.implementation.Implementation$Context$FrameGeneration$1 +net.bytebuddy.implementation.Implementation$Context$FrameGeneration$2 +net.bytebuddy.implementation.Implementation$Context$FrameGeneration$3 +net.bytebuddy.implementation.Implementation$Context$ExtractableView$AbstractBase +net.bytebuddy.implementation.Implementation$Context$Default +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod +net.bytebuddy.implementation.Implementation$Context$Default$DelegationRecord +net.bytebuddy.implementation.Implementation$Context$Default$AccessorMethodDelegation +net.bytebuddy.implementation.Implementation$Context$Default$FieldGetterDelegation +net.bytebuddy.implementation.Implementation$Context$Default$FieldSetterDelegation +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor$Constraint +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor$ValidatingFieldVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor$ValidatingMethodVisitor +net.bytebuddy.jar.asm.signature.SignatureVisitor +net.bytebuddy.jar.asm.signature.SignatureWriter +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$ForSignatureVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor$Constraint$ForClassFileVersion +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor$Constraint$ForClass +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ValidatingClassVisitor$Constraint$Compound +net.bytebuddy.implementation.attribute.AnnotationAppender$Default +net.bytebuddy.implementation.attribute.AnnotationAppender$Target$OnType +net.bytebuddy.implementation.attribute.AnnotationAppender$ForTypeAnnotations +java.util.AbstractList$SubList +net.bytebuddy.jar.asm.TypeReference +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$AccessBridgeWrapper +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$Sort +net.bytebuddy.description.modifier.Visibility$1 +net.bytebuddy.description.type.TypeList$Generic$OfMethodExceptionTypes +net.bytebuddy.implementation.MethodCall$TargetHandler$ForSelfOrStaticInvocation$Resolved +net.bytebuddy.implementation.bytecode.Duplication +net.bytebuddy.implementation.MethodCall$TargetHandler$ForMethodCall$Resolved +net.bytebuddy.implementation.bytecode.ByteCodeAppender$Size +net.bytebuddy.implementation.bytecode.StackManipulation$Compound +net.bytebuddy.implementation.bytecode.member.MethodVariableAccess +net.bytebuddy.implementation.bytecode.member.MethodVariableAccess$MethodLoading$TypeCastingHandler +net.bytebuddy.implementation.bytecode.member.MethodVariableAccess$OffsetLoading +net.bytebuddy.implementation.bytecode.member.MethodInvocation +net.bytebuddy.implementation.bytecode.member.MethodInvocation$WithImplicitInvocationTargetType +net.bytebuddy.implementation.bytecode.member.MethodInvocation$Invocation +net.bytebuddy.implementation.bytecode.member.MethodReturn +net.bytebuddy.implementation.bytecode.StackManipulation$Size +net.bytebuddy.implementation.MethodCall$TargetHandler$ForMethodParameter$Resolved +net.bytebuddy.implementation.MethodCall$ArgumentLoader +net.bytebuddy.implementation.MethodCall$ArgumentLoader$ForMethodParameter +net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveWideningDelegate +net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveWideningDelegate$WideningStackManipulation +net.bytebuddy.description.type.TypeList$Generic$OfMethodExceptionTypes$TypeProjection +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedExecutableExceptionType +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedExecutableExceptionType$Dispatcher +net.bytebuddy.description.type.$Proxy65 +net.bytebuddy.matcher.SignatureTokenMatcher +net.bytebuddy.implementation.Implementation$SpecialMethodInvocation$AbstractBase +net.bytebuddy.implementation.Implementation$SpecialMethodInvocation$Simple +net.bytebuddy.implementation.bytecode.member.MethodVariableAccess$MethodLoading +net.bytebuddy.implementation.bytecode.member.MethodVariableAccess$MethodLoading$TypeCastingHandler$NoOp +net.bytebuddy.dynamic.scaffold.TypeInitializer$Drain$Default +net.bytebuddy.description.method.ParameterList$Empty +net.bytebuddy.description.type.TypeList$Generic$Empty +net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForNonImplementedMethod +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$UnresolvedType +net.bytebuddy.dynamic.DynamicType +net.bytebuddy.dynamic.DynamicType$Unloaded +net.bytebuddy.dynamic.DynamicType$AbstractBase +net.bytebuddy.dynamic.DynamicType$Default +net.bytebuddy.dynamic.DynamicType$Default$Unloaded +net.bytebuddy.dynamic.DynamicType$Loaded +net.bytebuddy.dynamic.loading.InjectionClassLoader$Strategy +net.bytebuddy.dynamic.DynamicType$Default$Loaded +java.lang.invoke.LambdaForm$MH/0x00007faab42dc000 +java.lang.invoke.MethodHandleImpl$WrappedMember +java.lang.invoke.MethodHandleImpl$CasesHolder +java.lang.invoke.MethodHandleImpl$LoopClauses +java.lang.invoke.MethodHandleImpl$ArrayAccess +java.lang.invoke.MethodHandleImpl$2 +java.lang.invoke.MethodHandleImpl$ArrayAccessor +java.lang.invoke.MethodHandleImpl$ArrayAccessor$1 +java.lang.invoke.LambdaForm$DMH/0x00007faab42dc400 +java.lang.invoke.LambdaForm$DMH/0x00007faab42dc800 +java.lang.invoke.LambdaForm$MH/0x00007faab42dcc00 +java.lang.invoke.LambdaForm$MH/0x00007faab42dd000 +java.lang.invoke.BoundMethodHandle$Species_LLL +java.lang.invoke.LambdaForm$MH/0x00007faab42dd400 +java.lang.invoke.LambdaForm$MH/0x00007faab42dd800 +net.bytebuddy.dynamic.loading.ByteArrayClassLoader$ClassDefinitionAction +net.bytebuddy.dynamic.loading.PackageDefinitionStrategy$Definition$Trivial +org.mockito.internal.util.reflection.InstrumentationMemberAccessor$Dispatcher$ByteBuddy$csVRoV61 +java.lang.invoke.LambdaForm$DMH/0x00007faab42de000 +org.mockito.internal.exceptions.stacktrace.DefaultStackTraceCleanerProvider +org.mockito.internal.configuration.InjectingAnnotationEngine +org.mockito.internal.configuration.IndependentAnnotationEngine +org.mockito.internal.configuration.FieldAnnotationProcessor +org.mockito.internal.configuration.MockAnnotationProcessor +org.mockito.Captor +org.mockito.internal.configuration.CaptorAnnotationProcessor +org.mockito.internal.configuration.SpyAnnotationEngine +org.mockito.internal.util.ConsoleMockitoLogger +org.mockito.plugins.MockResolver +org.mockito.plugins.DoNotMockEnforcer +org.mockito.internal.configuration.DefaultDoNotMockEnforcer +org.mockito.internal.creation.instance.DefaultInstantiatorProvider +org.mockito.internal.creation.instance.ObjenesisInstantiator +org.objenesis.Objenesis +org.objenesis.ObjenesisBase +org.objenesis.ObjenesisStd +org.objenesis.strategy.InstantiatorStrategy +org.objenesis.strategy.BaseInstantiatorStrategy +org.objenesis.strategy.StdInstantiatorStrategy +org.objenesis.instantiator.ObjectInstantiator +org.mockito.internal.util.Supplier +org.mockito.internal.configuration.MockAnnotationProcessor$$Lambda$664/0x00007faab42e0650 +org.mockito.ArgumentMatchers +org.mockito.Mockito +org.mockito.ArgumentMatcher +org.mockito.verification.VerificationMode +org.mockito.verification.VerificationAfterDelay +org.mockito.verification.VerificationWithTimeout +org.mockito.MockitoFramework +org.mockito.session.MockitoSessionBuilder +org.mockito.internal.MockitoCore +org.mockito.stubbing.BaseStubber +org.mockito.stubbing.LenientStubber +org.mockito.ScopedMock +org.mockito.MockedStatic +org.mockito.MockedConstruction +org.mockito.MockingDetails +org.mockito.exceptions.misusing.NotAMockException +org.mockito.internal.verification.api.VerificationData +org.mockito.stubbing.Stubber +org.mockito.InOrder +org.mockito.exceptions.misusing.DoNotMockException +org.mockito.internal.verification.api.VerificationDataInOrder +org.mockito.MockSettings +org.mockito.mock.MockCreationSettings +org.mockito.internal.creation.settings.CreationSettings +org.mockito.internal.creation.MockSettingsImpl +org.mockito.mock.MockName +org.mockito.mock.SerializableMode +org.mockito.internal.util.MockCreationValidator +org.mockito.internal.util.MockUtil +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker$1 +org.mockito.mock.MockType +org.mockito.internal.util.MockNameImpl +org.mockito.plugins.DoNotMockEnforcer$Cache +org.mockito.internal.handler.MockHandlerFactory +org.mockito.invocation.MockHandler +org.mockito.internal.handler.MockHandlerImpl +org.mockito.invocation.MatchableInvocation +org.mockito.stubbing.OngoingStubbing +org.mockito.stubbing.Stubbing +org.mockito.invocation.InvocationContainer +org.mockito.internal.invocation.MatchersBinder +org.mockito.internal.stubbing.InvocationContainerImpl +org.mockito.invocation.StubInfo +org.mockito.internal.verification.RegisteredInvocations +org.mockito.internal.verification.DefaultRegisteredInvocations +org.mockito.internal.stubbing.DoAnswerStyleStubbing +org.mockito.internal.handler.NullResultGuardian +org.mockito.internal.handler.InvocationNotifierHandler +org.mockito.listeners.MethodInvocationReport +org.mockito.internal.creation.bytebuddy.MockFeatures +net.bytebuddy.TypeCache$SimpleKey +org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator$MockitoMockKey +org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator$$Lambda$665/0x00007faab42e8000 +net.bytebuddy.TypeCache$LookupKey +org.mockito.internal.creation.bytebuddy.TypeSupport +org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$$Lambda$666/0x00007faab42e8650 +org.mockito.internal.util.concurrent.WeakConcurrentMap$WeakKey +org.mockito.internal.util.concurrent.WeakConcurrentMap$LatentKey +sun.instrument.InstrumentationImpl$$Lambda$667/0x00007faab41e0010 +sun.instrument.InstrumentationImpl$$Lambda$668/0x00007faab41e0248 +net.bytebuddy.dynamic.ClassFileLocator$Simple +net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder +net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder +net.bytebuddy.description.type.TypeList$Generic$OfLoadedInterfaceTypes$TypeProjection +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedInterface +net.bytebuddy.description.field.FieldList$ForLoadedFields +net.bytebuddy.utility.FieldComparator +com.networknt.schema.SpecVersion$VersionFlag +sun.reflect.annotation.TypeAnnotation$TypeAnnotationTarget +sun.reflect.annotation.TypeAnnotationParser +sun.reflect.annotation.TypeAnnotation +sun.reflect.annotation.TypeAnnotation$LocationInfo +sun.reflect.annotation.TypeAnnotation$LocationInfo$Location +sun.reflect.annotation.AnnotatedTypeFactory +sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$Simple +net.bytebuddy.description.type.TypeDescription$Generic$OfNonGenericType$Latent +java.lang.Class$EnclosingMethodInfo +net.bytebuddy.description.type.RecordComponentDescription$Token +net.bytebuddy.implementation.attribute.TypeAttributeAppender$ForInstrumentedType$Differentiating +net.bytebuddy.asm.AsmVisitorWrapper$AbstractBase +org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$ParameterWritingVisitorWrapper +org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$ParameterWritingVisitorWrapper$ParameterAddingClassVisitor +net.bytebuddy.asm.AsmVisitorWrapper$Compound +net.bytebuddy.pool.TypePool$Default +net.bytebuddy.pool.TypePool$Default$TypeExtractor +net.bytebuddy.pool.TypePool$Default$ReaderMode +net.bytebuddy.dynamic.scaffold.inline.InliningImplementationMatcher +net.bytebuddy.dynamic.scaffold.MethodGraph$Node$Simple +net.bytebuddy.dynamic.scaffold.MethodGraph$Simple +net.bytebuddy.dynamic.scaffold.MethodGraph$Empty +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$RegistryContextClassVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor +net.bytebuddy.jar.asm.commons.Remapper +net.bytebuddy.jar.asm.commons.SimpleRemapper +net.bytebuddy.jar.asm.commons.ClassRemapper +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$OpenedClassRemapper +net.bytebuddy.dynamic.scaffold.inline.MethodRebaseResolver$Disabled +net.bytebuddy.dynamic.scaffold.inline.MethodRebaseResolver$Resolution +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$ContextRegistry +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$InitializationHandler +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor$AttributeObtainingRecordComponentVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor$AttributeObtainingFieldVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor$AttributeObtainingMethodVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor$CodePreservingMethodVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor$DeduplicatingClassVisitor +net.bytebuddy.jar.asm.Context +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$InitializationHandler$Creating +net.bytebuddy.implementation.Implementation$Context$Disabled +org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$ParameterWritingVisitorWrapper$MethodParameterStrippingMethodVisitor +net.bytebuddy.dynamic.scaffold.TypeWriter$Default$SignatureKey +net.bytebuddy.matcher.DescriptorMatcher +software.amazon.lambda.powertools.validation.Validation +net.bytebuddy.description.method.ParameterDescription$Token +net.bytebuddy.description.type.TypeDescription$Generic$OfParameterizedType$ForLoadedType$ParameterArgumentTypeList +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$ForTypeArgument +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$ForTypeArgument$AnnotatedParameterizedType +java.lang.reflect.AnnotatedParameterizedType +net.bytebuddy.description.type.$Proxy66 +net.bytebuddy.description.type.TypeDescription$Generic$OfWildcardType +net.bytebuddy.description.type.TypeDescription$Generic$OfWildcardType$ForLoadedType +net.bytebuddy.description.type.TypeDescription$Generic$OfWildcardType$Latent +net.bytebuddy.description.type.TypeDescription$Generic$OfWildcardType$ForLoadedType$WildcardUpperBoundTypeList +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$ForWildcardUpperBoundType +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$ForWildcardUpperBoundType$AnnotatedWildcardType +java.lang.reflect.AnnotatedWildcardType +net.bytebuddy.description.type.$Proxy67 +net.bytebuddy.description.type.TypeDescription$Generic$OfWildcardType$ForLoadedType$WildcardLowerBoundTypeList +net.bytebuddy.description.type.TypeDescription$Generic$OfParameterizedType$Latent +net.bytebuddy.description.method.ParameterDescription$Latent +java.lang.annotation.Annotation +net.bytebuddy.dynamic.loading.MultipleParentClassLoader$Builder +net.bytebuddy.dynamic.loading.MultipleParentClassLoader +jdk.internal.reflect.GeneratedMethodAccessor3 +net.bytebuddy.matcher.LatentMatcher$Disjunction +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$OptionalMethodMatchAdapter +net.bytebuddy.description.modifier.SynchronizationState +net.bytebuddy.dynamic.Transformer$ForMethod +net.bytebuddy.dynamic.Transformer$ForMethod$MethodModifierTransformer +net.bytebuddy.dynamic.Transformer$Compound +net.bytebuddy.implementation.attribute.MethodAttributeAppender$ForInstrumentedMethod +net.bytebuddy.implementation.attribute.MethodAttributeAppender$ForInstrumentedMethod$1 +net.bytebuddy.implementation.attribute.MethodAttributeAppender$ForInstrumentedMethod$2 +net.bytebuddy.implementation.attribute.MethodAttributeAppender$Factory$Compound +net.bytebuddy.description.modifier.FieldManifestation +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition$Optional$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition$Optional$Valuable$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$FieldDefinition$Optional$Valuable$AbstractBase$Adapter +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$FieldDefinitionAdapter +net.bytebuddy.implementation.attribute.FieldAttributeAppender$Factory +net.bytebuddy.description.field.FieldDescription$Token +net.bytebuddy.implementation.attribute.FieldAttributeAppender +net.bytebuddy.implementation.attribute.FieldAttributeAppender$ForInstrumentedField +net.bytebuddy.matcher.LatentMatcher$ForFieldToken +net.bytebuddy.dynamic.scaffold.FieldRegistry$Default$Entry +net.bytebuddy.implementation.FieldAccessor +net.bytebuddy.implementation.FieldAccessor$FieldLocation +net.bytebuddy.implementation.FieldAccessor$PropertyConfigurable +net.bytebuddy.implementation.FieldAccessor$AssignerConfigurable +net.bytebuddy.implementation.FieldAccessor$OwnerTypeLocatable +net.bytebuddy.implementation.FieldAccessor$FieldNameExtractor +net.bytebuddy.implementation.FieldAccessor$FieldNameExtractor$ForBeanProperty +net.bytebuddy.implementation.FieldAccessor$FieldNameExtractor$ForBeanProperty$1 +net.bytebuddy.implementation.FieldAccessor$FieldNameExtractor$ForBeanProperty$2 +net.bytebuddy.implementation.FieldAccessor$ForImplicitProperty +net.bytebuddy.implementation.FieldAccessor$FieldLocation$Relative +net.bytebuddy.implementation.FieldAccessor$FieldLocation$Prepared +net.bytebuddy.dynamic.scaffold.FieldLocator$ForClassHierarchy$Factory +net.bytebuddy.matcher.SuperTypeMatcher +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$TypeVariableDefinition$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ExceptionDefinition$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Initial$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$MethodDefinitionAdapter +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$TypeVariableDefinition$Annotatable +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Annotatable +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Simple$Annotatable +net.bytebuddy.description.method.ParameterDescription$Token$TypeList +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Simple$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Simple$Annotatable$AbstractBase +net.bytebuddy.dynamic.DynamicType$Builder$MethodDefinition$ParameterDefinition$Simple$Annotatable$AbstractBase$Adapter +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$MethodDefinitionAdapter$SimpleParameterAnnotationAdapter +net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Adapter$MethodDefinitionAdapter$AnnotationAdapter +net.bytebuddy.dynamic.loading.ClassLoadingStrategy$UsingLookup +net.bytebuddy.dynamic.loading.ClassInjector +net.bytebuddy.dynamic.loading.ClassInjector$AbstractBase +net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup +net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup$MethodHandles +net.bytebuddy.dynamic.loading.$Proxy68 +net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup$MethodHandles$Lookup +jdk.proxy2.$Proxy69 +net.bytebuddy.utility.JavaType +net.bytebuddy.description.type.TypeDescription$Latent +net.bytebuddy.utility.JavaType$LatentTypeWithSimpleName +net.bytebuddy.matcher.LatentMatcher$ForMethodToken +net.bytebuddy.matcher.LatentMatcher$ForMethodToken$ResolvedMatcher +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Reducing +net.bytebuddy.dynamic.Transformer$ForMethod$TransformedMethod +jdk.internal.reflect.GeneratedMethodAccessor4 +net.bytebuddy.implementation.MethodDelegation$ImplementationDelegate$Compiled$ForStaticCall +net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodInvoker +net.bytebuddy.implementation.MethodDelegation$Appender +net.bytebuddy.implementation.bind.MethodDelegationBinder$Processor +net.bytebuddy.implementation.attribute.MethodAttributeAppender$Compound +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Substitutor$WithoutTypeSubstitution +net.bytebuddy.dynamic.Transformer$ForMethod$TransformedMethod$AttachmentVisitor +net.bytebuddy.dynamic.Transformer$ForMethod$TransformedMethod$TransformedParameterList +net.bytebuddy.implementation.FieldAccessor$ForImplicitProperty$Appender +net.bytebuddy.implementation.FieldAccessor$FieldLocation$Relative$Prepared +net.bytebuddy.dynamic.scaffold.FieldLocator$Resolution +net.bytebuddy.dynamic.scaffold.FieldRegistry$Default$Compiled$Entry +net.bytebuddy.matcher.LatentMatcher$ForFieldToken$ResolvedMatcher +net.bytebuddy.description.field.FieldDescription$SignatureToken +net.bytebuddy.description.type.TypeVariableToken +net.bytebuddy.implementation.attribute.AnnotationAppender$1 +net.bytebuddy.description.annotation.AnnotationValue$Loaded$AbstractBase +net.bytebuddy.description.annotation.AnnotationValue$ForEnumerationDescription$Loaded +net.bytebuddy.description.field.FieldDescription$AbstractBase +net.bytebuddy.description.field.FieldDescription$InDefinedShape$AbstractBase +net.bytebuddy.description.field.FieldDescription$Latent +net.bytebuddy.dynamic.scaffold.TypeWriter$FieldPool$Record$ForExplicitField +net.bytebuddy.implementation.attribute.AnnotationAppender$Target$OnField +net.bytebuddy.implementation.attribute.AnnotationAppender$Target$OnMethod +net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodInvoker$Simple +net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodBinding$Builder +net.bytebuddy.implementation.bind.MethodDelegationBinder$ParameterBinding$Anonymous +net.bytebuddy.description.type.TypeDefinition$SuperClassIterator +net.bytebuddy.description.field.FieldList$Explicit +net.bytebuddy.dynamic.scaffold.FieldLocator$Resolution$Simple +net.bytebuddy.implementation.bytecode.member.FieldAccess +net.bytebuddy.implementation.bytecode.member.FieldAccess$Defined +net.bytebuddy.implementation.bytecode.member.FieldAccess$AccessDispatcher +net.bytebuddy.implementation.bytecode.member.FieldAccess$AccessDispatcher$AbstractFieldInstruction +net.bytebuddy.implementation.bytecode.member.FieldAccess$AccessDispatcher$FieldGetInstruction +net.bytebuddy.implementation.bytecode.constant.MethodConstant +net.bytebuddy.implementation.bytecode.constant.MethodConstant$CanCache +net.bytebuddy.implementation.bytecode.constant.MethodConstant$ForMethod +net.bytebuddy.implementation.bytecode.constant.MethodConstant$CachedMethod +net.bytebuddy.implementation.bytecode.collection.CollectionFactory +net.bytebuddy.implementation.bytecode.collection.ArrayFactory +net.bytebuddy.implementation.bytecode.collection.ArrayFactory$ArrayCreator +net.bytebuddy.implementation.bytecode.collection.ArrayFactory$ArrayCreator$ForReferenceType +net.bytebuddy.implementation.bytecode.collection.ArrayFactory$ArrayStackManipulation +net.bytebuddy.implementation.auxiliary.MethodCallProxy$AssignableSignatureCall +net.bytebuddy.implementation.auxiliary.AuxiliaryType +net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodBinding$Builder$Build +net.bytebuddy.implementation.bytecode.constant.DefaultValue +net.bytebuddy.implementation.bytecode.constant.IntegerConstant +net.bytebuddy.implementation.bytecode.constant.LongConstant +net.bytebuddy.implementation.bytecode.constant.FloatConstant +net.bytebuddy.implementation.bytecode.constant.DoubleConstant +net.bytebuddy.implementation.bytecode.constant.NullConstant +net.bytebuddy.implementation.bind.MethodDelegationBinder$1 +net.bytebuddy.implementation.bind.MethodDelegationBinder$AmbiguityResolver$Resolution +net.bytebuddy.implementation.Implementation$Context$Default$FieldCacheEntry +net.bytebuddy.implementation.Implementation$Context$Default$CacheValueField +net.bytebuddy.implementation.auxiliary.MethodCallProxy +net.bytebuddy.implementation.MethodAccessorFactory$AccessType +net.bytebuddy.implementation.Implementation$Context$Default$AbstractPropertyAccessorMethod +net.bytebuddy.implementation.Implementation$Context$Default$AccessorMethod +net.bytebuddy.description.method.ParameterList$Explicit$ForTypes +net.bytebuddy.implementation.auxiliary.MethodCallProxy$PrecomputedMethodGraph +net.bytebuddy.implementation.auxiliary.MethodCallProxy$MethodCall +net.bytebuddy.implementation.auxiliary.MethodCallProxy$ConstructorCall +net.bytebuddy.implementation.auxiliary.MethodCallProxy$MethodCall$Appender +net.bytebuddy.implementation.auxiliary.MethodCallProxy$ConstructorCall$Appender +net.bytebuddy.implementation.bytecode.Removal +net.bytebuddy.implementation.bytecode.Removal$1 +net.bytebuddy.implementation.bytecode.Removal$2 +net.bytebuddy.implementation.attribute.AnnotationAppender$Target$OnMethodParameter +net.bytebuddy.implementation.bytecode.member.FieldAccess$AccessDispatcher$FieldPutInstruction +net.bytebuddy.implementation.bytecode.TypeCreation +net.bytebuddy.implementation.bytecode.Duplication$1 +net.bytebuddy.implementation.bytecode.Duplication$2 +net.bytebuddy.implementation.bytecode.Duplication$3 +net.bytebuddy.implementation.bind.ArgumentTypeResolver$ParameterIndexToken +net.bytebuddy.implementation.bind.MethodDelegationBinder$ParameterBinding$Unique +net.bytebuddy.implementation.bytecode.assign.TypeCasting +net.bytebuddy.description.type.TypeDescription$Generic$Visitor$ForSignatureVisitor$OfTypeArgument +sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedParameterizedTypeImpl +sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedWildcardTypeImpl +net.bytebuddy.dynamic.scaffold.MethodGraph$Node$Unresolved +net.bytebuddy.implementation.Implementation$SpecialMethodInvocation$Illegal +net.bytebuddy.implementation.bind.MethodDelegationBinder$ParameterBinding$Illegal +net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodBinding$Illegal +com.networknt.schema.SpecVersion +jdk.internal.reflect.GeneratedMethodAccessor5 +jdk.internal.reflect.GeneratedMethodAccessor6 +jdk.internal.reflect.GeneratedMethodAccessor7 +jdk.internal.reflect.GeneratedMethodAccessor8 +jdk.internal.reflect.GeneratedMethodAccessor9 +jdk.internal.reflect.GeneratedMethodAccessor10 +net.bytebuddy.dynamic.scaffold.FieldLocator$Resolution$Illegal +net.bytebuddy.implementation.bytecode.ByteCodeAppender$Simple +net.bytebuddy.dynamic.scaffold.TypeInitializer$Simple +net.bytebuddy.implementation.bytecode.ByteCodeAppender$Compound +net.bytebuddy.implementation.bytecode.constant.ClassConstant +net.bytebuddy.implementation.bytecode.constant.ClassConstant$ForReferenceType +net.bytebuddy.description.type.PackageDescription$ForLoadedPackage +software.amazon.lambda.powertools.validation.Validation$MockitoMock$r3AWkvyb +software.amazon.lambda.powertools.validation.Validation$MockitoMock$r3AWkvyb$auxiliary$a0EM2a2n +software.amazon.lambda.powertools.validation.Validation$MockitoMock$r3AWkvyb$auxiliary$RFyMI1P9 +net.bytebuddy.TypeCache$StorageKey +org.mockito.plugins.MemberAccessor$OnConstruction +org.mockito.plugins.MemberAccessor$ConstructionDispatcher +org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker$$Lambda$669/0x00007faab431f9a8 +java.lang.invoke.LambdaForm$MH/0x00007faab431d800 +java.lang.invoke.LambdaForm$MH/0x00007faab431dc00 +java.lang.invoke.LambdaForm$MH/0x00007faab4320000 +java.lang.invoke.LambdaForm$MH/0x00007faab4320400 +sun.invoke.util.ValueConversions$WrapperCache +java.lang.invoke.LambdaForm$MH/0x00007faab4320800 +java.lang.invoke.LambdaForm$MH/0x00007faab4320c00 +java.lang.invoke.BoundMethodHandle$Species_LLLL +java.lang.invoke.LambdaForm$MH/0x00007faab4321000 +java.lang.invoke.LambdaForm$MH/0x00007faab4321400 +java.lang.invoke.LambdaForm$MH/0x00007faab4321800 +org.mockito.internal.util.reflection.InstrumentationMemberAccessor$$Lambda$670/0x00007faab431fbd0 +org.mockito.invocation.Invocation +org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport +org.mockito.exceptions.base.MockitoSerializationIssue +org.mockito.internal.progress.ThreadSafeMockingProgress +org.mockito.internal.progress.ThreadSafeMockingProgress$1 +org.mockito.internal.progress.MockingProgress +org.mockito.internal.progress.MockingProgressImpl +org.mockito.internal.progress.ArgumentMatcherStorage +org.mockito.verification.VerificationStrategy +org.mockito.internal.progress.ArgumentMatcherStorageImpl +java.util.Stack +org.mockito.internal.progress.MockingProgressImpl$1 +java.lang.invoke.LambdaForm$MH/0x00007faab4321c00 +java.lang.invoke.LambdaForm$MH/0x00007faab4322000 +java.lang.invoke.LambdaForm$MH/0x00007faab4322400 +jdk.internal.reflect.GeneratedMethodAccessor11 +org.aspectj.lang.Signature +jdk.internal.reflect.GeneratedMethodAccessor12 +net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveUnboxingDelegate +net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveUnboxingDelegate$UnboxingResponsible +net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveUnboxingDelegate$ImplicitlyTypedUnboxingResponsible +net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveBoxingDelegate +net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveBoxingDelegate$BoxingStackManipulation +jdk.internal.reflect.GeneratedMethodAccessor13 +jdk.internal.reflect.GeneratedMethodAccessor14 +org.aspectj.lang.Signature$MockitoMock$F8wJJwod +org.aspectj.lang.Signature$MockitoMock$F8wJJwod$auxiliary$TN3rx6QT +org.aspectj.lang.Signature$MockitoMock$F8wJJwod$auxiliary$MUj6Pxo6 +com.amazonaws.services.lambda.runtime.LambdaLogger +com.amazonaws.services.lambda.runtime.CognitoIdentity +com.amazonaws.services.lambda.runtime.ClientContext +net.bytebuddy.jar.asm.Label +net.bytebuddy.utility.visitor.StackAwareMethodVisitor +net.bytebuddy.asm.Advice$ArgumentHandler$Factory +net.bytebuddy.asm.Advice$ArgumentHandler$Factory$1 +net.bytebuddy.asm.Advice$ArgumentHandler$Factory$2 +net.bytebuddy.asm.Advice$ArgumentHandler$ForInstrumentedMethod +net.bytebuddy.asm.Advice$ArgumentHandler$ForInstrumentedMethod$Default +net.bytebuddy.asm.Advice$ArgumentHandler$ForInstrumentedMethod$Default$Copying +net.bytebuddy.asm.Advice$ArgumentHandler$ForAdvice +net.bytebuddy.asm.Advice$MethodSizeHandler +net.bytebuddy.asm.Advice$MethodSizeHandler$ForInstrumentedMethod +net.bytebuddy.asm.Advice$MethodSizeHandler$Default +net.bytebuddy.asm.Advice$MethodSizeHandler$ForAdvice +net.bytebuddy.asm.Advice$MethodSizeHandler$Default$WithCopiedArguments +net.bytebuddy.asm.Advice$StackMapFrameHandler +net.bytebuddy.asm.Advice$StackMapFrameHandler$ForInstrumentedMethod +net.bytebuddy.asm.Advice$StackMapFrameHandler$Default +net.bytebuddy.asm.Advice$StackMapFrameHandler$ForPostProcessor +net.bytebuddy.asm.Advice$StackMapFrameHandler$ForAdvice +net.bytebuddy.asm.Advice$StackMapFrameHandler$Default$WithPreservedArguments +net.bytebuddy.asm.Advice$StackMapFrameHandler$Default$WithPreservedArguments$WithArgumentCopy +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$AdviceMethodInliner +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$AdviceMethodInliner$ExceptionTableExtractor +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$AdviceMethodInliner$ExceptionTableSubstitutor +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$ForValue$Bound +net.bytebuddy.asm.Advice$Dispatcher$RelocationHandler$Relocation$ForLabel +net.bytebuddy.asm.Advice$Dispatcher$Inlining$Resolved$AdviceMethodInliner$ExceptionTableCollector +java.util.TreeMap$EntrySet +java.util.TreeMap$EntryIterator +net.bytebuddy.asm.Advice$ArgumentHandler$ForAdvice$Default +net.bytebuddy.asm.Advice$ArgumentHandler$ForAdvice$Default$ForMethodEnter +net.bytebuddy.asm.Advice$MethodSizeHandler$Default$ForAdvice +net.bytebuddy.asm.Advice$StackMapFrameHandler$Default$ForAdvice +net.bytebuddy.asm.Advice$StackMapFrameHandler$Default$TranslationMode +net.bytebuddy.asm.Advice$StackMapFrameHandler$Default$TranslationMode$1 +net.bytebuddy.asm.Advice$StackMapFrameHandler$Default$TranslationMode$2 +net.bytebuddy.asm.Advice$StackMapFrameHandler$Default$TranslationMode$3 +net.bytebuddy.asm.Advice$StackMapFrameHandler$Default$Initialization +net.bytebuddy.asm.Advice$StackMapFrameHandler$Default$Initialization$1 +net.bytebuddy.asm.Advice$StackMapFrameHandler$Default$Initialization$2 +net.bytebuddy.asm.Advice$OffsetMapping$Sort +net.bytebuddy.asm.Advice$OffsetMapping$Sort$1 +net.bytebuddy.asm.Advice$OffsetMapping$Sort$2 +net.bytebuddy.asm.Advice$OffsetMapping$Target$ForStackManipulation +net.bytebuddy.asm.Advice$OffsetMapping$Target$ForVariable +net.bytebuddy.asm.Advice$OffsetMapping$Target$ForVariable$ReadOnly +net.bytebuddy.asm.Advice$OffsetMapping$Target$ForArray +net.bytebuddy.asm.Advice$OffsetMapping$Target$ForArray$ReadOnly +net.bytebuddy.jar.asm.Opcodes +net.bytebuddy.asm.Advice$ArgumentHandler$ForAdvice$Default$ForMethodExit +net.bytebuddy.asm.Advice$OffsetMapping$Target$ForVariable$ReadWrite +net.bytebuddy.implementation.bytecode.member.MethodVariableAccess$OffsetWriting +com.amazonaws.services.lambda.runtime.Context +jdk.internal.reflect.GeneratedMethodAccessor15 +com.amazonaws.services.lambda.runtime.Context$MockitoMock$MawJ5Xou +com.amazonaws.services.lambda.runtime.Context$MockitoMock$MawJ5Xou$auxiliary$QIVtpjYT +com.amazonaws.services.lambda.runtime.Context$MockitoMock$MawJ5Xou$auxiliary$xgUr4uvo +org.aspectj.runtime.internal.AroundClosure +net.bytebuddy.description.type.TypeDescription$Generic$OfGenericArray +net.bytebuddy.description.type.TypeDescription$Generic$OfGenericArray$Latent +jdk.internal.reflect.GeneratedMethodAccessor16 +jdk.internal.reflect.GeneratedMethodAccessor17 +net.bytebuddy.description.type.TypeDescription$ArrayProjection +net.bytebuddy.implementation.bytecode.StackSize$1 +org.aspectj.lang.ProceedingJoinPoint +net.bytebuddy.description.field.FieldDescription$ForLoadedField +net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$ForLoadedFieldType +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedField +net.bytebuddy.description.type.TypeDescription$Generic$AnnotationReader$Delegator$ForLoadedField$Dispatcher +net.bytebuddy.description.type.$Proxy70 +org.aspectj.lang.reflect.SourceLocation +org.aspectj.lang.JoinPoint$StaticPart +org.aspectj.lang.JoinPoint$EnclosingStaticPart +net.bytebuddy.dynamic.scaffold.TypeWriter$FieldPool$Record$ForImplicitField +org.aspectj.lang.JoinPoint +jdk.internal.reflect.GeneratedMethodAccessor18 +net.bytebuddy.dynamic.Transformer$ForMethod$TransformedMethod$TransformedParameter +sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedArrayTypeImpl +org.aspectj.lang.ProceedingJoinPoint$MockitoMock$alXgCaDh +org.aspectj.lang.ProceedingJoinPoint$MockitoMock$alXgCaDh$auxiliary$XCx5KE2h +org.aspectj.lang.ProceedingJoinPoint$MockitoMock$alXgCaDh$auxiliary$GAQiDCJf +java.lang.invoke.LambdaForm$DMH/0x00007faab4331000 +org.mockito.internal.configuration.IndependentAnnotationEngine$$Lambda$671/0x00007faab4337dc8 +org.mockito.Spy +org.mockito.plugins.AnnotationEngine$NoAction +org.mockito.internal.util.collections.Sets +org.mockito.internal.util.collections.HashCodeAndEqualsSafeSet +org.mockito.internal.configuration.injection.scanner.InjectMocksScanner +org.mockito.InjectMocks +org.mockito.internal.configuration.injection.scanner.MockScanner +org.mockito.internal.util.reflection.FieldReader +org.mockito.internal.util.collections.HashCodeAndEqualsMockWrapper +java.lang.invoke.LambdaForm$MH/0x00007faab4331400 +org.mockito.internal.util.Checks +org.mockito.internal.util.collections.HashCodeAndEqualsSafeSet$1 +org.mockito.internal.configuration.DefaultInjectionEngine +org.mockito.internal.configuration.injection.MockInjection +org.mockito.internal.configuration.injection.MockInjection$OngoingMockInjection +org.mockito.internal.configuration.injection.MockInjectionStrategy +org.mockito.internal.configuration.injection.ConstructorInjection +org.mockito.internal.configuration.injection.PropertyAndSetterInjection +org.mockito.internal.configuration.injection.SpyOnInjectedFieldsHandler +org.mockito.internal.configuration.injection.MockInjectionStrategy$1 +org.mockito.internal.util.reflection.FieldInitializer$ConstructorArgumentResolver +org.mockito.internal.configuration.injection.filter.MockCandidateFilter +org.mockito.internal.configuration.injection.filter.TypeBasedCandidateFilter +org.mockito.internal.configuration.injection.filter.NameBasedCandidateFilter +org.mockito.internal.configuration.injection.filter.TerminalMockCandidateFilter +org.mockito.internal.configuration.InjectingAnnotationEngine$$Lambda$672/0x00007faab4339998 +org.mockito.internal.configuration.InjectingAnnotationEngine$$Lambda$673/0x00007faab4339bc0 +org.junit.jupiter.api.extension.BeforeTestExecutionCallback +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$674/0x00007faab4339fe8 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$675/0x00007faab433a208 +org.junit.jupiter.engine.descriptor.MethodExtensionContext$$Lambda$676/0x00007faab433a430 +org.junit.jupiter.engine.execution.DefaultParameterContext +org.junit.jupiter.engine.execution.ParameterResolutionUtils$$Lambda$677/0x00007faab433a940 +org.junit.jupiter.params.ResolverFacade$$Lambda$678/0x00007faab433ab98 +org.junit.jupiter.params.ResolverFacade$$Lambda$679/0x00007faab433adb8 +java.lang.invoke.LambdaForm$DMH/0x00007faab433c000 +org.junit.jupiter.params.ResolverFacade$$Lambda$680/0x00007faab433afd8 +java.lang.invoke.LambdaForm$DMH/0x00007faab433c400 +java.lang.invoke.LambdaForm$DMH/0x00007faab433c800 +java.lang.invoke.LambdaForm$MH/0x00007faab433cc00 +org.junit.jupiter.params.ResolverFacade$$Lambda$681/0x00007faab433b200 +org.junit.jupiter.params.converter.ConvertWith +org.junit.jupiter.params.ResolverFacade$$Lambda$682/0x00007faab433b648 +org.junit.jupiter.params.converter.ArgumentConverter +org.junit.jupiter.params.ResolverFacade$$Lambda$683/0x00007faab433ba88 +org.junit.jupiter.params.ResolverFacade$$Lambda$684/0x00007faab433bcd0 +org.junit.jupiter.params.ResolverFacade$Converter +org.junit.jupiter.params.ResolverFacade$$Lambda$685/0x00007faab433e240 +org.junit.jupiter.params.ResolverFacade$$Lambda$686/0x00007faab433e480 +org.junit.jupiter.params.converter.DefaultArgumentConverter +org.junit.platform.commons.support.conversion.ConversionException +org.junit.jupiter.params.converter.ArgumentConversionException +org.junit.jupiter.params.converter.DefaultArgumentConverter$LocaleConversionFormat +org.junit.jupiter.params.converter.DefaultArgumentConverter$$Lambda$687/0x00007faab433efe8 +org.junit.jupiter.params.ResolverFacade$ExecutableParameterDeclaration$$Lambda$688/0x00007faab433f228 +org.junit.jupiter.params.ResolverFacade$ExecutableParameterDeclaration$$Lambda$689/0x00007faab433f480 +org.junit.jupiter.params.EvaluatedArgumentSet$$Lambda$690/0x00007faab433f6a8 +org.junit.jupiter.engine.execution.ParameterResolutionUtils$$Lambda$691/0x00007faab433f8e8 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$692/0x00007faab433fb10 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$693/0x00007faab433fd68 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$694/0x00007faab433d000 +software.amazon.lambda.powertools.validation.handlers.GenericSchemaV7StringHandler +software.amazon.lambda.powertools.validation.handlers.GenericSchemaV7StringHandler$AjcClosure1 +org.aspectj.runtime.reflect.Factory +org.aspectj.lang.reflect.MemberSignature +org.aspectj.lang.reflect.CodeSignature +org.aspectj.lang.reflect.MethodSignature +org.aspectj.lang.reflect.ConstructorSignature +org.aspectj.lang.reflect.FieldSignature +org.aspectj.lang.reflect.AdviceSignature +org.aspectj.lang.reflect.InitializerSignature +org.aspectj.lang.reflect.CatchClauseSignature +org.aspectj.lang.reflect.LockSignature +org.aspectj.lang.reflect.UnlockSignature +org.aspectj.runtime.reflect.SignatureImpl +org.aspectj.runtime.reflect.MemberSignatureImpl +org.aspectj.runtime.reflect.CodeSignatureImpl +org.aspectj.runtime.reflect.MethodSignatureImpl +org.aspectj.runtime.reflect.SignatureImpl$Cache +org.aspectj.runtime.reflect.JoinPointImpl$StaticPartImpl +org.aspectj.runtime.reflect.SourceLocationImpl +org.aspectj.runtime.reflect.JoinPointImpl +jdk.proxy2.$Proxy71 +software.amazon.lambda.powertools.validation.ValidationConfig +software.amazon.lambda.powertools.validation.ValidationConfig$ConfigHolder +com.networknt.schema.JsonSchemaFactory +com.networknt.schema.resource.SchemaLoader +com.networknt.schema.JsonSchemaException +com.networknt.schema.JsonSchemaVersion +com.networknt.schema.resource.SchemaLoaders +com.networknt.schema.resource.SchemaLoaders$Builder +com.networknt.schema.resource.SchemaMappers +com.networknt.schema.resource.SchemaMappers$Builder +com.networknt.schema.JsonSchemaFactory$1 +com.networknt.schema.Version7 +com.networknt.schema.Version7$Holder +com.networknt.schema.JsonMetaSchema +com.networknt.schema.JsonMetaSchema$Builder +com.networknt.schema.InvalidSchemaException +com.networknt.schema.Formats +com.networknt.schema.Format +com.networknt.schema.format.PatternFormat +java.util.regex.Pattern$Loop +java.util.regex.Pattern$Prolog +com.networknt.schema.format.IPv6Format +java.util.regex.CharPredicates$$Lambda$695/0x00007faab41e3c78 +java.util.regex.CharPredicates$$Lambda$696/0x00007faab41e3f00 +com.networknt.schema.format.DateFormat +com.networknt.schema.format.DateTimeFormat +com.networknt.schema.utils.Classes +com.ethlo.time.ITU +com.networknt.schema.format.DateTimeFormat$Ethlo +com.ethlo.time.LeapSecondException +com.networknt.schema.format.DateTimeFormat$$Lambda$697/0x00007faab4346870 +com.networknt.schema.format.EmailFormat +com.networknt.org.apache.commons.validator.routines.EmailValidator +com.networknt.schema.format.IPv6AwareEmailValidator +com.networknt.org.apache.commons.validator.routines.DomainValidator +com.networknt.org.apache.commons.validator.routines.DomainValidator$LazyHolder +com.networknt.org.apache.commons.validator.routines.RegexValidator +com.networknt.schema.format.IdnEmailFormat +com.networknt.schema.format.IdnHostnameFormat +com.networknt.schema.format.AbstractRFC3986Format +com.networknt.schema.format.IriFormat +com.networknt.schema.format.IriReferenceFormat +com.networknt.schema.format.RegexFormat +com.networknt.schema.format.TimeFormat +com.networknt.schema.format.UriFormat +com.networknt.schema.format.UriReferenceFormat +com.networknt.schema.format.DurationFormat +java.util.regex.Pattern$$Lambda$698/0x00007faab41e4380 +java.util.regex.Pattern$Bound +com.networknt.schema.Keyword +com.networknt.schema.ErrorMessageType +com.networknt.schema.ValidatorTypeCode +com.networknt.schema.ValidatorTypeCode$1 +com.networknt.schema.ValidatorFactory +com.networknt.schema.SchemaLocation +com.networknt.schema.JsonNodePath +com.networknt.schema.walk.JsonSchemaWalker +com.networknt.schema.JsonValidator +com.networknt.schema.ValidationMessageHandler +com.networknt.schema.BaseJsonValidator +com.networknt.schema.JsonSchema +com.networknt.schema.ValidationContext +com.networknt.schema.AdditionalPropertiesValidator +com.networknt.schema.FailFastAssertionException +com.networknt.schema.ValidatorTypeCode$$Lambda$699/0x00007faab434b900 +com.networknt.schema.VersionCode +com.networknt.schema.AllOfValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$700/0x00007faab434c288 +com.networknt.schema.AnyOfValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$701/0x00007faab434c7e0 +com.networknt.schema.ConstValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$702/0x00007faab434cd20 +com.networknt.schema.ContainsValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$703/0x00007faab434d260 +com.networknt.schema.ContentEncodingValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$704/0x00007faab434d7a0 +com.networknt.schema.ContentMediaTypeValidator +com.fasterxml.jackson.core.JacksonException +com.fasterxml.jackson.core.JsonProcessingException +com.networknt.schema.ValidatorTypeCode$$Lambda$705/0x00007faab434e1e0 +com.networknt.schema.DependenciesValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$706/0x00007faab434e720 +com.networknt.schema.DependentRequired +com.networknt.schema.ValidatorTypeCode$$Lambda$707/0x00007faab434ec60 +com.networknt.schema.DependentSchemas +com.networknt.schema.ValidatorTypeCode$$Lambda$708/0x00007faab434f1a8 +com.networknt.schema.DiscriminatorValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$709/0x00007faab434f6f8 +com.networknt.schema.DynamicRefValidator +com.networknt.schema.InvalidSchemaRefException +com.networknt.schema.ValidatorTypeCode$$Lambda$710/0x00007faab4350000 +com.networknt.schema.EnumValidator +com.fasterxml.jackson.databind.node.JsonNodeCreator +com.fasterxml.jackson.databind.node.BaseJsonNode +com.fasterxml.jackson.databind.node.ContainerNode +com.fasterxml.jackson.databind.node.ArrayNode +com.fasterxml.jackson.databind.node.ValueNode +com.fasterxml.jackson.databind.node.NumericNode +com.fasterxml.jackson.databind.node.DecimalNode +com.networknt.schema.ValidatorTypeCode$$Lambda$711/0x00007faab4353210 +com.networknt.schema.ExclusiveMaximumValidator +com.networknt.schema.ThresholdMixin +com.networknt.schema.ValidatorTypeCode$$Lambda$712/0x00007faab4353950 +com.networknt.schema.ExclusiveMinimumValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$713/0x00007faab4353e90 +com.networknt.schema.FalseValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$714/0x00007faab43543d0 +com.networknt.schema.IfValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$715/0x00007faab4354910 +com.networknt.schema.ItemsValidator202012 +com.networknt.schema.ValidatorTypeCode$$Lambda$716/0x00007faab4354e60 +com.networknt.schema.ItemsValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$717/0x00007faab43553b0 +com.networknt.schema.MinMaxContainsValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$718/0x00007faab43558f0 +com.networknt.schema.MaxItemsValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$719/0x00007faab4355e30 +com.networknt.schema.MaxLengthValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$720/0x00007faab4356370 +com.networknt.schema.MaxPropertiesValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$721/0x00007faab43568b0 +com.networknt.schema.MaximumValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$722/0x00007faab4356df0 +com.networknt.schema.ValidatorTypeCode$$Lambda$723/0x00007faab4357010 +com.networknt.schema.MinItemsValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$724/0x00007faab4357550 +com.networknt.schema.MinLengthValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$725/0x00007faab4357a90 +com.networknt.schema.MinPropertiesValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$726/0x00007faab4358000 +com.networknt.schema.MinimumValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$727/0x00007faab4358540 +com.networknt.schema.MultipleOfValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$728/0x00007faab4358a90 +com.networknt.schema.NotAllowedValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$729/0x00007faab4358fd0 +com.networknt.schema.NotValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$730/0x00007faab4359518 +com.networknt.schema.OneOfValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$731/0x00007faab4359a70 +com.networknt.schema.PatternPropertiesValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$732/0x00007faab4359fb0 +com.networknt.schema.PatternValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$733/0x00007faab435a4f0 +com.networknt.schema.PrefixItemsValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$734/0x00007faab435aa38 +com.networknt.schema.PropertiesValidator +com.fasterxml.jackson.databind.node.MissingNode +com.networknt.schema.ValidatorTypeCode$$Lambda$735/0x00007faab435b5c8 +com.networknt.schema.PropertyNamesValidator +com.fasterxml.jackson.databind.node.TextNode +com.networknt.schema.ValidatorTypeCode$$Lambda$736/0x00007faab435c158 +com.networknt.schema.ReadOnlyValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$737/0x00007faab435c698 +com.networknt.schema.RecursiveRefValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$738/0x00007faab435cbe0 +com.networknt.schema.RefValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$739/0x00007faab435d128 +com.networknt.schema.RequiredValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$740/0x00007faab435d668 +com.networknt.schema.TrueValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$741/0x00007faab435dba8 +com.networknt.schema.TypeValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$742/0x00007faab435e0f8 +com.networknt.schema.UnevaluatedItemsValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$743/0x00007faab435e638 +com.networknt.schema.UnevaluatedPropertiesValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$744/0x00007faab435eb78 +com.networknt.schema.UnionTypeValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$745/0x00007faab435f0b8 +com.networknt.schema.UniqueItemsValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$746/0x00007faab435f5f8 +com.networknt.schema.WriteOnlyValidator +com.networknt.schema.ValidatorTypeCode$$Lambda$747/0x00007faab435fb38 +com.networknt.schema.AbstractKeyword +com.networknt.schema.NonValidationKeyword +com.networknt.schema.AnnotationKeyword +com.networknt.schema.FormatKeyword +com.networknt.schema.utils.StringUtils +com.networknt.schema.JsonSchemaFactory$Builder +com.networknt.schema.resource.DefaultSchemaLoader +com.networknt.schema.resource.SchemaMapper +com.networknt.schema.resource.MetaSchemaMapper +com.networknt.schema.resource.ClasspathSchemaLoader +com.networknt.schema.resource.ClasspathSchemaLoader$$Lambda$748/0x00007faab4361c00 +com.networknt.schema.resource.UriSchemaLoader +software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor +software.amazon.lambda.powertools.common.internal.SystemWrapper +software.amazon.lambda.powertools.validation.ValidationUtils +software.amazon.lambda.powertools.validation.ValidationUtils$$Lambda$749/0x00007faab4362678 +com.networknt.schema.SchemaValidatorsConfig +com.networknt.schema.regex.RegularExpressionFactory +com.networknt.schema.walk.WalkListenerRunner +com.networknt.schema.SchemaValidatorsConfig$Builder +com.networknt.schema.SchemaValidatorsConfig$ImmutableSchemaValidatorsConfig +com.networknt.schema.ApplyDefaultsStrategy +com.networknt.schema.PathType +com.networknt.schema.PathType$$Lambda$750/0x00007faab4363d08 +com.networknt.schema.PathType$$Lambda$751/0x00007faab4363f38 +com.networknt.schema.PathType$$Lambda$752/0x00007faab4364168 +com.networknt.schema.PathType$$Lambda$753/0x00007faab4364398 +com.networknt.schema.PathType$$Lambda$754/0x00007faab43645c8 +com.networknt.schema.PathType$$Lambda$755/0x00007faab43647f8 +com.networknt.schema.PathType$$Lambda$756/0x00007faab4364a28 +com.networknt.schema.PathType$$Lambda$757/0x00007faab4364c58 +com.networknt.schema.regex.JDKRegularExpressionFactory +com.networknt.schema.regex.RegularExpression +com.networknt.schema.JsonSchemaIdValidator +com.networknt.schema.JsonSchemaIdValidator$DefaultJsonSchemaIdValidator +com.networknt.schema.walk.AbstractWalkListenerRunner +com.networknt.schema.walk.DefaultItemWalkListenerRunner +com.networknt.schema.walk.DefaultKeywordWalkListenerRunner +com.networknt.schema.walk.DefaultPropertyWalkListenerRunner +com.networknt.schema.AbsoluteIri +com.networknt.schema.resource.InputStreamSource +com.networknt.schema.resource.ClasspathSchemaLoader$$Lambda$758/0x00007faab4366670 +com.networknt.schema.InputFormat +com.networknt.schema.serialization.JsonMapperFactory +com.networknt.schema.serialization.JsonMapperFactory$Holder +com.fasterxml.jackson.core.Versioned +com.fasterxml.jackson.core.TreeCodec +com.fasterxml.jackson.core.ObjectCodec +com.fasterxml.jackson.databind.ObjectMapper +com.fasterxml.jackson.databind.json.JsonMapper +com.fasterxml.jackson.core.TokenStreamFactory +com.fasterxml.jackson.core.JsonFactory +com.fasterxml.jackson.databind.MappingJsonFactory +com.fasterxml.jackson.databind.jsontype.SubtypeResolver +com.fasterxml.jackson.databind.jsontype.impl.StdSubtypeResolver +com.fasterxml.jackson.databind.DatabindContext +com.fasterxml.jackson.databind.SerializerProvider +com.fasterxml.jackson.databind.ser.DefaultSerializerProvider +com.fasterxml.jackson.databind.ser.DefaultSerializerProvider$Impl +com.fasterxml.jackson.databind.deser.DeserializerFactory +com.fasterxml.jackson.databind.deser.BasicDeserializerFactory +com.fasterxml.jackson.databind.deser.BeanDeserializerFactory +com.fasterxml.jackson.databind.DeserializationContext +com.fasterxml.jackson.databind.deser.DefaultDeserializationContext +com.fasterxml.jackson.databind.deser.DefaultDeserializationContext$Impl +com.fasterxml.jackson.databind.ser.SerializerFactory +com.fasterxml.jackson.databind.ser.BasicSerializerFactory +com.fasterxml.jackson.databind.ser.BeanSerializerFactory +com.fasterxml.jackson.databind.AnnotationIntrospector +com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector +com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy$Provider +com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy$Provider +com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator +com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator$Base +com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator +com.fasterxml.jackson.databind.util.StdDateFormat +com.fasterxml.jackson.databind.DatabindException +com.fasterxml.jackson.databind.JsonMappingException +com.fasterxml.jackson.databind.node.NullNode +com.fasterxml.jackson.databind.Module$SetupContext +com.fasterxml.jackson.core.JsonGenerator +com.fasterxml.jackson.databind.util.TokenBuffer +com.fasterxml.jackson.databind.introspect.ClassIntrospector +com.fasterxml.jackson.databind.introspect.BasicClassIntrospector +com.fasterxml.jackson.databind.introspect.VisibilityChecker +com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder +com.fasterxml.jackson.core.JsonParser +com.fasterxml.jackson.core.base.ParserMinimalBase +com.fasterxml.jackson.databind.node.TreeTraversingParser +com.fasterxml.jackson.core.util.BufferRecycler$Gettable +com.fasterxml.jackson.core.io.SegmentedStringWriter +com.fasterxml.jackson.core.util.ByteArrayBuilder +com.fasterxml.jackson.core.type.ResolvedType +com.fasterxml.jackson.databind.JavaType +com.fasterxml.jackson.databind.type.TypeBase +com.fasterxml.jackson.databind.type.ArrayType +com.fasterxml.jackson.databind.type.CollectionLikeType +com.fasterxml.jackson.databind.type.CollectionType +com.fasterxml.jackson.databind.type.MapLikeType +com.fasterxml.jackson.databind.type.MapType +com.fasterxml.jackson.databind.exc.MismatchedInputException +com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair +com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector +com.fasterxml.jackson.databind.introspect.Annotated +com.fasterxml.jackson.databind.introspect.TypeResolutionContext +com.fasterxml.jackson.databind.introspect.AnnotatedClass +com.fasterxml.jackson.databind.introspect.AnnotatedMember +com.fasterxml.jackson.databind.introspect.VirtualAnnotatedMember +com.fasterxml.jackson.databind.util.Named +com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition +com.fasterxml.jackson.databind.util.SimpleBeanPropertyDefinition +com.fasterxml.jackson.databind.BeanProperty +com.fasterxml.jackson.databind.introspect.ConcreteBeanPropertyBase +com.fasterxml.jackson.databind.ser.PropertyWriter +com.fasterxml.jackson.databind.ser.BeanPropertyWriter +com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter +com.fasterxml.jackson.databind.ser.impl.AttributePropertyWriter +com.fasterxml.jackson.databind.introspect.AnnotatedWithParams +com.fasterxml.jackson.databind.introspect.AnnotatedMethod +com.fasterxml.jackson.databind.annotation.JsonSerialize +com.fasterxml.jackson.annotation.JsonView +com.fasterxml.jackson.annotation.JsonFormat +com.fasterxml.jackson.annotation.JsonTypeInfo +com.fasterxml.jackson.annotation.JsonRawValue +com.fasterxml.jackson.annotation.JsonUnwrapped +com.fasterxml.jackson.annotation.JsonBackReference +com.fasterxml.jackson.annotation.JsonManagedReference +com.fasterxml.jackson.databind.annotation.JsonDeserialize +com.fasterxml.jackson.annotation.JsonMerge +com.fasterxml.jackson.databind.ext.Java7Support +java.lang.IllegalAccessError +com.fasterxml.jackson.databind.ext.Java7SupportImpl +com.fasterxml.jackson.databind.util.ClassUtil +com.fasterxml.jackson.databind.util.ClassUtil$Ctor +com.fasterxml.jackson.databind.util.LookupCache +com.fasterxml.jackson.databind.util.LRUMap +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$Builder +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap +com.fasterxml.jackson.databind.util.internal.Linked +com.fasterxml.jackson.databind.util.internal.LinkedDeque +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$1 +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$2 +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$DrainStatus$3 +com.fasterxml.jackson.databind.cfg.BaseSettings +com.fasterxml.jackson.databind.type.TypeFactory +com.fasterxml.jackson.databind.type.SimpleType +com.fasterxml.jackson.databind.type.IdentityEqualityType +com.fasterxml.jackson.databind.type.ResolvedRecursiveType +com.fasterxml.jackson.databind.type.PlaceholderForType +com.fasterxml.jackson.databind.type.ReferenceType +com.fasterxml.jackson.databind.type.IterationType +com.fasterxml.jackson.databind.type.TypeParser +com.fasterxml.jackson.databind.type.TypeBindings +com.fasterxml.jackson.core.Base64Variants +com.fasterxml.jackson.core.Base64Variant +com.fasterxml.jackson.core.Base64Variant$PaddingReadBehaviour +com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy +com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy +com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy$RecordNaming +com.fasterxml.jackson.databind.cfg.CacheProvider +com.fasterxml.jackson.databind.cfg.DefaultCacheProvider +com.fasterxml.jackson.databind.cfg.MapperBuilder +com.fasterxml.jackson.databind.json.JsonMapper$Builder +com.fasterxml.jackson.core.io.DataOutputAsStream +com.fasterxml.jackson.core.SerializableString +com.fasterxml.jackson.core.TSFBuilder +com.fasterxml.jackson.core.JsonFactoryBuilder +com.fasterxml.jackson.core.base.ParserBase +com.fasterxml.jackson.core.json.JsonParserBase +com.fasterxml.jackson.core.json.UTF8DataInputJsonParser +com.fasterxml.jackson.core.json.ReaderBasedJsonParser +com.fasterxml.jackson.core.async.NonBlockingInputFeeder +com.fasterxml.jackson.core.async.ByteArrayFeeder +com.fasterxml.jackson.core.json.async.NonBlockingJsonParserBase +com.fasterxml.jackson.core.json.async.NonBlockingUtf8JsonParserBase +com.fasterxml.jackson.core.json.async.NonBlockingJsonParser +com.fasterxml.jackson.core.async.ByteBufferFeeder +com.fasterxml.jackson.core.json.async.NonBlockingByteBufferJsonParser +com.fasterxml.jackson.core.base.GeneratorBase +com.fasterxml.jackson.core.json.JsonGeneratorImpl +com.fasterxml.jackson.core.json.UTF8JsonGenerator +com.fasterxml.jackson.core.io.UTF8Writer +com.fasterxml.jackson.core.json.WriterBasedJsonGenerator +com.fasterxml.jackson.core.util.JacksonFeature +com.fasterxml.jackson.core.JsonFactory$Feature +com.fasterxml.jackson.core.JsonParser$Feature +com.fasterxml.jackson.core.JsonGenerator$Feature +com.fasterxml.jackson.core.io.SerializedString +com.fasterxml.jackson.core.io.JsonStringEncoder +com.fasterxml.jackson.core.io.CharTypes +com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer +com.fasterxml.jackson.core.exc.StreamConstraintsException +com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer$TableInfo +com.fasterxml.jackson.core.util.JsonRecyclerPools +com.fasterxml.jackson.core.util.RecyclerPool +com.fasterxml.jackson.core.util.RecyclerPool$ThreadLocalPoolBase +com.fasterxml.jackson.core.util.JsonRecyclerPools$ThreadLocalPool +com.fasterxml.jackson.core.util.RecyclerPool$WithPool +com.fasterxml.jackson.core.StreamReadConstraints +com.fasterxml.jackson.core.StreamWriteConstraints +com.fasterxml.jackson.core.ErrorReportConfiguration +com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer +com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer$TableInfo +com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer$Bucket +com.fasterxml.jackson.databind.util.RootNameLookup +com.fasterxml.jackson.databind.introspect.ClassIntrospector$MixInResolver +com.fasterxml.jackson.databind.introspect.SimpleMixInResolver +com.fasterxml.jackson.databind.BeanDescription +com.fasterxml.jackson.databind.introspect.BasicBeanDescription +com.fasterxml.jackson.databind.cfg.MapperConfig +com.fasterxml.jackson.databind.cfg.MapperConfigBase +com.fasterxml.jackson.databind.DeserializationConfig +com.fasterxml.jackson.databind.SerializationConfig +com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver +com.fasterxml.jackson.databind.introspect.AnnotationCollector +com.fasterxml.jackson.databind.util.Annotations +com.fasterxml.jackson.databind.introspect.AnnotationCollector$EmptyCollector +com.fasterxml.jackson.databind.introspect.AnnotationCollector$NoAnnotations +com.fasterxml.jackson.databind.introspect.AnnotatedClass$Creators +com.fasterxml.jackson.databind.introspect.AnnotatedConstructor +com.fasterxml.jackson.databind.cfg.ConfigOverrides +com.fasterxml.jackson.annotation.JacksonAnnotationValue +com.fasterxml.jackson.annotation.JsonInclude$Value +com.fasterxml.jackson.annotation.JsonInclude$Include +com.fasterxml.jackson.annotation.JsonSetter$Value +com.fasterxml.jackson.annotation.Nulls +com.fasterxml.jackson.databind.introspect.VisibilityChecker$Std +com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility +com.fasterxml.jackson.databind.cfg.CoercionConfigs +com.fasterxml.jackson.databind.type.LogicalType +com.fasterxml.jackson.databind.cfg.CoercionAction +com.fasterxml.jackson.databind.cfg.CoercionConfig +com.fasterxml.jackson.databind.cfg.MutableCoercionConfig +com.fasterxml.jackson.databind.cfg.CoercionInputShape +com.fasterxml.jackson.databind.jsontype.DefaultBaseTypeLimitingValidator +com.fasterxml.jackson.core.PrettyPrinter +com.fasterxml.jackson.annotation.JsonFormat$Value +com.fasterxml.jackson.annotation.JsonFormat$Shape +com.fasterxml.jackson.annotation.JsonFormat$Features +com.fasterxml.jackson.databind.cfg.ConfigOverride +com.fasterxml.jackson.databind.cfg.ConfigOverride$Empty +com.fasterxml.jackson.databind.cfg.ConfigFeature +com.fasterxml.jackson.databind.MapperFeature +com.fasterxml.jackson.core.util.Instantiatable +com.fasterxml.jackson.core.util.DefaultPrettyPrinter +com.fasterxml.jackson.core.util.DefaultPrettyPrinter$Indenter +com.fasterxml.jackson.core.util.Separators +com.fasterxml.jackson.core.util.Separators$Spacing +com.fasterxml.jackson.core.util.DefaultPrettyPrinter$NopIndenter +com.fasterxml.jackson.core.util.DefaultPrettyPrinter$FixedSpaceIndenter +com.fasterxml.jackson.core.util.DefaultIndenter +com.fasterxml.jackson.databind.SerializationFeature +com.fasterxml.jackson.databind.cfg.DatatypeFeatures +com.fasterxml.jackson.databind.cfg.DatatypeFeatures$DefaultHolder +com.fasterxml.jackson.databind.cfg.DatatypeFeature +com.fasterxml.jackson.databind.cfg.EnumFeature +com.fasterxml.jackson.databind.cfg.JsonNodeFeature +com.fasterxml.jackson.databind.cfg.ContextAttributes +com.fasterxml.jackson.databind.cfg.ContextAttributes$Impl +com.fasterxml.jackson.databind.DeserializationFeature +com.fasterxml.jackson.databind.node.JsonNodeFactory +com.fasterxml.jackson.databind.node.BooleanNode +com.fasterxml.jackson.databind.node.DoubleNode +com.fasterxml.jackson.databind.node.ShortNode +com.fasterxml.jackson.databind.node.IntNode +com.fasterxml.jackson.databind.node.LongNode +com.fasterxml.jackson.databind.node.FloatNode +com.fasterxml.jackson.databind.node.BigIntegerNode +com.fasterxml.jackson.databind.node.BinaryNode +com.fasterxml.jackson.databind.node.POJONode +com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable +com.fasterxml.jackson.databind.JsonSerializer +com.fasterxml.jackson.databind.jsonschema.SchemaAware +com.fasterxml.jackson.databind.ser.std.StdSerializer +com.fasterxml.jackson.databind.ser.std.NullSerializer +com.fasterxml.jackson.databind.ser.impl.FailingSerializer +com.fasterxml.jackson.databind.ser.std.ToEmptyObjectSerializer +com.fasterxml.jackson.databind.ser.impl.UnknownSerializer +com.fasterxml.jackson.databind.ser.ContextualSerializer +com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer +com.fasterxml.jackson.databind.exc.InvalidDefinitionException +com.fasterxml.jackson.databind.exc.InvalidTypeIdException +com.fasterxml.jackson.databind.node.ObjectNode +com.fasterxml.jackson.databind.ser.ResolvableSerializer +com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer +com.fasterxml.jackson.databind.ser.SerializerCache +com.fasterxml.jackson.databind.deser.NullValueProvider +com.fasterxml.jackson.databind.JsonDeserializer +com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer +com.fasterxml.jackson.databind.exc.PropertyBindingException +com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException +com.fasterxml.jackson.databind.exc.InvalidFormatException +com.fasterxml.jackson.databind.exc.ValueInstantiationException +com.fasterxml.jackson.databind.deser.UnresolvedForwardReference +com.fasterxml.jackson.databind.deser.ContextualDeserializer +com.fasterxml.jackson.databind.deser.ValueInstantiator$Gettable +com.fasterxml.jackson.databind.deser.std.StdDeserializer +com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer +com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase +com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer +com.fasterxml.jackson.databind.deser.std.EnumSetDeserializer +com.fasterxml.jackson.databind.deser.std.CollectionDeserializer +com.fasterxml.jackson.databind.deser.std.ArrayBlockingQueueDeserializer +com.fasterxml.jackson.databind.deser.std.StringCollectionDeserializer +com.fasterxml.jackson.databind.deser.ResolvableDeserializer +com.fasterxml.jackson.databind.deser.std.EnumMapDeserializer +com.fasterxml.jackson.databind.deser.std.MapDeserializer +com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer +com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer +com.fasterxml.jackson.databind.deser.std.StringDeserializer +com.fasterxml.jackson.databind.deser.std.MapEntryDeserializer +com.fasterxml.jackson.databind.deser.std.TokenBufferDeserializer +com.fasterxml.jackson.databind.deser.AbstractDeserializer +com.fasterxml.jackson.databind.deser.std.EnumDeserializer +com.fasterxml.jackson.databind.deser.std.ReferenceTypeDeserializer +com.fasterxml.jackson.databind.deser.std.AtomicReferenceDeserializer +com.fasterxml.jackson.databind.introspect.AnnotatedParameter +com.fasterxml.jackson.databind.deser.SettableBeanProperty +com.fasterxml.jackson.databind.deser.CreatorProperty +com.fasterxml.jackson.databind.deser.impl.ErrorThrowingDeserializer +com.fasterxml.jackson.annotation.ObjectIdGenerator +com.fasterxml.jackson.annotation.ObjectIdGenerators$Base +com.fasterxml.jackson.annotation.ObjectIdGenerators$PropertyGenerator +com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator +com.fasterxml.jackson.databind.deser.impl.MethodProperty +com.fasterxml.jackson.databind.deser.impl.FieldProperty +com.fasterxml.jackson.databind.deser.impl.SetterlessProperty +com.fasterxml.jackson.databind.deser.BeanDeserializerBase +com.fasterxml.jackson.databind.deser.BeanDeserializer +com.fasterxml.jackson.databind.deser.std.ThrowableDeserializer +com.fasterxml.jackson.databind.deser.impl.UnsupportedTypeDeserializer +com.fasterxml.jackson.databind.deser.Deserializers +com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig +com.fasterxml.jackson.databind.deser.BeanDeserializerModifier +com.fasterxml.jackson.databind.AbstractTypeResolver +com.fasterxml.jackson.databind.deser.ValueInstantiators +com.fasterxml.jackson.databind.deser.KeyDeserializers +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializers +com.fasterxml.jackson.databind.KeyDeserializer +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$DelegatingKD +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$EnumKD +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringCtorKeyDeserializer +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringFactoryKeyDeserializer +com.fasterxml.jackson.databind.deser.DeserializerCache +com.fasterxml.jackson.databind.deser.std.StdDelegatingDeserializer +com.fasterxml.jackson.databind.ser.std.JsonValueSerializer +com.fasterxml.jackson.databind.ser.std.ToStringSerializerBase +com.fasterxml.jackson.databind.ser.std.ToStringSerializer +com.fasterxml.jackson.databind.ser.std.StdScalarSerializer +com.fasterxml.jackson.databind.ser.std.EnumSerializer +com.fasterxml.jackson.databind.ser.ContainerSerializer +com.fasterxml.jackson.databind.ser.impl.MapEntrySerializer +com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase +com.fasterxml.jackson.databind.ser.impl.IteratorSerializer +com.fasterxml.jackson.databind.ser.std.IterableSerializer +com.fasterxml.jackson.databind.ser.std.MapSerializer +com.fasterxml.jackson.databind.ser.std.StaticListSerializerBase +com.fasterxml.jackson.databind.ser.impl.IndexedStringListSerializer +com.fasterxml.jackson.databind.ser.impl.StringCollectionSerializer +com.fasterxml.jackson.databind.ser.std.CollectionSerializer +com.fasterxml.jackson.databind.ser.std.ArraySerializerBase +com.fasterxml.jackson.databind.ser.impl.StringArraySerializer +com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer +com.fasterxml.jackson.databind.ser.std.EnumSetSerializer +com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer +com.fasterxml.jackson.databind.ser.std.ReferenceTypeSerializer +com.fasterxml.jackson.databind.ser.std.SerializableSerializer +com.fasterxml.jackson.databind.ser.std.DateTimeSerializerBase +com.fasterxml.jackson.databind.ser.std.CalendarSerializer +com.fasterxml.jackson.databind.ser.std.DateSerializer +com.fasterxml.jackson.databind.ser.std.ByteBufferSerializer +com.fasterxml.jackson.databind.ser.std.InetAddressSerializer +com.fasterxml.jackson.databind.ser.std.InetSocketAddressSerializer +com.fasterxml.jackson.databind.ser.std.TimeZoneSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializer +com.fasterxml.jackson.databind.ser.impl.MapEntryAsPOJOSerializer +com.fasterxml.jackson.databind.ser.std.BeanSerializerBase +com.fasterxml.jackson.databind.ser.BeanSerializer +com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer +com.fasterxml.jackson.databind.introspect.AnnotatedField +com.fasterxml.jackson.databind.ser.impl.PropertyBasedObjectIdGenerator +com.fasterxml.jackson.databind.ser.std.StringSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers +com.fasterxml.jackson.databind.ser.std.NumberSerializers$Base +com.fasterxml.jackson.databind.ser.std.NumberSerializers$IntegerSerializer +com.fasterxml.jackson.core.JsonParser$NumberType +com.fasterxml.jackson.databind.ser.std.NumberSerializers$LongSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$IntLikeSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$ShortSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$DoubleSerializer +com.fasterxml.jackson.databind.ser.std.NumberSerializers$FloatSerializer +com.fasterxml.jackson.databind.ser.std.BooleanSerializer +com.fasterxml.jackson.databind.ser.std.BooleanSerializer$AsNumber +com.fasterxml.jackson.databind.ser.std.NumberSerializer$BigDecimalAsStringSerializer +com.fasterxml.jackson.databind.ser.std.StdJdkSerializers +com.fasterxml.jackson.databind.ser.std.UUIDSerializer +com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicBooleanSerializer +com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicIntegerSerializer +com.fasterxml.jackson.databind.ser.std.StdJdkSerializers$AtomicLongSerializer +com.fasterxml.jackson.databind.ser.std.FileSerializer +com.fasterxml.jackson.databind.ser.std.ClassSerializer +com.fasterxml.jackson.databind.ser.std.TokenBufferSerializer +com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig +com.fasterxml.jackson.databind.ser.Serializers +com.fasterxml.jackson.databind.ser.BeanSerializerModifier +com.fasterxml.jackson.core.io.ContentReference +com.fasterxml.jackson.core.util.BufferRecyclers +com.fasterxml.jackson.core.util.BufferRecycler +com.fasterxml.jackson.core.io.IOContext +com.fasterxml.jackson.core.util.TextBuffer +com.fasterxml.jackson.core.util.ReadConstrainedTextBuffer +com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper +com.fasterxml.jackson.core.json.UTF8StreamJsonParser +com.fasterxml.jackson.core.io.MergedStream +com.fasterxml.jackson.core.io.UTF32Reader +com.fasterxml.jackson.core.JsonEncoding +com.fasterxml.jackson.core.util.InternCache +com.fasterxml.jackson.core.exc.StreamReadException +com.fasterxml.jackson.core.exc.InputCoercionException +com.fasterxml.jackson.core.JsonParseException +com.fasterxml.jackson.core.io.JsonEOFException +com.fasterxml.jackson.core.JsonStreamContext +com.fasterxml.jackson.core.json.JsonReadContext +com.fasterxml.jackson.core.StreamReadCapability +com.fasterxml.jackson.core.util.JacksonFeatureSet +com.fasterxml.jackson.core.JsonToken +com.fasterxml.jackson.databind.util.ArrayIterator +com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer +com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer +com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer$ObjectDeserializer +com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer$ArrayDeserializer +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$WeightedValue +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$Node +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$AddTask +com.fasterxml.jackson.databind.util.LinkedNode +com.fasterxml.jackson.databind.annotation.JsonTypeResolver +com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer$ContainerStack +com.fasterxml.jackson.core.io.NumberInput +com.fasterxml.jackson.core.JsonParser$NumberTypeFP +com.fasterxml.jackson.core.StreamReadFeature +com.fasterxml.jackson.databind.node.JsonNodeType +com.networknt.schema.JsonSchemaFactory$$Lambda$759/0x00007faab43c1cc0 +com.networknt.schema.JsonSchemaFactory$$Lambda$760/0x00007faab43c1f08 +com.networknt.schema.OutputFormat +java.lang.invoke.LambdaForm$DMH/0x00007faab43c4000 +com.networknt.schema.JsonSchema$$Lambda$761/0x00007faab43c2350 +com.networknt.schema.i18n.DefaultMessageSource +com.networknt.schema.i18n.DefaultMessageSource$Holder +com.networknt.schema.i18n.MessageSource +com.networknt.schema.i18n.ResourceBundleMessageSource +java.util.MissingResourceException +com.networknt.schema.AbstractJsonValidator +com.networknt.schema.NonValidationKeyword$Validator +com.networknt.schema.TypeFactory +com.networknt.schema.JsonType +com.networknt.schema.AnnotationKeyword$Validator +com.networknt.schema.SchemaLocation$Fragment +com.fasterxml.jackson.core.io.NumberOutput +com.networknt.schema.ExclusiveMinimumValidator$2 +com.networknt.schema.JsonSchemaRef +com.networknt.schema.RefValidator$$Lambda$762/0x00007faab43c6660 +com.networknt.schema.CachedSupplier +com.networknt.schema.JsonSchema$$Lambda$763/0x00007faab43c6ab8 +com.networknt.schema.MinimumValidator$1 +com.networknt.schema.RefValidator$$Lambda$764/0x00007faab43c6f40 +com.fasterxml.jackson.databind.node.InternalNodeMapper +com.fasterxml.jackson.databind.ObjectWriter +com.fasterxml.jackson.core.util.MinimalPrettyPrinter +com.fasterxml.jackson.databind.ObjectWriter$GeneratorSettings +com.fasterxml.jackson.databind.ObjectWriter$Prefetch +com.fasterxml.jackson.databind.RuntimeJsonMappingException +com.fasterxml.jackson.databind.ObjectReader +com.fasterxml.jackson.core.filter.TokenFilter +com.fasterxml.jackson.core.filter.JsonPointerBasedFilter +com.fasterxml.jackson.core.util.JsonParserDelegate +com.fasterxml.jackson.core.filter.FilteringParserDelegate +com.fasterxml.jackson.databind.node.InternalNodeMapper$WrapperForSerializer +com.fasterxml.jackson.core.exc.StreamWriteException +com.fasterxml.jackson.core.JsonGenerationException +com.fasterxml.jackson.core.json.JsonWriteContext +com.fasterxml.jackson.core.StreamWriteCapability +com.fasterxml.jackson.core.FormatFeature +com.fasterxml.jackson.core.json.JsonWriteFeature +com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap +com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap$Bucket +com.fasterxml.jackson.databind.util.TypeKey +com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap$$Lambda$765/0x00007faab43ca5c0 +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$EntrySet +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$EntryIterator +com.fasterxml.jackson.databind.type.ClassStack +com.fasterxml.jackson.databind.introspect.AnnotationCollector$OneCollector +com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector +com.fasterxml.jackson.annotation.JsonAutoDetect +com.fasterxml.jackson.annotation.JsonIdentityInfo +com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$WriteThroughEntry +com.networknt.schema.format.BaseFormatJsonValidator +com.networknt.schema.FormatValidator +java.util.regex.PatternSyntaxException +com.networknt.schema.OutputFormat$Default +com.networknt.schema.OutputFormat$Boolean +com.networknt.schema.OutputFormat$Flag +com.networknt.schema.OutputFormat$List +com.networknt.schema.output.OutputUnitData +com.networknt.schema.ValidationMessage +com.networknt.schema.OutputFormat$List$$Lambda$766/0x00007faab43cd088 +com.networknt.schema.OutputFormat$Hierarchical +com.networknt.schema.OutputFormat$Hierarchical$$Lambda$767/0x00007faab43cd510 +com.networknt.schema.OutputFormat$Result +com.networknt.schema.ExecutionConfig +com.networknt.schema.ExecutionConfig$$Lambda$768/0x00007faab43cdc00 +com.networknt.schema.ExecutionContext +com.networknt.schema.BaseJsonValidator$JsonNodePathJsonPointer +com.networknt.schema.utils.JsonNodeUtil +com.networknt.schema.TypeFactory$1 +com.networknt.schema.ValidationMessage$BuilderSupport +com.networknt.schema.MessageSourceValidationMessage$BuilderSupport +com.networknt.schema.MessageSourceValidationMessage$Builder +com.networknt.schema.FormatValidator$$Lambda$769/0x00007faab43cf038 +com.networknt.schema.format.UriReferenceFormat$$Lambda$770/0x00007faab43cf260 +java.util.stream.MatchOps$$Lambda$771/0x00007faab41e4f78 +java.util.stream.MatchOps$2MatchSink +com.networknt.schema.format.UriFormat$$Lambda$772/0x00007faab43cf4b0 +com.networknt.schema.utils.SetView +com.networknt.schema.ValidationMessageHandler$$Lambda$773/0x00007faab43cfb18 +com.networknt.schema.MessageSourceValidationMessage +com.networknt.schema.i18n.MessageFormatter +com.networknt.schema.MessageSourceValidationMessage$BuilderSupport$$Lambda$774/0x00007faab43d0000 +com.networknt.schema.utils.CachingSupplier +com.networknt.schema.ValidationMessage$BuilderSupport$$Lambda$775/0x00007faab43d0458 +com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent +com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent +com.amazonaws.services.lambda.runtime.events.SNSEvent +com.amazonaws.services.lambda.runtime.events.ScheduledEvent +com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent +com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent +com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent +com.amazonaws.services.lambda.runtime.events.StreamsEventResponse +com.amazonaws.services.lambda.runtime.events.StreamsEventResponse$StreamsEventResponseBuilder +io.burt.jmespath.function.Function +io.burt.jmespath.function.BaseFunction +software.amazon.lambda.powertools.utilities.jmespath.Base64Function +io.burt.jmespath.JmesPathException +io.burt.jmespath.function.FunctionConfigurationException +sun.nio.cs.ThreadLocalCoders +sun.nio.cs.ThreadLocalCoders$Cache +sun.nio.cs.ThreadLocalCoders$1 +sun.nio.cs.ThreadLocalCoders$2 +software.amazon.lambda.powertools.utilities.JsonConfig +io.burt.jmespath.JmesPath +software.amazon.lambda.powertools.utilities.JsonConfig$$Lambda$776/0x00007faab43d2b30 +software.amazon.lambda.powertools.utilities.JsonConfig$ConfigHolder +io.burt.jmespath.function.FunctionRegistry +io.burt.jmespath.function.MathFunction +io.burt.jmespath.function.AbsFunction +io.burt.jmespath.JmesPathType +io.burt.jmespath.function.ArgumentConstraints +io.burt.jmespath.function.ArgumentConstraint +io.burt.jmespath.function.ArgumentConstraints$BaseArgumentConstraint +io.burt.jmespath.function.ArgumentConstraints$TypeCheck +io.burt.jmespath.function.ArgumentConstraints$TypeOf +io.burt.jmespath.function.ArrayMathFunction +io.burt.jmespath.function.AvgFunction +io.burt.jmespath.function.ArgumentConstraints$ArrayOf +io.burt.jmespath.function.ArgumentError +io.burt.jmespath.function.ArgumentError$ArgumentTypeError +io.burt.jmespath.function.ContainsFunction +io.burt.jmespath.function.ArgumentConstraints$TypeOfEither +io.burt.jmespath.function.ArgumentConstraints$AnyValue +io.burt.jmespath.function.ArgumentConstraints$HeterogeneousListOf +io.burt.jmespath.function.CeilFunction +io.burt.jmespath.function.EndsWithFunction +io.burt.jmespath.function.FloorFunction +io.burt.jmespath.function.JoinFunction +io.burt.jmespath.function.KeysFunction +io.burt.jmespath.function.LengthFunction +io.burt.jmespath.function.MapFunction +io.burt.jmespath.function.ArgumentConstraints$Expression +io.burt.jmespath.function.CompareFunction +io.burt.jmespath.function.MaxFunction +io.burt.jmespath.function.TransformByFunction +io.burt.jmespath.function.CompareByFunction +io.burt.jmespath.function.MaxByFunction +io.burt.jmespath.function.TransformByFunction$Aggregator +io.burt.jmespath.function.CompareByFunction$ComparingAggregator +io.burt.jmespath.function.MergeFunction +io.burt.jmespath.function.ArgumentConstraints$HomogeneousListOf +io.burt.jmespath.function.MinFunction +io.burt.jmespath.function.MinByFunction +io.burt.jmespath.function.NotNullFunction +io.burt.jmespath.function.ReverseFunction +io.burt.jmespath.function.SortFunction +io.burt.jmespath.function.SortByFunction +io.burt.jmespath.function.SortByFunction$SortingAggregator +io.burt.jmespath.function.StartsWithFunction +io.burt.jmespath.function.SumFunction +io.burt.jmespath.function.ToArrayFunction +io.burt.jmespath.function.ToStringFunction +io.burt.jmespath.function.ToNumberFunction +io.burt.jmespath.function.TypeFunction +io.burt.jmespath.function.ValuesFunction +software.amazon.lambda.powertools.utilities.jmespath.Base64GZipFunction +java.util.zip.GZIPInputStream +software.amazon.lambda.powertools.utilities.jmespath.JsonFunction +io.burt.jmespath.RuntimeConfiguration$Builder +io.burt.jmespath.RuntimeConfiguration +io.burt.jmespath.Adapter +io.burt.jmespath.BaseRuntime +io.burt.jmespath.jackson.JacksonRuntime +io.burt.jmespath.node.NodeFactory +io.burt.jmespath.function.FunctionCallException +io.burt.jmespath.function.ArgumentTypeException +io.burt.jmespath.node.StandardNodeFactory +io.burt.jmespath.Expression +io.burt.jmespath.node.Node +io.burt.jmespath.node.StringNode +io.burt.jmespath.node.CurrentNode +io.burt.jmespath.node.PropertyNode +io.burt.jmespath.node.IndexNode +io.burt.jmespath.node.SliceNode +io.burt.jmespath.node.ProjectionNode +io.burt.jmespath.node.FlattenArrayNode +io.burt.jmespath.node.FlattenObjectNode +io.burt.jmespath.node.SelectionNode +io.burt.jmespath.node.OperatorNode +io.burt.jmespath.node.OrNode +io.burt.jmespath.node.AndNode +io.burt.jmespath.node.FunctionCallNode +io.burt.jmespath.node.ExpressionReferenceNode +io.burt.jmespath.node.NegateNode +io.burt.jmespath.node.CreateObjectNode +io.burt.jmespath.node.CreateArrayNode +io.burt.jmespath.node.JsonLiteralNode +io.burt.jmespath.node.SequenceNode +java.math.MathContext +com.amazonaws.services.lambda.runtime.events.SQSBatchResponse +org.assertj.core.api.InstanceOfAssertFactories +org.assertj.core.api.Assertions +org.assertj.core.api.NumberAssert +org.assertj.core.api.ComparableAssert +org.assertj.core.api.Descriptable +org.assertj.core.api.ExtensionPoints +org.assertj.core.api.Assert +org.assertj.core.api.AbstractAssert +org.assertj.core.api.AbstractObjectAssert +org.assertj.core.api.AbstractComparableAssert +org.assertj.core.api.AbstractBigIntegerAssert +org.assertj.core.api.BigIntegerAssert +org.assertj.core.data.TemporalOffset +org.assertj.core.data.TemporalUnitOffset +org.assertj.core.data.TemporalUnitWithinOffset +org.assertj.core.data.TemporalUnitLessThanOffset +org.assertj.core.configuration.ConfigurationProvider +org.assertj.core.api.AssertionsForClassTypes +org.assertj.core.api.AbstractTemporalAssert +org.assertj.core.api.AbstractInstantAssert +org.assertj.core.api.InstantAssert +org.assertj.core.api.AbstractYearMonthAssert +org.assertj.core.api.YearMonthAssert +org.assertj.core.api.AbstractLocalDateAssert +org.assertj.core.api.LocalDateAssert +org.assertj.core.api.AbstractLocalTimeAssert +org.assertj.core.api.LocalTimeAssert +org.assertj.core.api.ArraySortedAssert +org.assertj.core.api.EnumerableAssert +org.assertj.core.api.AbstractEnumerableAssert +org.assertj.core.api.AbstractArrayAssert +org.assertj.core.api.AbstractByteArrayAssert +org.assertj.core.api.ByteArrayAssert +org.assertj.core.api.AbstractThrowableAssert +org.assertj.core.api.ThrowableAssert +org.assertj.core.api.AbstractPeriodAssert +org.assertj.core.api.PeriodAssert +org.assertj.core.api.AbstractDurationAssert +org.assertj.core.api.DurationAssert +org.assertj.core.api.AbstractDateAssert +org.assertj.core.api.DateAssert +org.assertj.core.api.AbstractCharSequenceAssert +org.assertj.core.api.AbstractStringAssert +org.assertj.core.api.StringAssert +org.assertj.core.api.CharSequenceAssert +org.assertj.core.api.AbstractOffsetTimeAssert +org.assertj.core.api.OffsetTimeAssert +org.assertj.core.api.AbstractOffsetDateTimeAssert +org.assertj.core.api.OffsetDateTimeAssert +org.assertj.core.api.AbstractLocalDateTimeAssert +org.assertj.core.api.LocalDateTimeAssert +org.assertj.core.api.AbstractZonedDateTimeAssert +org.assertj.core.api.ZonedDateTimeAssert +org.assertj.core.api.AbstractBigDecimalAssert +org.assertj.core.api.BigDecimalAssert +org.assertj.core.api.AbstractUriAssert +org.assertj.core.api.UriAssert +org.assertj.core.api.AbstractBooleanArrayAssert +org.assertj.core.api.BooleanArrayAssert +org.assertj.core.api.AbstractByteAssert +org.assertj.core.api.ByteAssert +org.assertj.core.api.AbstractBooleanAssert +org.assertj.core.api.BooleanAssert +org.assertj.core.api.AbstractUrlAssert +org.assertj.core.api.UrlAssert +org.assertj.core.api.AbstractInputStreamAssert +org.assertj.core.api.InputStreamAssert +org.assertj.core.api.AbstractFileAssert +org.assertj.core.api.FileAssert +org.assertj.core.api.AbstractDoubleArrayAssert +org.assertj.core.api.DoubleArrayAssert +org.assertj.core.api.AbstractFloatArrayAssert +org.assertj.core.api.FloatArrayAssert +org.assertj.core.api.FloatingPointNumberAssert +org.assertj.core.api.AbstractFloatAssert +org.assertj.core.api.FloatAssert +org.assertj.core.api.AbstractCharacterAssert +org.assertj.core.api.CharacterAssert +org.assertj.core.api.AbstractCharArrayAssert +org.assertj.core.api.CharArrayAssert +org.assertj.core.api.AbstractDoubleAssert +org.assertj.core.api.DoubleAssert +org.assertj.core.api.AbstractLongArrayAssert +org.assertj.core.api.LongArrayAssert +org.assertj.core.api.AbstractLongAssert +org.assertj.core.api.LongAssert +org.assertj.core.api.AbstractShortAssert +org.assertj.core.api.ShortAssert +org.assertj.core.api.AbstractIntegerAssert +org.assertj.core.api.IntegerAssert +org.assertj.core.api.AbstractShortArrayAssert +org.assertj.core.api.ShortArrayAssert +org.assertj.core.api.AbstractIntArrayAssert +org.assertj.core.api.IntArrayAssert +org.assertj.core.description.Description +org.assertj.core.description.LazyTextDescription +org.assertj.core.description.TextDescription +org.assertj.core.api.AssertionInfo +org.assertj.core.internal.ComparisonStrategy +org.assertj.core.api.ObjectEnumerableAssert +org.assertj.core.api.IndexedObjectEnumerableAssert +org.assertj.core.api.AbstractIterableAssert +org.assertj.core.api.AbstractCollectionAssert +org.assertj.core.api.AbstractListAssert +org.assertj.core.api.FactoryBasedNavigableListAssert +org.assertj.core.api.ListAssert +org.assertj.core.internal.Objects +org.assertj.core.error.ErrorMessageFactory +org.assertj.core.util.introspection.IntrospectionError +org.assertj.core.internal.AbstractComparisonStrategy +org.assertj.core.internal.StandardComparisonStrategy +org.assertj.core.util.introspection.PropertySupport +org.assertj.core.internal.Failures +org.assertj.core.error.AssertionErrorCreator +org.assertj.core.util.Arrays +org.assertj.core.error.ConstructorInvoker +org.assertj.core.util.introspection.FieldSupport +org.assertj.core.error.GroupTypeDescription +org.assertj.core.internal.Conditions +org.assertj.core.api.WritableAssertionInfo +org.assertj.core.presentation.Representation +org.assertj.core.configuration.Configuration +org.assertj.core.configuration.PreferredAssumptionException +org.assertj.core.configuration.PreferredAssumptionException$1 +org.assertj.core.configuration.Services +org.assertj.core.util.Lists +org.assertj.core.util.Streams +org.assertj.core.util.Lists$$Lambda$777/0x00007faab441c950 +org.assertj.core.presentation.CompositeRepresentation +java.lang.invoke.LambdaForm$DMH/0x00007faab4420000 +org.assertj.core.presentation.CompositeRepresentation$$Lambda$778/0x00007faab441cdc8 +java.util.stream.SortedOps$SizedRefSortingSink +org.assertj.core.presentation.StandardRepresentation +java.nio.file.DirectoryStream +org.assertj.core.internal.Strings +org.assertj.core.internal.Comparables +org.junit.jupiter.api.extension.AfterTestExecutionCallback +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$779/0x00007faab441dc38 +org.junit.jupiter.engine.descriptor.CallbackSupport$$Lambda$780/0x00007faab441de58 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$781/0x00007faab441e090 +org.junit.jupiter.api.extension.AfterEachCallback +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$782/0x00007faab441e4b8 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$783/0x00007faab441e6d8 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$784/0x00007faab441e900 +org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$785/0x00007faab441eb28 +org.junit.jupiter.engine.descriptor.MethodExtensionContext$$Lambda$786/0x00007faab441ed50 +org.junit.jupiter.engine.descriptor.TestTemplateExtensionContext$$Lambda$787/0x00007faab441ef90 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$788/0x00007faab441f1d0 +org.junit.jupiter.engine.descriptor.CallbackSupport$$Lambda$789/0x00007faab441f3f0 +org.junit.jupiter.engine.extension.AutoCloseExtension$$Lambda$790/0x00007faab441f618 +org.junit.jupiter.engine.extension.AutoCloseExtension$$Lambda$791/0x00007faab441f868 +org.junit.jupiter.api.extension.TestInstancePreDestroyCallback$$Lambda$792/0x00007faab441faa0 +org.junit.jupiter.api.extension.TestInstancePreDestroyCallback$$Lambda$793/0x00007faab441fce0 +org.junit.jupiter.engine.extension.AutoCloseExtension$$Lambda$794/0x00007faab4424000 +org.junit.jupiter.api.AutoClose +org.junit.jupiter.engine.extension.AutoCloseExtension$$Lambda$795/0x00007faab4424450 +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$796/0x00007faab4424688 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$797/0x00007faab44248b0 +java.util.concurrent.ConcurrentHashMap$EntrySpliterator +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$EvaluatedValue +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$798/0x00007faab4424d10 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$799/0x00007faab4424f50 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$EvaluatedValue$$Lambda$800/0x00007faab44251a0 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$801/0x00007faab44253e0 +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$$Lambda$802/0x00007faab4425618 +org.junit.jupiter.engine.config.CachingJupiterConfiguration$$Lambda$803/0x00007faab4425840 +org.junit.platform.engine.TestExecutionResult +org.junit.platform.engine.TestExecutionResult$Status +org.junit.jupiter.api.extension.TestWatcher +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$804/0x00007faab44262e8 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$$Lambda$805/0x00007faab4426520 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$806/0x00007faab4426758 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$807/0x00007faab4426998 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$808/0x00007faab4426be8 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$809/0x00007faab4426e28 +org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener$$Lambda$810/0x00007faab4427068 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$811/0x00007faab44272b8 +org.junit.platform.launcher.core.CompositeEngineExecutionListener$$Lambda$812/0x00007faab44274f0 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$813/0x00007faab4427718 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$814/0x00007faab4427950 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$815/0x00007faab4427b78 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$816/0x00007faab4427db0 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$817/0x00007faab4422000 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$1 +org.junit.platform.engine.support.hierarchical.NodeTestTask$DefaultDynamicTestExecutor$$Lambda$818/0x00007faab4422460 +org.apache.maven.surefire.junitplatform.RunListenerAdapter$$Lambda$819/0x00007faab4422698 +com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage +com.amazonaws.services.lambda.runtime.events.SQSEvent$MessageAttribute +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringKD +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.ContextualKeyDeserializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JacksonStdImpl +jdk.proxy2.$Proxy72 +software.amazon.lambda.powertools.validation.handlers.StandardSQSHandler +software.amazon.lambda.powertools.validation.handlers.StandardSQSHandler$AjcClosure1 +com.amazonaws.services.lambda.runtime.events.SQSBatchResponse$SQSBatchResponseBuilder +com.networknt.schema.result.JsonNodeResults +com.networknt.schema.result.JsonNodeResult +com.networknt.schema.result.JsonNodeResults$$Lambda$820/0x00007faab4421230 +software.amazon.lambda.powertools.validation.ValidationUtils$ValidationErrors +com.fasterxml.jackson.databind.introspect.PotentialCreators +com.fasterxml.jackson.databind.introspect.CollectorBase +com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector +com.fasterxml.jackson.databind.introspect.AnnotationMap +com.fasterxml.jackson.databind.introspect.TypeResolutionContext$Basic +com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector$FieldBuilder +com.fasterxml.jackson.annotation.JsonKey +com.fasterxml.jackson.annotation.JsonValue +com.fasterxml.jackson.annotation.JsonAnyGetter +com.fasterxml.jackson.annotation.JsonAnySetter +com.fasterxml.jackson.databind.PropertyName +com.fasterxml.jackson.annotation.JsonGetter +com.fasterxml.jackson.annotation.JsonProperty +com.fasterxml.jackson.annotation.JsonAutoDetect$1 +com.fasterxml.jackson.annotation.PropertyAccessor +com.fasterxml.jackson.annotation.JsonIgnore +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$WithMember +com.fasterxml.jackson.databind.AnnotationIntrospector$ReferenceProperty +com.fasterxml.jackson.databind.AnnotationIntrospector$ReferenceProperty$Type +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$Linked +com.fasterxml.jackson.databind.introspect.AnnotatedMethodCollector +com.fasterxml.jackson.databind.introspect.MemberKey +com.fasterxml.jackson.databind.introspect.AnnotatedMethodCollector$MethodBuilder +com.fasterxml.jackson.databind.introspect.AnnotatedMethodMap +com.fasterxml.jackson.databind.introspect.AnnotatedCreatorCollector +com.fasterxml.jackson.annotation.JsonCreator +com.fasterxml.jackson.databind.introspect.PotentialCreator +com.fasterxml.jackson.annotation.JsonCreator$Mode +com.fasterxml.jackson.databind.cfg.ConstructorDetector +com.fasterxml.jackson.databind.cfg.ConstructorDetector$SingleArgConstructor +sun.reflect.generics.scope.ConstructorScope +com.fasterxml.jackson.databind.type.TypeBindings$TypeParamStash +com.fasterxml.jackson.databind.type.TypeBindings$AsKey +com.fasterxml.jackson.annotation.JsonSetter +com.fasterxml.jackson.annotation.JacksonInject +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$5 +com.fasterxml.jackson.annotation.JsonProperty$Access +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$6 +com.fasterxml.jackson.databind.annotation.JsonNaming +com.fasterxml.jackson.annotation.JsonPropertyOrder +com.fasterxml.jackson.annotation.JsonPropertyDescription +com.fasterxml.jackson.databind.PropertyMetadata +com.fasterxml.jackson.databind.ext.OptionalHandlerFactory +com.fasterxml.jackson.databind.ext.Java7Handlers +com.fasterxml.jackson.databind.ext.Java7HandlersImpl +com.fasterxml.jackson.databind.ext.NioPathSerializer +com.fasterxml.jackson.databind.ext.NioPathDeserializer +com.fasterxml.jackson.databind.util.BeanUtil +com.fasterxml.jackson.databind.ser.BeanSerializerBuilder +com.fasterxml.jackson.annotation.JsonIgnoreType +com.fasterxml.jackson.databind.ser.PropertyBuilder +com.fasterxml.jackson.annotation.JsonInclude +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$3 +com.fasterxml.jackson.annotation.JsonTypeId +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$2 +com.fasterxml.jackson.databind.BeanProperty$Std +com.fasterxml.jackson.annotation.JsonIgnoreProperties +com.fasterxml.jackson.annotation.JacksonAnnotation +jdk.proxy2.$Proxy73 +jdk.proxy2.$Proxy74 +jdk.proxy2.$Proxy75 +com.fasterxml.jackson.databind.introspect.AnnotationCollector$NCollector +com.fasterxml.jackson.annotation.JacksonAnnotationsInside +jdk.proxy2.$Proxy76 +com.fasterxml.jackson.databind.ser.PropertyBuilder$1 +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$1 +com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Empty +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Single +com.fasterxml.jackson.databind.annotation.JsonAppend +com.fasterxml.jackson.annotation.JsonIgnoreProperties$Value +com.fasterxml.jackson.annotation.JsonIncludeProperties +com.fasterxml.jackson.annotation.JsonIncludeProperties$Value +com.fasterxml.jackson.annotation.JsonFilter +com.fasterxml.jackson.databind.ser.impl.BeanAsArraySerializer +com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer +com.fasterxml.jackson.databind.ser.AnyGetterWriter +java.util.stream.DoubleStream +java.util.stream.LongStream +com.fasterxml.jackson.annotation.JsonFormat$Feature +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$SerializerAndMapResult +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Double +com.networknt.schema.utils.SetView$SetViewIterator +com.fasterxml.jackson.databind.annotation.JsonSerialize$Inclusion +com.fasterxml.jackson.databind.annotation.JsonSerialize$Typing +com.fasterxml.jackson.databind.util.Converter +com.fasterxml.jackson.databind.util.Converter$None +com.fasterxml.jackson.databind.JsonSerializer$None +jdk.proxy2.$Proxy77 +com.networknt.schema.ValidationMessage$Builder +com.fasterxml.jackson.databind.introspect.MethodGenericTypeResolver +com.fasterxml.jackson.databind.annotation.NoClass +com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector$1 +com.fasterxml.jackson.databind.util.IgnorePropertiesUtil +com.fasterxml.jackson.databind.ser.std.StdArraySerializers +com.fasterxml.jackson.databind.ser.std.StdArraySerializers$BooleanArraySerializer +com.fasterxml.jackson.databind.ser.std.ByteArraySerializer +com.fasterxml.jackson.databind.ser.std.StdArraySerializers$CharArraySerializer +com.fasterxml.jackson.databind.ser.std.StdArraySerializers$TypedPrimitiveArraySerializer +com.fasterxml.jackson.databind.ser.std.StdArraySerializers$ShortArraySerializer +com.fasterxml.jackson.databind.ser.std.StdArraySerializers$IntArraySerializer +com.fasterxml.jackson.databind.ser.std.StdArraySerializers$LongArraySerializer +com.fasterxml.jackson.databind.ser.std.StdArraySerializers$FloatArraySerializer +com.fasterxml.jackson.databind.ser.std.StdArraySerializers$DoubleArraySerializer +com.networknt.schema.i18n.ResourceBundleMessageSource$$Lambda$821/0x00007faab4437d00 +com.networknt.schema.i18n.ResourceBundleMessageSource$$Lambda$822/0x00007faab4439a70 +com.networknt.schema.i18n.ResourceBundleMessageSource$$Lambda$823/0x00007faab4439cb8 +com.networknt.schema.i18n.ResourceBundleMessageSource$$Lambda$824/0x00007faab4439f00 +com.networknt.schema.i18n.ResourceBundleMessageSource$$Lambda$825/0x00007faab443a150 +com.networknt.schema.i18n.ResourceBundleMessageSource$$Lambda$826/0x00007faab443a398 +com.networknt.schema.i18n.ResourceBundleMessageSource$$Lambda$827/0x00007faab443a5e8 +com.networknt.schema.i18n.ResourceBundleMessageSource$$Lambda$828/0x00007faab443a828 +java.util.ResourceBundle$ResourceBundleControlProviderHolder +java.util.ResourceBundle$ResourceBundleControlProviderHolder$$Lambda$829/0x00007faab41e71c0 +java.util.spi.ResourceBundleControlProvider +java.util.ResourceBundle$ResourceBundleControlProviderHolder$$Lambda$830/0x00007faab41e75e0 +java.util.ImmutableCollections$Access +jdk.internal.access.JavaUtilCollectionAccess +java.util.ImmutableCollections$Access$1 +java.util.ResourceBundle$CacheKey +java.util.ResourceBundle$CacheKeyReference +java.util.ResourceBundle$KeyElementReference +java.util.ResourceBundle$$Lambda$831/0x00007faab41e84e0 +java.util.ResourceBundle$Control$2 +java.util.PropertyResourceBundle +sun.util.PropertyResourceBundleCharset +sun.util.PropertyResourceBundleCharset$PropertiesFileDecoder +java.util.ResourceBundle$BundleReference +com.networknt.schema.i18n.ResourceBundleMessageSource$$Lambda$832/0x00007faab443aa70 +com.networknt.schema.i18n.ResourceBundleMessageSource$$Lambda$833/0x00007faab443acb0 +java.text.Format$FieldDelegate +java.text.FieldPosition$Delegate +java.text.NumberFormat$Field +com.fasterxml.jackson.databind.ser.std.NumberSerializers$1 +org.slf4j.event.Level +org.slf4j.helpers.MessageFormatter +org.slf4j.helpers.FormattingTuple +org.apache.maven.surefire.api.report.TestOutputReportEntry +com.amazonaws.services.lambda.runtime.events.SQSBatchResponse$BatchItemFailure +com.amazonaws.services.lambda.runtime.events.SQSBatchResponse$BatchItemFailure$BatchItemFailureBuilder +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Multi +org.assertj.core.api.AssertionsForInterfaceTypes +org.assertj.core.api.GenericComparableAssert +org.assertj.core.api.AbstractUniversalComparableAssert +org.assertj.core.api.UniversalComparableAssert +org.assertj.core.api.AbstractIterableSizeAssert +org.assertj.core.api.IterableSizeAssert +org.assertj.core.api.AssertFactory +org.assertj.core.api.ObjectAssert +org.assertj.core.api.ListAssert$$Lambda$834/0x00007faab443f898 +org.assertj.core.internal.Iterables +org.assertj.core.internal.Predicates +org.assertj.core.internal.Lists +org.assertj.core.util.IterableUtil +org.assertj.core.internal.CommonValidations +software.amazon.lambda.powertools.validation.internal.ValidationAspectTest$$Lambda$835/0x00007faab4441120 +org.assertj.core.internal.Iterables$$Lambda$836/0x00007faab4441360 +org.assertj.core.internal.Iterables$$Lambda$837/0x00007faab44415b8 +org.assertj.core.internal.StandardComparisonStrategy$$Lambda$838/0x00007faab44417d8 +org.junit.jupiter.engine.extension.TimeoutExtension$$Lambda$839/0x00007faab4441a30 +org.junit.jupiter.engine.extension.TimeoutConfiguration$$Lambda$840/0x00007faab4441c88 +software.amazon.lambda.powertools.validation.handlers.ValidationInboundAPIGatewayV2HTTPEventHandler +software.amazon.lambda.powertools.validation.handlers.ValidationInboundAPIGatewayV2HTTPEventHandler$AjcClosure1 +org.assertj.core.internal.WholeNumbers +org.assertj.core.internal.Numbers +org.assertj.core.internal.Integers +org.mockito.internal.invocation.RealMethod$IsIllegal +org.mockito.internal.debugging.LocationFactory +org.mockito.internal.debugging.LocationFactory$Factory +org.mockito.internal.util.Platform +org.mockito.internal.debugging.LocationFactory$DefaultLocationFactory +org.mockito.invocation.Location +org.mockito.internal.debugging.LocationImpl +org.mockito.exceptions.stacktrace.StackTraceCleaner +org.mockito.exceptions.stacktrace.StackTraceCleaner$StackFrameMetadata +org.mockito.internal.exceptions.stacktrace.DefaultStackTraceCleaner +org.mockito.internal.debugging.LocationImpl$$Lambda$841/0x00007faab4444500 +org.mockito.internal.debugging.LocationImpl$$Lambda$842/0x00007faab4444740 +org.mockito.internal.debugging.LocationImpl$$Lambda$843/0x00007faab4444998 +java.lang.StackStreamFactory +java.lang.StackWalker$ExtendedOption +java.lang.StackStreamFactory$StackFrameTraverser +java.lang.StackStreamFactory$WalkerState +java.lang.StackStreamFactory$1 +java.lang.StackStreamFactory$FrameBuffer +java.lang.StackStreamFactory$StackFrameTraverser$StackFrameBuffer +org.mockito.internal.debugging.LocationImpl$$Lambda$844/0x00007faab4444bd8 +org.mockito.internal.debugging.LocationImpl$MetadataShim +org.mockito.internal.debugging.LocationImpl$$Lambda$845/0x00007faab4445058 +org.mockito.internal.debugging.LocationImpl$$Lambda$846/0x00007faab4445298 +org.mockito.invocation.InvocationFactory +org.mockito.internal.invocation.DefaultInvocationFactory +org.mockito.internal.invocation.mockref.MockReference +org.mockito.internal.invocation.AbstractAwareMethod +org.mockito.internal.invocation.MockitoMethod +org.mockito.internal.exceptions.VerificationAwareInvocation +org.mockito.internal.invocation.InterceptedInvocation +org.mockito.internal.invocation.InterceptedInvocation$1 +org.mockito.internal.invocation.mockref.MockWeakReference +org.mockito.internal.creation.DelegatingMethod +org.mockito.internal.creation.SuspendMethod +org.mockito.internal.progress.SequenceNumber +org.mockito.internal.invocation.ArgumentsProcessor +org.mockito.internal.invocation.InvocationMatcher +org.mockito.internal.invocation.ArgumentMatcherAction +org.mockito.internal.stubbing.BaseStubbing +org.mockito.internal.stubbing.OngoingStubbingImpl +org.mockito.internal.listeners.StubbingLookupNotifier +org.mockito.listeners.StubbingLookupEvent +org.mockito.internal.util.ObjectMethodsGuru +org.mockito.internal.util.Primitives +org.mockito.internal.stubbing.answers.DefaultAnswerValidator +org.mockito.internal.stubbing.answers.InvocationInfo +org.mockito.internal.stubbing.answers.Returns +org.mockito.internal.util.reflection.GenericMetadataSupport +org.mockito.internal.util.reflection.GenericMetadataSupport$GenericArrayReturnType +org.mockito.internal.util.reflection.GenericMetadataSupport$FromClassGenericMetadataSupport +org.mockito.internal.util.reflection.GenericMetadataSupport$FromParameterizedTypeGenericMetadataSupport +org.mockito.internal.util.reflection.GenericMetadataSupport$BoundedType +org.mockito.internal.util.reflection.GenericMetadataSupport$NotGenericReturnTypeSupport +org.mockito.internal.util.reflection.GenericMetadataSupport$ParameterizedReturnType +org.mockito.internal.util.reflection.GenericMetadataSupport$TypeVariableReturnType +org.mockito.internal.stubbing.StubbedInvocationMatcher +org.mockito.internal.stubbing.ConsecutiveStubbing +org.mockito.internal.invocation.MatcherApplicationStrategy +org.mockito.internal.invocation.TypeSafeMatching +org.mockito.internal.invocation.StubInfoImpl +org.mockito.internal.invocation.InvocationMatcher$1 +org.mockito.internal.util.KotlinInlineClassUtil +org.mockito.internal.matchers.ContainsExtraTypeInfo +org.mockito.internal.matchers.Equals +org.mockito.internal.matchers.ArrayEquals +org.assertj.core.api.NotThrownAssert +org.assertj.core.api.ThrowableAssert$ThrowingCallable +software.amazon.lambda.powertools.validation.internal.ValidationAspectTest$$Lambda$847/0x00007faab444b9b0 +org.mockito.internal.invocation.TypeSafeMatching$$Lambda$848/0x00007faab444bbd8 +org.mockito.internal.matchers.CapturesArguments +org.assertj.core.internal.Throwables +org.assertj.core.description.EmptyTextDescription +software.amazon.lambda.powertools.validation.handlers.KinesisHandlerWithError +software.amazon.lambda.powertools.validation.handlers.KinesisHandlerWithError$AjcClosure1 +com.amazonaws.services.lambda.runtime.events.StreamsEventResponse$BatchItemFailure +com.amazonaws.services.lambda.runtime.events.StreamsEventResponse$BatchItemFailure$BatchItemFailureBuilder +software.amazon.lambda.powertools.validation.internal.ValidationAspectTest$$Lambda$849/0x00007faab444cdd0 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$850/0x00007faab444d010 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$851/0x00007faab444d260 +org.junit.jupiter.params.provider.AnnotationBasedArgumentsProvider$$Lambda$852/0x00007faab444d4b0 +org.junit.jupiter.engine.descriptor.TestTemplateExtensionContext$$Lambda$853/0x00007faab444d6f8 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$854/0x00007faab444d938 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$855/0x00007faab444db80 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$856/0x00007faab444ddc8 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$857/0x00007faab444e010 +org.junit.jupiter.params.provider.ArgumentsUtils +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$858/0x00007faab444e458 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$859/0x00007faab444e698 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$860/0x00007faab444e8c0 +org.junit.platform.commons.util.ReflectionUtils$$Lambda$861/0x00007faab444eb08 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$862/0x00007faab444ed60 +org.junit.jupiter.params.provider.MethodArgumentsProvider$$Lambda$863/0x00007faab444ef88 +com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent +com.amazonaws.services.lambda.runtime.events.KafkaEvent +com.amazonaws.services.lambda.runtime.events.ActiveMQEvent +com.amazonaws.services.lambda.runtime.events.RabbitMQEvent +com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent +com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent +com.fasterxml.jackson.databind.deser.std.JdkDeserializers +com.fasterxml.jackson.databind.deser.std.FromStringDeserializer +com.fasterxml.jackson.databind.deser.std.UUIDDeserializer +com.fasterxml.jackson.databind.deser.std.AtomicBooleanDeserializer +com.fasterxml.jackson.databind.deser.std.AtomicIntegerDeserializer +com.fasterxml.jackson.databind.deser.std.AtomicLongDeserializer +com.fasterxml.jackson.databind.deser.std.ByteBufferDeserializer +com.fasterxml.jackson.databind.deser.std.NullifyingDeserializer +com.fasterxml.jackson.databind.deser.std.StdNodeBasedDeserializer +com.fasterxml.jackson.databind.deser.std.ThreadGroupDeserializer +com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$StringBuilderDeserializer +com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$StringBufferDeserializer +com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$Std +com.fasterxml.jackson.databind.jsontype.impl.SubTypeValidator +com.fasterxml.jackson.databind.annotation.JsonValueInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators +com.fasterxml.jackson.databind.deser.ValueInstantiator +com.fasterxml.jackson.databind.deser.ValueInstantiator$Base +com.fasterxml.jackson.databind.deser.std.JsonLocationInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$JDKValueInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$ArrayListInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$HashSetInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$LinkedListInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$TreeSetInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$ConstantValueInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$LinkedHashMapInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$HashMapInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$ConcurrentHashMapInstantiator +com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators$TreeMapInstantiator +com.fasterxml.jackson.core.JsonLocation +com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord +com.fasterxml.jackson.databind.deser.impl.CreatorCollector +com.fasterxml.jackson.databind.deser.std.StdValueInstantiator +com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder +com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty +com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer +com.fasterxml.jackson.databind.deser.impl.FailingDeserializer +com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider +com.fasterxml.jackson.databind.util.AccessPattern +com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$4 +com.fasterxml.jackson.annotation.JsonAlias +com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap +com.fasterxml.jackson.databind.exc.IgnoredPropertyException +com.fasterxml.jackson.databind.deser.SettableBeanProperty$Delegating +com.fasterxml.jackson.databind.deser.impl.ManagedReferenceProperty +com.fasterxml.jackson.databind.deser.impl.ObjectIdReferenceProperty +com.fasterxml.jackson.databind.deser.impl.MergingSettableBeanProperty +com.fasterxml.jackson.databind.deser.impl.InnerClassProperty +com.fasterxml.jackson.databind.deser.impl.ReadableObjectId$Referring +com.fasterxml.jackson.databind.deser.BeanDeserializer$BeanReferring +com.fasterxml.jackson.databind.deser.impl.BeanAsArrayDeserializer +com.fasterxml.jackson.databind.deser.BasicDeserializerFactory$ContainerDefaultMappings +com.amazonaws.services.lambda.runtime.events.SNSEvent$SNS +com.amazonaws.services.lambda.runtime.events.SNSEvent$MessageAttribute +com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer$StringKD +com.fasterxml.jackson.databind.deser.ContextualKeyDeserializer +com.fasterxml.jackson.databind.annotation.JacksonStdImpl +jdk.proxy2.$Proxy78 +com.fasterxml.jackson.core.util.InternalJacksonUtil +software.amazon.lambda.powertools.validation.internal.ValidationAspect$$Lambda$864/0x00007faab445d7b0 +com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializerNR +com.fasterxml.jackson.databind.deser.std.NumberDeserializers +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$PrimitiveOrWrapperDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$IntegerDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BooleanDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$LongDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$DoubleDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$CharacterDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$ByteDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$ShortDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$FloatDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$NumberDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BigDecimalDeserializer +com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BigIntegerDeserializer +com.fasterxml.jackson.databind.util.TokenBuffer$Parser +com.fasterxml.jackson.databind.util.TokenBuffer$Segment +com.fasterxml.jackson.databind.ser.std.MapProperty +com.fasterxml.jackson.databind.ser.BasicSerializerFactory$1 +com.fasterxml.jackson.databind.ser.std.StdKeySerializers +com.fasterxml.jackson.databind.ser.std.StdKeySerializer +com.fasterxml.jackson.databind.ser.std.StdKeySerializers$StringKeySerializer +com.fasterxml.jackson.databind.ser.std.StdKeySerializers$Dynamic +com.fasterxml.jackson.databind.ser.std.StdKeySerializers$Default +com.fasterxml.jackson.databind.ser.std.StdKeySerializers$EnumKeySerializer +com.fasterxml.jackson.databind.ser.std.MapSerializer$1 +com.fasterxml.jackson.databind.util.TokenBufferReadContext +com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent$RequestContext +com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent$Elb +com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent$AWSLogs +java.util.Base64 +java.util.Base64$Decoder +java.util.Base64$Encoder +java.lang.invoke.LambdaForm$MH/0x00007faab4468000 +java.lang.invoke.LambdaForm$MH/0x00007faab4468400 +com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent$CloudFormationCustomResourceEventBuilder +com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent$Record +com.fasterxml.jackson.databind.deser.std.DateDeserializers +com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateBasedDeserializer +com.fasterxml.jackson.databind.deser.std.DateDeserializers$CalendarDeserializer +com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer +com.fasterxml.jackson.databind.util.ByteBufferBackedOutputStream +software.amazon.lambda.powertools.validation.internal.ValidationAspect$$Lambda$865/0x00007faab4465ee8 +com.amazonaws.services.lambda.runtime.events.KafkaEvent$KafkaEventBuilder +com.amazonaws.services.lambda.runtime.events.KafkaEvent$KafkaEventRecord +com.amazonaws.services.lambda.runtime.events.KafkaEvent$SchemaMetadata +com.amazonaws.services.lambda.runtime.events.KafkaEvent$KafkaEventRecord$KafkaEventRecordBuilder +sun.reflect.generics.tree.IntSignature +sun.reflect.generics.tree.LongSignature +sun.reflect.generics.tree.ByteSignature +com.amazonaws.services.lambda.runtime.events.KafkaEvent$SchemaMetadata$SchemaMetadataBuilder +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$IntDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$LongDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$ByteDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$ShortDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$FloatDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$DoubleDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$BooleanDeser +com.fasterxml.jackson.databind.deser.std.PrimitiveArrayDeserializers$CharDeser +com.fasterxml.jackson.databind.exc.InvalidNullException +software.amazon.lambda.powertools.validation.internal.ValidationAspect$$Lambda$866/0x00007faab446de00 +software.amazon.lambda.powertools.validation.internal.ValidationAspect$$Lambda$867/0x00007faab446e038 +com.amazonaws.services.lambda.runtime.events.ActiveMQEvent$ActiveMQEventBuilder +com.amazonaws.services.lambda.runtime.events.ActiveMQEvent$ActiveMQMessage +com.amazonaws.services.lambda.runtime.events.ActiveMQEvent$Destination +com.amazonaws.services.lambda.runtime.events.ActiveMQEvent$ActiveMQMessage$ActiveMQMessageBuilder +sun.reflect.generics.tree.BooleanSignature +com.amazonaws.services.lambda.runtime.events.ActiveMQEvent$Destination$DestinationBuilder +com.fasterxml.jackson.databind.cfg.CoercionConfigs$1 +software.amazon.lambda.powertools.validation.internal.ValidationAspect$$Lambda$868/0x00007faab446f088 +com.amazonaws.services.lambda.runtime.events.RabbitMQEvent$RabbitMQEventBuilder +com.amazonaws.services.lambda.runtime.events.RabbitMQEvent$RabbitMessage +com.amazonaws.services.lambda.runtime.events.RabbitMQEvent$BasicProperties +com.amazonaws.services.lambda.runtime.events.RabbitMQEvent$RabbitMessage$RabbitMessageBuilder +com.amazonaws.services.lambda.runtime.events.RabbitMQEvent$BasicProperties$BasicPropertiesBuilder +com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializerNR$Scope +software.amazon.lambda.powertools.validation.internal.ValidationAspect$$Lambda$869/0x00007faab446a210 +software.amazon.lambda.powertools.validation.internal.ValidationAspect$$Lambda$870/0x00007faab446a448 +com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent$Record +com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent$Record$KinesisFirehoseRecordMetadata +software.amazon.lambda.powertools.validation.internal.ValidationAspect$$Lambda$871/0x00007faab446aae0 +jdk.internal.reflect.GeneratedConstructorAccessor8 +jdk.internal.reflect.GeneratedMethodAccessor19 +com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent$Record +com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent$Record$KinesisStreamRecordMetadata +software.amazon.lambda.powertools.validation.internal.ValidationAspect$$Lambda$872/0x00007faab446b1a8 +software.amazon.lambda.powertools.validation.model.Basket +com.amazonaws.services.lambda.runtime.events.APIGatewayV2WebSocketResponse +com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent +com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsInputPreprocessingResponse +com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsInputPreprocessingResponse$Record +com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsInputPreprocessingResponse$Result +software.amazon.lambda.powertools.validation.internal.ResponseEventsArgumentsProvider$$Lambda$873/0x00007faab4469440 +org.assertj.core.api.ThrowableTypeAssert +software.amazon.lambda.powertools.validation.internal.ValidationAspectTest$$Lambda$874/0x00007faab4469920 +org.assertj.core.api.ThrowableAssertAlternative +software.amazon.lambda.powertools.validation.internal.ValidationAspect$$Lambda$875/0x00007faab4469b48 +software.amazon.lambda.powertools.validation.handlers.SQSHandlerWithError +software.amazon.lambda.powertools.validation.handlers.SQSHandlerWithError$AjcClosure1 +software.amazon.lambda.powertools.validation.internal.ValidationAspectTest$$Lambda$876/0x00007faab4470bc0 +software.amazon.lambda.powertools.validation.handlers.GenericSchemaV7APIGatewayProxyRequestEventHandler +software.amazon.lambda.powertools.validation.handlers.GenericSchemaV7APIGatewayProxyRequestEventHandler$AjcClosure1 +org.assertj.core.api.AbstractCharSequenceAssert$$Lambda$877/0x00007faab4471280 +org.assertj.core.api.AbstractMapAssert +org.assertj.core.api.MapAssert +org.assertj.core.api.AbstractMapSizeAssert +org.assertj.core.api.MapSizeAssert +org.assertj.core.internal.Maps +software.amazon.lambda.powertools.validation.internal.HandledResponseEventsArgumentsProvider$$Lambda$878/0x00007faab4474720 +org.assertj.core.util.Preconditions +org.assertj.core.internal.ComparatorBasedComparisonStrategy +java.util.concurrent.atomic.Striped64 +java.util.concurrent.atomic.LongAdder +java.util.concurrent.atomic.AtomicMarkableReference +java.util.concurrent.atomic.AtomicStampedReference +java.util.concurrent.atomic.AtomicIntegerFieldUpdater +java.util.concurrent.atomic.AtomicLongFieldUpdater +java.util.concurrent.atomic.AtomicReferenceFieldUpdater +org.assertj.core.presentation.PredicateDescription +org.assertj.core.presentation.TransformingList +org.assertj.core.presentation.StandardRepresentation$$Lambda$879/0x00007faab4475580 +java.lang.invoke.LambdaForm$DMH/0x00007faab4478000 +java.lang.invoke.LambdaForm$MH/0x00007faab4478400 +software.amazon.lambda.powertools.validation.internal.ValidationAspectTest$$Lambda$880/0x00007faab44757c8 +org.assertj.core.data.MapEntry +org.assertj.core.internal.ErrorMessages +software.amazon.lambda.powertools.validation.internal.ValidationAspectTest$$Lambda$881/0x00007faab4476048 +software.amazon.lambda.powertools.validation.handlers.StandardKinesisHandler +software.amazon.lambda.powertools.validation.handlers.StandardKinesisHandler$AjcClosure1 +software.amazon.lambda.powertools.validation.internal.ValidationAspectTest$$Lambda$882/0x00007faab4476700 +software.amazon.lambda.powertools.validation.handlers.SQSWithCustomEnvelopeHandler +software.amazon.lambda.powertools.validation.handlers.SQSWithCustomEnvelopeHandler$AjcClosure1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.exc.StreamWriteException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.JsonGenerationException +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.json.JsonWriteContext +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.StreamWriteCapability +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap$Bucket +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.TypeKey +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap$$Lambda$883/0x00007faab447c420 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$EntrySet +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$EntryIterator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BeanSerializerBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.PropertyBuilder +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonInclude +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$3 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonTypeId +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.BeanProperty$Std +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.PropertyBuilder$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Empty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Single +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.annotation.JsonAppend +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonFilter +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.BeanAsArraySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$SerializerAndMapResult +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$Double +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.MapProperty +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.BasicSerializerFactory$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdKeySerializers +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdKeySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdKeySerializers$StringKeySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdKeySerializers$Dynamic +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdKeySerializers$Default +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.StdKeySerializers$EnumKeySerializer +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.MapSerializer$1 +io.burt.jmespath.antlr.v4.runtime.tree.ParseTreeVisitor +io.burt.jmespath.parser.JmesPathVisitor +io.burt.jmespath.antlr.v4.runtime.tree.AbstractParseTreeVisitor +io.burt.jmespath.parser.JmesPathBaseVisitor +io.burt.jmespath.parser.ExpressionParser +io.burt.jmespath.antlr.v4.runtime.tree.Tree +io.burt.jmespath.antlr.v4.runtime.tree.SyntaxTree +io.burt.jmespath.antlr.v4.runtime.tree.ParseTree +io.burt.jmespath.antlr.v4.runtime.tree.RuleNode +io.burt.jmespath.antlr.v4.runtime.ANTLRErrorListener +io.burt.jmespath.parser.ParseException +io.burt.jmespath.util.StringEscapeHelper +io.burt.jmespath.antlr.v4.runtime.BaseErrorListener +io.burt.jmespath.parser.ParseErrorAccumulator +io.burt.jmespath.util.AntlrHelper +io.burt.jmespath.antlr.v4.runtime.TokenSource +io.burt.jmespath.antlr.v4.runtime.IntStream +io.burt.jmespath.antlr.v4.runtime.TokenStream +io.burt.jmespath.antlr.v4.runtime.CharStream +io.burt.jmespath.antlr.v4.runtime.Recognizer +io.burt.jmespath.antlr.v4.runtime.Lexer +io.burt.jmespath.parser.JmesPathLexer +io.burt.jmespath.antlr.v4.runtime.RecognitionException +io.burt.jmespath.antlr.v4.runtime.LexerNoViableAltException +java.util.EmptyStackException +io.burt.jmespath.antlr.v4.runtime.atn.ATNSimulator +io.burt.jmespath.antlr.v4.runtime.atn.LexerATNSimulator +io.burt.jmespath.antlr.v4.runtime.Vocabulary +io.burt.jmespath.antlr.v4.runtime.RuntimeMetaData +io.burt.jmespath.antlr.v4.runtime.atn.PredictionContextCache +io.burt.jmespath.antlr.v4.runtime.atn.PredictionContext +io.burt.jmespath.antlr.v4.runtime.atn.SingletonPredictionContext +io.burt.jmespath.antlr.v4.runtime.atn.EmptyPredictionContext +io.burt.jmespath.antlr.v4.runtime.VocabularyImpl +io.burt.jmespath.antlr.v4.runtime.atn.ATNDeserializer +java.io.InvalidClassException +io.burt.jmespath.antlr.v4.runtime.atn.Transition +io.burt.jmespath.antlr.v4.runtime.atn.EpsilonTransition +io.burt.jmespath.antlr.v4.runtime.atn.ActionTransition +io.burt.jmespath.antlr.v4.runtime.atn.ATNState +io.burt.jmespath.antlr.v4.runtime.atn.DecisionState +io.burt.jmespath.antlr.v4.runtime.atn.BlockStartState +io.burt.jmespath.antlr.v4.runtime.atn.BasicBlockStartState +io.burt.jmespath.antlr.v4.runtime.atn.BlockEndState +io.burt.jmespath.antlr.v4.runtime.atn.RuleStopState +io.burt.jmespath.antlr.v4.runtime.atn.BasicState +io.burt.jmespath.antlr.v4.runtime.atn.AtomTransition +io.burt.jmespath.antlr.v4.runtime.atn.RangeTransition +io.burt.jmespath.antlr.v4.runtime.atn.RuleTransition +io.burt.jmespath.antlr.v4.runtime.atn.AbstractPredicateTransition +io.burt.jmespath.antlr.v4.runtime.atn.PredicateTransition +io.burt.jmespath.antlr.v4.runtime.atn.PrecedencePredicateTransition +io.burt.jmespath.antlr.v4.runtime.atn.SetTransition +io.burt.jmespath.antlr.v4.runtime.atn.NotSetTransition +io.burt.jmespath.antlr.v4.runtime.atn.WildcardTransition +io.burt.jmespath.antlr.v4.runtime.atn.RuleStartState +io.burt.jmespath.antlr.v4.runtime.atn.PlusBlockStartState +io.burt.jmespath.antlr.v4.runtime.atn.StarBlockStartState +io.burt.jmespath.antlr.v4.runtime.atn.TokensStartState +io.burt.jmespath.antlr.v4.runtime.atn.StarLoopbackState +io.burt.jmespath.antlr.v4.runtime.atn.StarLoopEntryState +io.burt.jmespath.antlr.v4.runtime.atn.PlusLoopbackState +io.burt.jmespath.antlr.v4.runtime.atn.LoopEndState +io.burt.jmespath.antlr.v4.runtime.atn.ATNDeserializer$UnicodeDeserializer +io.burt.jmespath.antlr.v4.runtime.atn.LexerAction +io.burt.jmespath.antlr.v4.runtime.atn.ATNDeserializationOptions +io.burt.jmespath.antlr.v4.runtime.atn.ATNType +io.burt.jmespath.antlr.v4.runtime.atn.ATN +io.burt.jmespath.antlr.v4.runtime.misc.IntSet +io.burt.jmespath.antlr.v4.runtime.misc.Pair +io.burt.jmespath.antlr.v4.runtime.atn.ATNDeserializer$UnicodeDeserializingMode +io.burt.jmespath.antlr.v4.runtime.atn.ATNDeserializer$1 +io.burt.jmespath.antlr.v4.runtime.misc.IntervalSet +io.burt.jmespath.antlr.v4.runtime.misc.Interval +io.burt.jmespath.antlr.v4.runtime.atn.ATNDeserializer$2 +io.burt.jmespath.antlr.v4.runtime.atn.Transition$1 +io.burt.jmespath.antlr.v4.runtime.atn.LexerActionType +io.burt.jmespath.antlr.v4.runtime.atn.ATNDeserializer$3 +io.burt.jmespath.antlr.v4.runtime.atn.LexerSkipAction +io.burt.jmespath.antlr.v4.runtime.dfa.DFA +io.burt.jmespath.antlr.v4.runtime.dfa.DFASerializer +io.burt.jmespath.antlr.v4.runtime.dfa.LexerDFASerializer +io.burt.jmespath.antlr.v4.runtime.CharStreams +io.burt.jmespath.antlr.v4.runtime.CodePointBuffer +io.burt.jmespath.antlr.v4.runtime.CodePointBuffer$Builder +io.burt.jmespath.antlr.v4.runtime.CodePointBuffer$Type +io.burt.jmespath.antlr.v4.runtime.CodePointBuffer$1 +io.burt.jmespath.antlr.v4.runtime.CodePointCharStream +io.burt.jmespath.antlr.v4.runtime.CodePointCharStream$CodePoint8BitCharStream +io.burt.jmespath.antlr.v4.runtime.CodePointCharStream$CodePoint16BitCharStream +io.burt.jmespath.antlr.v4.runtime.CodePointCharStream$CodePoint32BitCharStream +io.burt.jmespath.antlr.v4.runtime.CodePointCharStream$1 +io.burt.jmespath.antlr.v4.runtime.Recognizer$1 +io.burt.jmespath.antlr.v4.runtime.ConsoleErrorListener +io.burt.jmespath.antlr.v4.runtime.TokenFactory +io.burt.jmespath.antlr.v4.runtime.CommonTokenFactory +io.burt.jmespath.antlr.v4.runtime.Token +io.burt.jmespath.antlr.v4.runtime.misc.IntegerList +io.burt.jmespath.antlr.v4.runtime.misc.IntegerStack +io.burt.jmespath.antlr.v4.runtime.atn.ATNConfig +io.burt.jmespath.antlr.v4.runtime.atn.LexerATNConfig +io.burt.jmespath.antlr.v4.runtime.atn.ATNConfigSet +io.burt.jmespath.antlr.v4.runtime.atn.OrderedATNConfigSet +io.burt.jmespath.antlr.v4.runtime.dfa.DFAState +io.burt.jmespath.antlr.v4.runtime.misc.Array2DHashSet +io.burt.jmespath.antlr.v4.runtime.atn.ATNConfigSet$AbstractConfigHashSet +io.burt.jmespath.antlr.v4.runtime.atn.ATNConfigSet$ConfigHashSet +io.burt.jmespath.antlr.v4.runtime.misc.EqualityComparator +io.burt.jmespath.antlr.v4.runtime.misc.AbstractEqualityComparator +io.burt.jmespath.antlr.v4.runtime.misc.ObjectEqualityComparator +io.burt.jmespath.antlr.v4.runtime.atn.ATNConfigSet$ConfigEqualityComparator +io.burt.jmespath.antlr.v4.runtime.atn.LexerATNSimulator$SimState +io.burt.jmespath.antlr.v4.runtime.BufferedTokenStream +io.burt.jmespath.antlr.v4.runtime.CommonTokenStream +io.burt.jmespath.antlr.v4.runtime.Parser +io.burt.jmespath.parser.JmesPathParser +io.burt.jmespath.antlr.v4.runtime.ANTLRErrorStrategy +io.burt.jmespath.antlr.v4.runtime.RuleContext +io.burt.jmespath.antlr.v4.runtime.ParserRuleContext +io.burt.jmespath.antlr.v4.runtime.tree.ParseTreeListener +io.burt.jmespath.antlr.v4.runtime.tree.TerminalNode +io.burt.jmespath.antlr.v4.runtime.tree.ErrorNode +io.burt.jmespath.antlr.v4.runtime.atn.ParserATNSimulator +io.burt.jmespath.antlr.v4.runtime.atn.ProfilingATNSimulator +io.burt.jmespath.parser.JmesPathParser$SliceContext +io.burt.jmespath.parser.JmesPathParser$WildcardContext +io.burt.jmespath.parser.JmesPathParser$LiteralContext +io.burt.jmespath.parser.JmesPathParser$ExpressionContext +io.burt.jmespath.parser.JmesPathParser$BracketExpressionContext +io.burt.jmespath.parser.JmesPathParser$NotExpressionContext +io.burt.jmespath.parser.JmesPathParser$IdentifierExpressionContext +io.burt.jmespath.parser.JmesPathParser$ParenExpressionContext +io.burt.jmespath.parser.JmesPathParser$WildcardExpressionContext +io.burt.jmespath.parser.JmesPathParser$MultiSelectListExpressionContext +io.burt.jmespath.parser.JmesPathParser$MultiSelectHashExpressionContext +io.burt.jmespath.parser.JmesPathParser$LiteralExpressionContext +io.burt.jmespath.parser.JmesPathParser$FunctionCallExpressionContext +io.burt.jmespath.parser.JmesPathParser$RawStringExpressionContext +io.burt.jmespath.parser.JmesPathParser$CurrentNodeExpressionContext +io.burt.jmespath.parser.JmesPathParser$ComparisonExpressionContext +io.burt.jmespath.antlr.v4.runtime.FailedPredicateException +io.burt.jmespath.parser.JmesPathParser$AndExpressionContext +io.burt.jmespath.parser.JmesPathParser$OrExpressionContext +io.burt.jmespath.parser.JmesPathParser$PipeExpressionContext +io.burt.jmespath.parser.JmesPathParser$ChainExpressionContext +io.burt.jmespath.parser.JmesPathParser$BracketedExpressionContext +io.burt.jmespath.parser.JmesPathParser$IdentifierContext +io.burt.jmespath.parser.JmesPathParser$JsonObjectContext +io.burt.jmespath.parser.JmesPathParser$CurrentNodeContext +io.burt.jmespath.parser.JmesPathParser$JmesPathExpressionContext +io.burt.jmespath.parser.JmesPathParser$BracketSpecifierContext +io.burt.jmespath.parser.JmesPathParser$BracketIndexContext +io.burt.jmespath.parser.JmesPathParser$BracketStarContext +io.burt.jmespath.parser.JmesPathParser$BracketSliceContext +io.burt.jmespath.parser.JmesPathParser$BracketFlattenContext +io.burt.jmespath.parser.JmesPathParser$SelectContext +io.burt.jmespath.parser.JmesPathParser$ChainedExpressionContext +io.burt.jmespath.parser.JmesPathParser$KeyvalExprContext +io.burt.jmespath.parser.JmesPathParser$FunctionArgContext +io.burt.jmespath.antlr.v4.runtime.NoViableAltException +io.burt.jmespath.parser.JmesPathParser$JsonValueContext +io.burt.jmespath.parser.JmesPathParser$JsonStringValueContext +io.burt.jmespath.parser.JmesPathParser$JsonNumberValueContext +io.burt.jmespath.parser.JmesPathParser$JsonObjectValueContext +io.burt.jmespath.parser.JmesPathParser$JsonArrayValueContext +io.burt.jmespath.parser.JmesPathParser$JsonConstantValueContext +io.burt.jmespath.parser.JmesPathParser$MultiSelectListContext +io.burt.jmespath.parser.JmesPathParser$MultiSelectHashContext +io.burt.jmespath.parser.JmesPathParser$FunctionExpressionContext +io.burt.jmespath.parser.JmesPathParser$ExpressionTypeContext +io.burt.jmespath.parser.JmesPathParser$JsonObjectPairContext +io.burt.jmespath.parser.JmesPathParser$JsonArrayContext +io.burt.jmespath.antlr.v4.runtime.DefaultErrorStrategy +io.burt.jmespath.antlr.v4.runtime.InputMismatchException +io.burt.jmespath.antlr.v4.runtime.atn.SemanticContext +io.burt.jmespath.antlr.v4.runtime.atn.SemanticContext$PrecedencePredicate +io.burt.jmespath.antlr.v4.runtime.atn.SemanticContext$Predicate +io.burt.jmespath.antlr.v4.runtime.atn.PredictionMode +io.burt.jmespath.antlr.v4.runtime.atn.ArrayPredictionContext +io.burt.jmespath.antlr.v4.runtime.misc.MurmurHash +io.burt.jmespath.antlr.v4.runtime.atn.OrderedATNConfigSet$LexerConfigHashSet +io.burt.jmespath.antlr.v4.runtime.atn.SemanticContext$Operator +io.burt.jmespath.antlr.v4.runtime.atn.SemanticContext$OR +io.burt.jmespath.antlr.v4.runtime.atn.SemanticContext$AND +io.burt.jmespath.antlr.v4.runtime.WritableToken +io.burt.jmespath.antlr.v4.runtime.CommonToken +io.burt.jmespath.antlr.v4.runtime.atn.LL1Analyzer +io.burt.jmespath.antlr.v4.runtime.misc.DoubleKeyMap +io.burt.jmespath.antlr.v4.runtime.misc.FlexibleHashMap +io.burt.jmespath.antlr.v4.runtime.atn.PredictionMode$AltAndContextMap +io.burt.jmespath.antlr.v4.runtime.atn.PredictionMode$AltAndContextConfigEqualityComparator +io.burt.jmespath.antlr.v4.runtime.misc.FlexibleHashMap$Entry +io.burt.jmespath.antlr.v4.runtime.tree.TerminalNodeImpl +io.burt.jmespath.antlr.v4.runtime.dfa.DFAState$PredPrediction +io.burt.jmespath.jackson.JacksonRuntime$1 +io.burt.jmespath.jackson.JacksonRuntime$ArrayNodeListWrapper +io.burt.jmespath.function.FunctionArgument +io.burt.jmespath.function.FunctionArgument$V +io.burt.jmespath.function.FunctionArgument$E +software.amazon.lambda.powertools.validation.ValidationUtils$$Lambda$884/0x00007faab44a4e30 +software.amazon.lambda.powertools.validation.handlers.SQSWithWrongEnvelopeHandler$AjcClosure1 +software.amazon.lambda.powertools.validation.internal.ValidationAspectTest$$Lambda$885/0x00007faab44a52b8 +java.lang.invoke.LambdaForm$DMH/0x00007faab44a8000 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$886/0x00007faab44a54e0 +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$887/0x00007faab44a5718 +org.junit.jupiter.engine.extension.AutoCloseExtension$$Lambda$888/0x00007faab44a5938 +org.junit.jupiter.engine.extension.AutoCloseExtension$$Lambda$889/0x00007faab44a5b88 +org.apache.maven.surefire.api.util.internal.ObjectUtils +org.apache.maven.surefire.api.util.internal.ImmutableMap$Node +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$890/0x00007faab44a6228 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$891/0x00007faab44a6450 +com.fasterxml.jackson.databind.util.NativeImageUtil +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$892/0x00007faab44a6880 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$893/0x00007faab44a6aa8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonCreator +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonCreator$Mode +java.util.concurrent.ConcurrentHashMap$TreeNode +java.util.concurrent.ConcurrentHashMap$TreeBin +com.networknt.schema.Version201909 +com.networknt.schema.Version201909$Holder +com.networknt.schema.Vocabularies +com.networknt.schema.Vocabulary +java.lang.invoke.LambdaForm$DMH/0x00007faab44a8400 +com.networknt.schema.RefValidator$$Lambda$894/0x00007faab44a7b68 +java.lang.invoke.LambdaForm$DMH/0x00007faab44a8800 +java.lang.invoke.LambdaForm$DMH/0x00007faab44a8c00 +java.lang.invoke.LambdaForm$MH/0x00007faab44a9000 +com.networknt.schema.RecursiveRefValidator$$Lambda$895/0x00007faab44a7d90 +com.networknt.schema.utils.JsonNodes +com.networknt.schema.PatternValidator$$Lambda$896/0x00007faab44ac208 +com.networknt.schema.PatternValidator$$Lambda$897/0x00007faab44ac458 +com.networknt.schema.regex.JDKRegularExpression +com.networknt.schema.Version202012 +com.networknt.schema.Version202012$Holder +com.networknt.schema.DynamicRefValidator$$Lambda$898/0x00007faab44accf8 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$899/0x00007faab44acf20 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap$WriteThroughEntry +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$900/0x00007faab44ad3a8 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.std.NumberSerializers$1 +com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.core.io.NumberOutput +io.burt.jmespath.antlr.v4.runtime.ProxyErrorListener +io.burt.jmespath.parser.ParseError +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$901/0x00007faab44ade60 +com.networknt.schema.Version4 +com.networknt.schema.Version4$Holder +com.networknt.schema.MinimumValidator$2 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$902/0x00007faab44ae6f0 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$903/0x00007faab44ae910 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$904/0x00007faab44aeb38 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$905/0x00007faab44aed60 +jdk.internal.reflect.GeneratedConstructorAccessor9 +jdk.internal.reflect.GeneratedMethodAccessor20 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$906/0x00007faab44aef88 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$907/0x00007faab44af1b0 +java.lang.Throwable$PrintStreamOrWriter +java.lang.Throwable$WrappedPrintStream +java.lang.StackTraceElement$HashedModules +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$908/0x00007faab44af3d0 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$909/0x00007faab44af5f8 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$910/0x00007faab44af820 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$911/0x00007faab44afa48 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$912/0x00007faab44afc70 +com.fasterxml.jackson.databind.JsonMappingException$Reference +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$913/0x00007faab44aa250 +jdk.internal.reflect.GeneratedMethodAccessor21 +jdk.internal.reflect.GeneratedMethodAccessor22 +jdk.internal.reflect.GeneratedMethodAccessor23 +jdk.internal.reflect.GeneratedMethodAccessor24 +jdk.internal.reflect.GeneratedMethodAccessor25 +jdk.internal.reflect.GeneratedMethodAccessor26 +jdk.internal.reflect.GeneratedMethodAccessor27 +jdk.internal.reflect.GeneratedMethodAccessor28 +jdk.internal.reflect.GeneratedMethodAccessor29 +jdk.internal.reflect.GeneratedMethodAccessor30 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$914/0x00007faab44aa478 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$915/0x00007faab44aa6a0 +com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap$TypeAndSerializer +jdk.internal.reflect.GeneratedMethodAccessor31 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$916/0x00007faab44aacd0 +software.amazon.lambda.powertools.validation.ValidationUtilsTest$$Lambda$917/0x00007faab44aaef8 +com.networknt.schema.Version6 +com.networknt.schema.Version6$Holder +org.junit.platform.launcher.core.OutcomeDelayingEngineExecutionListener$Outcome +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$918/0x00007faab44ab990 +org.junit.platform.launcher.core.CompositeTestExecutionListener$$Lambda$919/0x00007faab44abbc8 +java.lang.invoke.LambdaForm$DMH/0x00007faab44b2800 +org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$920/0x00007faab44b4000 +org.junit.platform.launcher.core.DefaultLauncherSession$ClosedLauncher +org.apache.maven.surefire.api.suite.RunResult +org.apache.maven.surefire.booter.ForkedBooter$6 +org.apache.maven.surefire.booter.ForkedBooter$7 +java.util.concurrent.locks.AbstractQueuedSynchronizer$SharedNode +java.util.concurrent.locks.AbstractQueuedSynchronizer$ExclusiveNode +org.apache.maven.surefire.booter.ForkedBooter$1 +org.apache.maven.surefire.booter.spi.AbstractMasterProcessChannelProcessorFactory$2 +java.util.IdentityHashMap$IdentityHashMapIterator +java.util.IdentityHashMap$KeyIterator diff --git a/powertools-validation/src/main/resources/schemas/meta/applicator b/powertools-validation/src/main/resources/schemas/meta/applicator deleted file mode 100644 index 24a1cc4f4..000000000 --- a/powertools-validation/src/main/resources/schemas/meta/applicator +++ /dev/null @@ -1,56 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "https://json-schema.org/draft/2019-09/meta/applicator", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/applicator": true - }, - "$recursiveAnchor": true, - - "title": "Applicator vocabulary meta-schema", - "type": ["object", "boolean"], - "properties": { - "additionalItems": { "$recursiveRef": "#" }, - "unevaluatedItems": { "$recursiveRef": "#" }, - "items": { - "anyOf": [ - { "$recursiveRef": "#" }, - { "$ref": "#/$defs/schemaArray" } - ] - }, - "contains": { "$recursiveRef": "#" }, - "additionalProperties": { "$recursiveRef": "#" }, - "unevaluatedProperties": { "$recursiveRef": "#" }, - "properties": { - "type": "object", - "additionalProperties": { "$recursiveRef": "#" }, - "default": {} - }, - "patternProperties": { - "type": "object", - "additionalProperties": { "$recursiveRef": "#" }, - "propertyNames": { "format": "regex" }, - "default": {} - }, - "dependentSchemas": { - "type": "object", - "additionalProperties": { - "$recursiveRef": "#" - } - }, - "propertyNames": { "$recursiveRef": "#" }, - "if": { "$recursiveRef": "#" }, - "then": { "$recursiveRef": "#" }, - "else": { "$recursiveRef": "#" }, - "allOf": { "$ref": "#/$defs/schemaArray" }, - "anyOf": { "$ref": "#/$defs/schemaArray" }, - "oneOf": { "$ref": "#/$defs/schemaArray" }, - "not": { "$recursiveRef": "#" } - }, - "$defs": { - "schemaArray": { - "type": "array", - "minItems": 1, - "items": { "$recursiveRef": "#" } - } - } -} diff --git a/powertools-validation/src/main/resources/schemas/meta/content b/powertools-validation/src/main/resources/schemas/meta/content deleted file mode 100644 index f6752a8ef..000000000 --- a/powertools-validation/src/main/resources/schemas/meta/content +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "https://json-schema.org/draft/2019-09/meta/content", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/content": true - }, - "$recursiveAnchor": true, - - "title": "Content vocabulary meta-schema", - - "type": ["object", "boolean"], - "properties": { - "contentMediaType": { "type": "string" }, - "contentEncoding": { "type": "string" }, - "contentSchema": { "$recursiveRef": "#" } - } -} diff --git a/powertools-validation/src/main/resources/schemas/meta/core b/powertools-validation/src/main/resources/schemas/meta/core deleted file mode 100644 index eb708a560..000000000 --- a/powertools-validation/src/main/resources/schemas/meta/core +++ /dev/null @@ -1,57 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "https://json-schema.org/draft/2019-09/meta/core", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/core": true - }, - "$recursiveAnchor": true, - - "title": "Core vocabulary meta-schema", - "type": ["object", "boolean"], - "properties": { - "$id": { - "type": "string", - "format": "uri-reference", - "$comment": "Non-empty fragments not allowed.", - "pattern": "^[^#]*#?$" - }, - "$schema": { - "type": "string", - "format": "uri" - }, - "$anchor": { - "type": "string", - "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$" - }, - "$ref": { - "type": "string", - "format": "uri-reference" - }, - "$recursiveRef": { - "type": "string", - "format": "uri-reference" - }, - "$recursiveAnchor": { - "type": "boolean", - "default": false - }, - "$vocabulary": { - "type": "object", - "propertyNames": { - "type": "string", - "format": "uri" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "$comment": { - "type": "string" - }, - "$defs": { - "type": "object", - "additionalProperties": { "$recursiveRef": "#" }, - "default": {} - } - } -} diff --git a/powertools-validation/src/main/resources/schemas/meta/format b/powertools-validation/src/main/resources/schemas/meta/format deleted file mode 100644 index 09bbfdda9..000000000 --- a/powertools-validation/src/main/resources/schemas/meta/format +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "https://json-schema.org/draft/2019-09/meta/format", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/format": true - }, - "$recursiveAnchor": true, - - "title": "Format vocabulary meta-schema", - "type": ["object", "boolean"], - "properties": { - "format": { "type": "string" } - } -} diff --git a/powertools-validation/src/main/resources/schemas/meta/meta-data b/powertools-validation/src/main/resources/schemas/meta/meta-data deleted file mode 100644 index da04cff6d..000000000 --- a/powertools-validation/src/main/resources/schemas/meta/meta-data +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "https://json-schema.org/draft/2019-09/meta/meta-data", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/meta-data": true - }, - "$recursiveAnchor": true, - - "title": "Meta-data vocabulary meta-schema", - - "type": ["object", "boolean"], - "properties": { - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "default": true, - "deprecated": { - "type": "boolean", - "default": false - }, - "readOnly": { - "type": "boolean", - "default": false - }, - "writeOnly": { - "type": "boolean", - "default": false - }, - "examples": { - "type": "array", - "items": true - } - } -} diff --git a/powertools-validation/src/main/resources/schemas/meta/validation b/powertools-validation/src/main/resources/schemas/meta/validation deleted file mode 100644 index 9f59677b3..000000000 --- a/powertools-validation/src/main/resources/schemas/meta/validation +++ /dev/null @@ -1,98 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "https://json-schema.org/draft/2019-09/meta/validation", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/validation": true - }, - "$recursiveAnchor": true, - - "title": "Validation vocabulary meta-schema", - "type": ["object", "boolean"], - "properties": { - "multipleOf": { - "type": "number", - "exclusiveMinimum": 0 - }, - "maximum": { - "type": "number" - }, - "exclusiveMaximum": { - "type": "number" - }, - "minimum": { - "type": "number" - }, - "exclusiveMinimum": { - "type": "number" - }, - "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, - "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, - "pattern": { - "type": "string", - "format": "regex" - }, - "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, - "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, - "uniqueItems": { - "type": "boolean", - "default": false - }, - "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, - "minContains": { - "$ref": "#/$defs/nonNegativeInteger", - "default": 1 - }, - "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, - "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, - "required": { "$ref": "#/$defs/stringArray" }, - "dependentRequired": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/stringArray" - } - }, - "const": true, - "enum": { - "type": "array", - "items": true - }, - "type": { - "anyOf": [ - { "$ref": "#/$defs/simpleTypes" }, - { - "type": "array", - "items": { "$ref": "#/$defs/simpleTypes" }, - "minItems": 1, - "uniqueItems": true - } - ] - } - }, - "$defs": { - "nonNegativeInteger": { - "type": "integer", - "minimum": 0 - }, - "nonNegativeIntegerDefault0": { - "$ref": "#/$defs/nonNegativeInteger", - "default": 0 - }, - "simpleTypes": { - "enum": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ] - }, - "stringArray": { - "type": "array", - "items": { "type": "string" }, - "uniqueItems": true, - "default": [] - } - } -} diff --git a/powertools-validation/src/main/resources/schemas/meta_schema_V201909 b/powertools-validation/src/main/resources/schemas/meta_schema_V201909 deleted file mode 100644 index 2248a0c80..000000000 --- a/powertools-validation/src/main/resources/schemas/meta_schema_V201909 +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "https://json-schema.org/draft/2019-09/schema", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/core": true, - "https://json-schema.org/draft/2019-09/vocab/applicator": true, - "https://json-schema.org/draft/2019-09/vocab/validation": true, - "https://json-schema.org/draft/2019-09/vocab/meta-data": true, - "https://json-schema.org/draft/2019-09/vocab/format": false, - "https://json-schema.org/draft/2019-09/vocab/content": true - }, - "$recursiveAnchor": true, - - "title": "Core and Validation specifications meta-schema", - "allOf": [ - {"$ref": "meta/core"}, - {"$ref": "meta/applicator"}, - {"$ref": "meta/validation"}, - {"$ref": "meta/meta-data"}, - {"$ref": "meta/format"}, - {"$ref": "meta/content"} - ], - "type": ["object", "boolean"], - "properties": { - "definitions": { - "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.", - "type": "object", - "additionalProperties": { "$recursiveRef": "#" }, - "default": {} - }, - "dependencies": { - "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"", - "type": "object", - "additionalProperties": { - "anyOf": [ - { "$recursiveRef": "#" }, - { "$ref": "meta/validation#/$defs/stringArray" } - ] - } - } - } -} diff --git a/powertools-validation/src/main/resources/schemas/meta_schema_V4 b/powertools-validation/src/main/resources/schemas/meta_schema_V4 deleted file mode 100644 index bcbb84743..000000000 --- a/powertools-validation/src/main/resources/schemas/meta_schema_V4 +++ /dev/null @@ -1,149 +0,0 @@ -{ - "id": "http://json-schema.org/draft-04/schema#", - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Core schema meta-schema", - "definitions": { - "schemaArray": { - "type": "array", - "minItems": 1, - "items": { "$ref": "#" } - }, - "positiveInteger": { - "type": "integer", - "minimum": 0 - }, - "positiveIntegerDefault0": { - "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] - }, - "simpleTypes": { - "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] - }, - "stringArray": { - "type": "array", - "items": { "type": "string" }, - "minItems": 1, - "uniqueItems": true - } - }, - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "$schema": { - "type": "string" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "default": {}, - "multipleOf": { - "type": "number", - "minimum": 0, - "exclusiveMinimum": true - }, - "maximum": { - "type": "number" - }, - "exclusiveMaximum": { - "type": "boolean", - "default": false - }, - "minimum": { - "type": "number" - }, - "exclusiveMinimum": { - "type": "boolean", - "default": false - }, - "maxLength": { "$ref": "#/definitions/positiveInteger" }, - "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, - "pattern": { - "type": "string", - "format": "regex" - }, - "additionalItems": { - "anyOf": [ - { "type": "boolean" }, - { "$ref": "#" } - ], - "default": {} - }, - "items": { - "anyOf": [ - { "$ref": "#" }, - { "$ref": "#/definitions/schemaArray" } - ], - "default": {} - }, - "maxItems": { "$ref": "#/definitions/positiveInteger" }, - "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, - "uniqueItems": { - "type": "boolean", - "default": false - }, - "maxProperties": { "$ref": "#/definitions/positiveInteger" }, - "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, - "required": { "$ref": "#/definitions/stringArray" }, - "additionalProperties": { - "anyOf": [ - { "type": "boolean" }, - { "$ref": "#" } - ], - "default": {} - }, - "definitions": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "properties": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "patternProperties": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "dependencies": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { "$ref": "#" }, - { "$ref": "#/definitions/stringArray" } - ] - } - }, - "enum": { - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "type": { - "anyOf": [ - { "$ref": "#/definitions/simpleTypes" }, - { - "type": "array", - "items": { "$ref": "#/definitions/simpleTypes" }, - "minItems": 1, - "uniqueItems": true - } - ] - }, - "format": { "type": "string" }, - "allOf": { "$ref": "#/definitions/schemaArray" }, - "anyOf": { "$ref": "#/definitions/schemaArray" }, - "oneOf": { "$ref": "#/definitions/schemaArray" }, - "not": { "$ref": "#" } - }, - "dependencies": { - "exclusiveMaximum": [ "maximum" ], - "exclusiveMinimum": [ "minimum" ] - }, - "default": {} -} diff --git a/powertools-validation/src/main/resources/schemas/meta_schema_V6 b/powertools-validation/src/main/resources/schemas/meta_schema_V6 deleted file mode 100644 index bd3e763bc..000000000 --- a/powertools-validation/src/main/resources/schemas/meta_schema_V6 +++ /dev/null @@ -1,155 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-06/schema#", - "$id": "http://json-schema.org/draft-06/schema#", - "title": "Core schema meta-schema", - "definitions": { - "schemaArray": { - "type": "array", - "minItems": 1, - "items": { "$ref": "#" } - }, - "nonNegativeInteger": { - "type": "integer", - "minimum": 0 - }, - "nonNegativeIntegerDefault0": { - "allOf": [ - { "$ref": "#/definitions/nonNegativeInteger" }, - { "default": 0 } - ] - }, - "simpleTypes": { - "enum": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ] - }, - "stringArray": { - "type": "array", - "items": { "type": "string" }, - "uniqueItems": true, - "default": [] - } - }, - "type": ["object", "boolean"], - "properties": { - "$id": { - "type": "string", - "format": "uri-reference" - }, - "$schema": { - "type": "string", - "format": "uri" - }, - "$ref": { - "type": "string", - "format": "uri-reference" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "default": {}, - "examples": { - "type": "array", - "items": {} - }, - "multipleOf": { - "type": "number", - "exclusiveMinimum": 0 - }, - "maximum": { - "type": "number" - }, - "exclusiveMaximum": { - "type": "number" - }, - "minimum": { - "type": "number" - }, - "exclusiveMinimum": { - "type": "number" - }, - "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, - "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, - "pattern": { - "type": "string", - "format": "regex" - }, - "additionalItems": { "$ref": "#" }, - "items": { - "anyOf": [ - { "$ref": "#" }, - { "$ref": "#/definitions/schemaArray" } - ], - "default": {} - }, - "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, - "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, - "uniqueItems": { - "type": "boolean", - "default": false - }, - "contains": { "$ref": "#" }, - "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, - "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, - "required": { "$ref": "#/definitions/stringArray" }, - "additionalProperties": { "$ref": "#" }, - "definitions": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "properties": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "patternProperties": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "propertyNames": { "format": "regex" }, - "default": {} - }, - "dependencies": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { "$ref": "#" }, - { "$ref": "#/definitions/stringArray" } - ] - } - }, - "propertyNames": { "$ref": "#" }, - "const": {}, - "enum": { - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "type": { - "anyOf": [ - { "$ref": "#/definitions/simpleTypes" }, - { - "type": "array", - "items": { "$ref": "#/definitions/simpleTypes" }, - "minItems": 1, - "uniqueItems": true - } - ] - }, - "format": { "type": "string" }, - "allOf": { "$ref": "#/definitions/schemaArray" }, - "anyOf": { "$ref": "#/definitions/schemaArray" }, - "oneOf": { "$ref": "#/definitions/schemaArray" }, - "not": { "$ref": "#" } - }, - "default": {} -} diff --git a/powertools-validation/src/main/resources/schemas/meta_schema_V7 b/powertools-validation/src/main/resources/schemas/meta_schema_V7 deleted file mode 100644 index fb92c7f75..000000000 --- a/powertools-validation/src/main/resources/schemas/meta_schema_V7 +++ /dev/null @@ -1,172 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "http://json-schema.org/draft-07/schema#", - "title": "Core schema meta-schema", - "definitions": { - "schemaArray": { - "type": "array", - "minItems": 1, - "items": { "$ref": "#" } - }, - "nonNegativeInteger": { - "type": "integer", - "minimum": 0 - }, - "nonNegativeIntegerDefault0": { - "allOf": [ - { "$ref": "#/definitions/nonNegativeInteger" }, - { "default": 0 } - ] - }, - "simpleTypes": { - "enum": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ] - }, - "stringArray": { - "type": "array", - "items": { "type": "string" }, - "uniqueItems": true, - "default": [] - } - }, - "type": ["object", "boolean"], - "properties": { - "$id": { - "type": "string", - "format": "uri-reference" - }, - "$schema": { - "type": "string", - "format": "uri" - }, - "$ref": { - "type": "string", - "format": "uri-reference" - }, - "$comment": { - "type": "string" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "default": true, - "readOnly": { - "type": "boolean", - "default": false - }, - "writeOnly": { - "type": "boolean", - "default": false - }, - "examples": { - "type": "array", - "items": true - }, - "multipleOf": { - "type": "number", - "exclusiveMinimum": 0 - }, - "maximum": { - "type": "number" - }, - "exclusiveMaximum": { - "type": "number" - }, - "minimum": { - "type": "number" - }, - "exclusiveMinimum": { - "type": "number" - }, - "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, - "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, - "pattern": { - "type": "string", - "format": "regex" - }, - "additionalItems": { "$ref": "#" }, - "items": { - "anyOf": [ - { "$ref": "#" }, - { "$ref": "#/definitions/schemaArray" } - ], - "default": true - }, - "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, - "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, - "uniqueItems": { - "type": "boolean", - "default": false - }, - "contains": { "$ref": "#" }, - "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, - "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, - "required": { "$ref": "#/definitions/stringArray" }, - "additionalProperties": { "$ref": "#" }, - "definitions": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "properties": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "patternProperties": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "propertyNames": { "format": "regex" }, - "default": {} - }, - "dependencies": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { "$ref": "#" }, - { "$ref": "#/definitions/stringArray" } - ] - } - }, - "propertyNames": { "$ref": "#" }, - "const": true, - "enum": { - "type": "array", - "items": true, - "minItems": 1, - "uniqueItems": true - }, - "type": { - "anyOf": [ - { "$ref": "#/definitions/simpleTypes" }, - { - "type": "array", - "items": { "$ref": "#/definitions/simpleTypes" }, - "minItems": 1, - "uniqueItems": true - } - ] - }, - "format": { "type": "string" }, - "contentMediaType": { "type": "string" }, - "contentEncoding": { "type": "string" }, - "if": { "$ref": "#" }, - "then": { "$ref": "#" }, - "else": { "$ref": "#" }, - "allOf": { "$ref": "#/definitions/schemaArray" }, - "anyOf": { "$ref": "#/definitions/schemaArray" }, - "oneOf": { "$ref": "#/definitions/schemaArray" }, - "not": { "$ref": "#" } - }, - "default": true -} diff --git a/powertools-validation/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/powertools-validation/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000..bcaa3fabf --- /dev/null +++ b/powertools-validation/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.lambda.powertools.validation.internal.ValidationUserAgentInterceptor diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64GZipFunctionTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64GZipFunctionTest.java deleted file mode 100644 index 4fc0e57c5..000000000 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64GZipFunctionTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. - * 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 - * http://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. - * - */ -package software.amazon.lambda.powertools.validation; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeType; -import io.burt.jmespath.Expression; -import org.junit.jupiter.api.Test; - -import java.io.IOException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class Base64GZipFunctionTest { - - @Test - public void testPowertoolsGzip() throws IOException { - JsonNode event = ValidationConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/custom_event_gzip.json")); - Expression<JsonNode> expression = ValidationConfig.get().getJmesPath().compile("basket.powertools_base64_gzip(hiddenProduct)"); - JsonNode result = expression.search(event); - assertThat(result.getNodeType()).isEqualTo(JsonNodeType.STRING); - assertThat(result.asText()).isEqualTo("{ \"id\": 43242, \"name\": \"FooBar XY\", \"price\": 258}"); - } -} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationConfigTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationConfigTest.java new file mode 100644 index 000000000..bde195271 --- /dev/null +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationConfigTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.validation; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.Mockito.mock; + +import org.crac.Context; +import org.crac.Resource; +import org.junit.jupiter.api.Test; + +class ValidationConfigTest { + + ValidationConfig config = ValidationConfig.get(); + Context<Resource> context = mock(Context.class); + + @Test + void testBeforeCheckpointDoesNotThrowException() { + assertThatNoException().isThrownBy(() -> config.beforeCheckpoint(context)); + } + + @Test + void testAfterRestoreDoesNotThrowException() { + assertThatNoException().isThrownBy(() -> config.afterRestore(context)); + } +} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationUtilsTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationUtilsTest.java index d5e9332ed..73c3c6567 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationUtilsTest.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationUtilsTest.java @@ -1,25 +1,42 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + package software.amazon.lambda.powertools.validation; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static software.amazon.lambda.powertools.validation.ValidationUtils.getJsonSchema; +import static software.amazon.lambda.powertools.validation.ValidationUtils.validate; + import com.fasterxml.jackson.databind.JsonNode; import com.networknt.schema.JsonSchema; import com.networknt.schema.SpecVersion; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import software.amazon.lambda.powertools.validation.model.Basket; import software.amazon.lambda.powertools.validation.model.MyCustomEvent; import software.amazon.lambda.powertools.validation.model.Product; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.*; -import static software.amazon.lambda.powertools.validation.ValidationUtils.getJsonSchema; -import static software.amazon.lambda.powertools.validation.ValidationUtils.validate; - public class ValidationUtilsTest { - private JsonSchema schema = getJsonSchema("classpath:/schema_v7.json"); + private String schemaString = "classpath:/schema_v7.json"; + private JsonSchema schema = getJsonSchema(schemaString); @BeforeEach public void setup() { @@ -31,7 +48,7 @@ public void testLoadSchemaV7OK() { ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V7); JsonSchema jsonSchema = getJsonSchema("classpath:/schema_v7.json", true); assertThat(jsonSchema).isNotNull(); - assertThat(jsonSchema.getCurrentUri()).asString().isEqualTo("http://example.com/product.json"); + assertThat(jsonSchema.getId()).isEqualTo("http://example.com/product.json"); } @Test @@ -39,40 +56,52 @@ public void testLoadSchemaV7KO() { ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V7); assertThatThrownBy(() -> getJsonSchema("classpath:/schema_v7_ko.json", true)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("The schema classpath:/schema_v7_ko.json is not valid, it does not respect the specification V7"); + .hasMessage( + "The schema classpath:/schema_v7_ko.json is not valid, it does not respect the specification /draft-07/schema#"); } @Test public void testLoadMetaSchema_NoValidation() { - ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V201909); - getJsonSchema("classpath:/schemas/meta_schema_V201909", false); + ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V7); + + assertThatNoException().isThrownBy(() -> + { + getJsonSchema("classpath:/schema_v7_ko.json", false); + }); } @Test public void testLoadMetaSchemaV2019() { ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V201909); - JsonSchema jsonSchema = getJsonSchema("classpath:/schemas/meta_schema_V201909", true); + JsonSchema jsonSchema = getJsonSchema("classpath:/draft/2019-09/schema", true); + assertThat(jsonSchema).isNotNull(); + } + + @Test + public void testLoadMetaSchemaV2020() { + ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V202012); + JsonSchema jsonSchema = getJsonSchema("classpath:/draft/2020-12/schema", true); assertThat(jsonSchema).isNotNull(); } @Test public void testLoadMetaSchemaV7() { ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V7); - JsonSchema jsonSchema = getJsonSchema("classpath:/schemas/meta_schema_V7", true); + JsonSchema jsonSchema = getJsonSchema("classpath:/draft-07/schema", true); assertThat(jsonSchema).isNotNull(); } @Test public void testLoadMetaSchemaV6() { ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V6); - JsonSchema jsonSchema = getJsonSchema("classpath:/schemas/meta_schema_V6", true); + JsonSchema jsonSchema = getJsonSchema("classpath:/draft-06/schema", true); assertThat(jsonSchema).isNotNull(); } @Test public void testLoadMetaSchemaV4() { ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V4); - JsonSchema jsonSchema = getJsonSchema("classpath:/schemas/meta_schema_V4", true); + JsonSchema jsonSchema = getJsonSchema("classpath:/draft-04/schema", true); assertThat(jsonSchema).isNotNull(); } @@ -92,26 +121,35 @@ public void testLoadSchemaNotFound() { @Test public void testValidateJsonNodeOK() throws IOException { - JsonNode node = ValidationConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/json_ok.json")); + JsonNode node = + ValidationConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/json_ok.json")); - validate(node, schema); + assertThatNoException().isThrownBy(() -> + { + validate(node, schemaString); + }); } @Test public void testValidateJsonNodeKO() throws IOException { - JsonNode node = ValidationConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/json_ko.json")); + JsonNode node = + ValidationConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/json_ko.json")); assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(node, schema)); } @Test public void testValidateMapOK() { + Map<String, Object> map = new HashMap<>(); map.put("id", 43242); map.put("name", "FooBar XY"); map.put("price", 258); - validate(map, schema); + assertThatNoException().isThrownBy(() -> + { + validate(map, schemaString); + }); } @Test @@ -123,11 +161,22 @@ public void testValidateMapKO() { assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(map, schema)); } + @Test + public void testValidateMapNotValidJsonObject() { + Map<String, Object> map = new HashMap<>(); + map.put("1234", new Object()); + + assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(map, schema)); + } + @Test public void testValidateStringOK() { String json = "{\n \"id\": 43242,\n \"name\": \"FooBar XY\",\n \"price\": 258\n}"; - validate(json, schema); + assertThatNoException().isThrownBy(() -> + { + validate(json, schemaString); + }); } @Test @@ -140,14 +189,23 @@ public void testValidateStringKO() { @Test public void testValidateObjectOK() { Product product = new Product(42, "FooBar", 42); - validate(product, schema); + + assertThatNoException().isThrownBy(() -> + { + validate(product, schemaString); + }); } @Test public void testValidateObjectKO() { - Product product = new Product(42, "FooBar", -12); - assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(product, schema)); + assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(new Object(), schema)); + } + + @Test + public void testValidateObjectNotValidJson() { + + assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(new Object(), schema)); } @Test @@ -158,7 +216,11 @@ public void testValidateSubObjectOK() { basket.add(product); basket.add(product2); MyCustomEvent event = new MyCustomEvent(basket); - validate(event, schema, "basket.products[0]"); + + assertThatNoException().isThrownBy(() -> + { + validate(event, schemaString, "basket.products[0]"); + }); } @Test @@ -170,7 +232,8 @@ public void testValidateSubObjectKO() { basket.add(product2); MyCustomEvent event = new MyCustomEvent(basket); - assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(event, schema, "basket.products[0]")); + assertThatExceptionOfType(ValidationException.class).isThrownBy( + () -> validate(event, schema, "basket.products[0]")); } @Test @@ -182,7 +245,7 @@ public void testValidateSubObjectListOK() { basket.add(product2); MyCustomEvent event = new MyCustomEvent(basket); - validate(event, schema, "basket.products[*]"); + assertThatNoException().isThrownBy(() -> validate(event, schema, "basket.products[*]")); } @Test @@ -194,7 +257,8 @@ public void testValidateSubObjectListKO() { basket.add(product2); MyCustomEvent event = new MyCustomEvent(basket); - assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(event, schema, "basket.products[*]")); + assertThatExceptionOfType(ValidationException.class).isThrownBy( + () -> validate(event, schema, "basket.products[*]")); } @Test @@ -203,7 +267,7 @@ public void testValidateSubObjectNotFound() { Basket basket = new Basket(); basket.add(product); MyCustomEvent event = new MyCustomEvent(basket); - assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(event, schema, "basket.product")); + assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(event, schema, "basket.")); } @Test @@ -226,7 +290,7 @@ public void testValidateSubObjectJsonString() { basket.setHiddenProduct("ewogICJpZCI6IDQzMjQyLAogICJuYW1lIjogIkZvb0JhciBYWSIsCiAgInByaWNlIjogMjU4Cn0="); MyCustomEvent event = new MyCustomEvent(basket); - validate(event, schema, "basket.powertools_base64(hiddenProduct)"); + assertThatNoException().isThrownBy(() -> validate(event, schema, "basket.powertools_base64(hiddenProduct)")); } @Test @@ -243,7 +307,13 @@ public void testValidateSubObjectSimpleString() { @Test public void testValidateSubObjectWithoutEnvelope() { Product product = new Product(42, "BarBazFoo", 42); - validate(product, schema, null); + assertThatNoException().isThrownBy(() -> validate(product, schema, null)); + } + + @Test + public void testValidateSubObjectWithEmptyEnvelope() { + Product product = new Product(42, "BarBazFoo", 42); + assertThatNoException().isThrownBy(() -> validate(product, schema, "")); } } diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/GenericSchemaV7APIGatewayProxyRequestEventHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/GenericSchemaV7APIGatewayProxyRequestEventHandler.java new file mode 100644 index 000000000..74e8605a5 --- /dev/null +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/GenericSchemaV7APIGatewayProxyRequestEventHandler.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.validation.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + +import software.amazon.lambda.powertools.validation.Validation; + +public class GenericSchemaV7APIGatewayProxyRequestEventHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { + + @Validation(inboundSchema = "classpath:/schema_v7.json") + @Override + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); + response.setBody("valid-test"); + response.setStatusCode(200); + return response; + } +} \ No newline at end of file diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/GenericSchemaV7StringHandler.java similarity index 78% rename from powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java rename to powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/GenericSchemaV7StringHandler.java index cd6719b0f..ab0645f29 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/GenericSchemaV7StringHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,18 +11,19 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; + import software.amazon.lambda.powertools.validation.Validation; -public class SQSHandler implements RequestHandler<SQSEvent, String> { +public class GenericSchemaV7StringHandler<T> implements RequestHandler<T, String> { - @Override @Validation(inboundSchema = "classpath:/schema_v7.json") - public String handleRequest(SQSEvent input, Context context) { + @Override + public String handleRequest(T input, Context context) { return "OK"; } -} +} \ No newline at end of file diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandlerWithError.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandlerWithError.java new file mode 100644 index 000000000..e6e702fb6 --- /dev/null +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandlerWithError.java @@ -0,0 +1,34 @@ +/* + * Copyright 2024 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.validation.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; +import java.util.ArrayList; +import software.amazon.lambda.powertools.validation.Validation; + +public class KinesisHandlerWithError implements RequestHandler<KinesisEvent, StreamsEventResponse> { + + @Override + @Validation(inboundSchema = "classpath:/schema_v7.json") + public StreamsEventResponse handleRequest(KinesisEvent input, Context context) { + StreamsEventResponse response = StreamsEventResponse.builder().withBatchItemFailures(new ArrayList<>()).build(); + assert input.getRecords().size() == 2; // invalid messages have been removed from the input + response.getBatchItemFailures().add(StreamsEventResponse.BatchItemFailure.builder().withItemIdentifier("1234").build()); + return response; + } +} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/MyCustomEventHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/MyCustomEventHandler.java index 07954ddff..6989cdbb6 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/MyCustomEventHandler.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/MyCustomEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation.handlers; import com.amazonaws.services.lambda.runtime.Context; diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandlerWithError.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandlerWithError.java new file mode 100644 index 000000000..23fceab5b --- /dev/null +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandlerWithError.java @@ -0,0 +1,34 @@ +/* + * Copyright 2024 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.validation.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import java.util.ArrayList; +import software.amazon.lambda.powertools.validation.Validation; + +public class SQSHandlerWithError implements RequestHandler<SQSEvent, SQSBatchResponse> { + + @Override + @Validation(inboundSchema = "classpath:/schema_v7.json") + public SQSBatchResponse handleRequest(SQSEvent input, Context context) { + SQSBatchResponse response = SQSBatchResponse.builder().withBatchItemFailures(new ArrayList<>()).build(); + assert input.getRecords().size() == 2; // invalid messages have been removed from the input + response.getBatchItemFailures().add(SQSBatchResponse.BatchItemFailure.builder().withItemIdentifier("1234").build()); + return response; + } +} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundClasspathHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSWithCustomEnvelopeHandler.java similarity index 67% rename from powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundClasspathHandler.java rename to powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSWithCustomEnvelopeHandler.java index 59b6cc7b5..c49ebff69 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundClasspathHandler.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSWithCustomEnvelopeHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,19 +11,19 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; import software.amazon.lambda.powertools.validation.Validation; - -public class ValidationInboundClasspathHandler implements RequestHandler<APIGatewayProxyRequestEvent, String> { +public class SQSWithCustomEnvelopeHandler implements RequestHandler<SQSEvent, String> { @Override - @Validation(inboundSchema = "classpath:/schema_v7.json") - public String handleRequest(APIGatewayProxyRequestEvent input, Context context) { + @Validation(inboundSchema = "classpath:/schema_v7.json", envelope = "Records[*].powertools_json(body).powertools_json(Message)") + public String handleRequest(SQSEvent input, Context context) { return "OK"; } } diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSWithWrongEnvelopeHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSWithWrongEnvelopeHandler.java new file mode 100644 index 000000000..d3f46d4ad --- /dev/null +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSWithWrongEnvelopeHandler.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.validation.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import software.amazon.lambda.powertools.validation.Validation; + +public class SQSWithWrongEnvelopeHandler implements RequestHandler<SQSEvent, String> { + + @Override + // real event contains Records with big R (https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html) + @Validation(inboundSchema = "classpath:/schema_v7.json", envelope = "records[*].powertools_json(body).powertools_json(Message)") + public String handleRequest(SQSEvent input, Context context) { + return "OK"; + } +} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/StandardKinesisHandler.java similarity index 64% rename from powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java rename to powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/StandardKinesisHandler.java index 7132fcb9b..1afc5c5ec 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/StandardKinesisHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2024 Amazon.com, Inc. or its affiliates. * 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 @@ -11,18 +11,22 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; import software.amazon.lambda.powertools.validation.Validation; -public class KinesisHandler implements RequestHandler<KinesisEvent, String> { +public class StandardKinesisHandler implements RequestHandler<KinesisEvent, StreamsEventResponse> { - @Validation(inboundSchema = "classpath:/schema_v7.json") @Override - public String handleRequest(KinesisEvent input, Context context) { - return "OK"; + @Validation(inboundSchema = "classpath:/schema_v7.json") + public StreamsEventResponse handleRequest(KinesisEvent input, Context context) { + StreamsEventResponse response = StreamsEventResponse.builder().build(); + assert input.getRecords().size() == 2; // invalid messages have been removed from the input + return response; } } diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/StandardSQSHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/StandardSQSHandler.java new file mode 100644 index 000000000..e0f0ece2d --- /dev/null +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/StandardSQSHandler.java @@ -0,0 +1,32 @@ +/* + * Copyright 2024 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.validation.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import software.amazon.lambda.powertools.validation.Validation; + +public class StandardSQSHandler implements RequestHandler<SQSEvent, SQSBatchResponse> { + + @Override + @Validation(inboundSchema = "classpath:/schema_v7.json") + public SQSBatchResponse handleRequest(SQSEvent input, Context context) { + SQSBatchResponse response = SQSBatchResponse.builder().build(); + assert input.getRecords().size() == 2; // invalid messages have been removed from the input + return response; + } +} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundStringHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundAPIGatewayV2HTTPEventHandler.java similarity index 85% rename from powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundStringHandler.java rename to powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundAPIGatewayV2HTTPEventHandler.java index e27f31129..b8c67b1eb 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundStringHandler.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundAPIGatewayV2HTTPEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,15 +11,18 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; + import software.amazon.lambda.powertools.validation.Validation; -public class ValidationInboundStringHandler implements RequestHandler<APIGatewayV2HTTPEvent, String> { +public class ValidationInboundAPIGatewayV2HTTPEventHandler implements RequestHandler<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> { private static final String schema = "{\n" + " \"$schema\": \"http://json-schema.org/draft-07/schema\",\n" + @@ -76,10 +79,13 @@ public class ValidationInboundStringHandler implements RequestHandler<APIGateway " },\n" + " \"additionalProperties\": true\n" + "}"; - + @Override @Validation(inboundSchema = schema) - public String handleRequest(APIGatewayV2HTTPEvent input, Context context) { - return "OK"; + public APIGatewayV2HTTPResponse handleRequest(APIGatewayV2HTTPEvent input, Context context) { + APIGatewayV2HTTPResponse response = new APIGatewayV2HTTPResponse(); + response.setBody("valid-test"); + response.setStatusCode(200); + return response; } } diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/HandledResponseEventsArgumentsProvider.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/HandledResponseEventsArgumentsProvider.java new file mode 100644 index 000000000..728e0ae88 --- /dev/null +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/HandledResponseEventsArgumentsProvider.java @@ -0,0 +1,64 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.validation.internal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; + +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; + +/** + * Provides test arguments that are used in unit tests. + * It creates API Gateway response arguments that can be used to confirm + * that @Validation validates responses and returns a response's headers even + * when validation fails + */ +public class HandledResponseEventsArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream<? extends Arguments> provideArguments(ExtensionContext context) { + + String body = "{id"; + + Map<String, String> headers = new HashMap<>(); + headers.put("header1", "value1,value2,value3"); + Map<String, List<String>> headersList = new HashMap<>(); + List<String> headerValues = new ArrayList<>(); + headerValues.add("value1"); + headerValues.add("value2"); + headerValues.add("value3"); + headersList.put("header1", headerValues); + + final APIGatewayProxyResponseEvent apiGWProxyResponseEvent = new APIGatewayProxyResponseEvent() + .withBody(body) + .withHeaders(headers) + .withMultiValueHeaders(headersList); + + APIGatewayV2HTTPResponse apiGWV2HTTPResponse = new APIGatewayV2HTTPResponse(); + apiGWV2HTTPResponse.setBody(body); + apiGWV2HTTPResponse.setHeaders(headers); + apiGWV2HTTPResponse.setMultiValueHeaders(headersList); + + return Stream.of(apiGWProxyResponseEvent, apiGWV2HTTPResponse).map(Arguments::of); + } +} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ResponseEventsArgumentsProvider.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ResponseEventsArgumentsProvider.java new file mode 100644 index 000000000..74803a05a --- /dev/null +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ResponseEventsArgumentsProvider.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ + +package software.amazon.lambda.powertools.validation.internal; + +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2WebSocketResponse; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsInputPreprocessingResponse; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; + +public class ResponseEventsArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream<? extends Arguments> provideArguments(ExtensionContext context) { + + String body = "{id"; + + APIGatewayV2WebSocketResponse apiGWV2WebSocketResponse = new APIGatewayV2WebSocketResponse(); + apiGWV2WebSocketResponse.setBody(body); + + ApplicationLoadBalancerResponseEvent albResponseEvent = new ApplicationLoadBalancerResponseEvent(); + albResponseEvent.setBody(body); + + KinesisAnalyticsInputPreprocessingResponse kaipResponse = new KinesisAnalyticsInputPreprocessingResponse(); + List records = new ArrayList<KinesisAnalyticsInputPreprocessingResponse.Record>(); + ByteBuffer buffer = ByteBuffer.wrap(body.getBytes(StandardCharsets.UTF_8)); + records.add(new KinesisAnalyticsInputPreprocessingResponse.Record("1", + KinesisAnalyticsInputPreprocessingResponse.Result.Ok, buffer)); + kaipResponse.setRecords(records); + + return Stream.of(apiGWV2WebSocketResponse, albResponseEvent, + kaipResponse).map(Arguments::of); + } +} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java index 2bbe7cdaa..42a18307e 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,106 +11,341 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation.internal; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.when; + import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; +import com.amazonaws.services.lambda.runtime.events.ActiveMQEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent; +import com.amazonaws.services.lambda.runtime.events.KafkaEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent; import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; +import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; +import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; +import com.amazonaws.services.lambda.runtime.tests.annotations.Event; +import com.networknt.schema.SpecVersion; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import software.amazon.lambda.powertools.validation.ValidationException; +import software.amazon.lambda.powertools.validation.Validation; import software.amazon.lambda.powertools.validation.ValidationConfig; -import software.amazon.lambda.powertools.validation.handlers.*; +import software.amazon.lambda.powertools.validation.ValidationException; +import software.amazon.lambda.powertools.validation.handlers.GenericSchemaV7APIGatewayProxyRequestEventHandler; +import software.amazon.lambda.powertools.validation.handlers.GenericSchemaV7StringHandler; +import software.amazon.lambda.powertools.validation.handlers.KinesisHandlerWithError; +import software.amazon.lambda.powertools.validation.handlers.SQSHandlerWithError; +import software.amazon.lambda.powertools.validation.handlers.SQSWithCustomEnvelopeHandler; +import software.amazon.lambda.powertools.validation.handlers.SQSWithWrongEnvelopeHandler; +import software.amazon.lambda.powertools.validation.handlers.StandardKinesisHandler; +import software.amazon.lambda.powertools.validation.handlers.StandardSQSHandler; +import software.amazon.lambda.powertools.validation.handlers.ValidationInboundAPIGatewayV2HTTPEventHandler; import software.amazon.lambda.powertools.validation.model.MyCustomEvent; -import java.io.IOException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -public class ValidationAspectTest { +class ValidationAspectTest { + @Mock + Validation validation; + @Mock + Signature signature; @Mock private Context context; + @Mock + private ProceedingJoinPoint pjp; + private ValidationAspect validationAspect = new ValidationAspect(); + + private static Stream<Arguments> provideEventAndEventType() { + return Stream.of( + Arguments.of("/sns_event.json", SNSEvent.class), + Arguments.of("/scheduled_event.json", ScheduledEvent.class), + Arguments.of("/alb_event.json", ApplicationLoadBalancerRequestEvent.class), + Arguments.of("/cwl_event.json", CloudWatchLogsEvent.class), + Arguments.of("/cfcr_event.json", CloudFormationCustomResourceEvent.class), + Arguments.of("/kf_event.json", KinesisFirehoseEvent.class), + Arguments.of("/kafka_event.json", KafkaEvent.class), + Arguments.of("/amq_event.json", ActiveMQEvent.class), + Arguments.of("/rabbitmq_event.json", RabbitMQEvent.class), + Arguments.of("/kafip_event.json", KinesisAnalyticsFirehoseInputPreprocessingEvent.class), + Arguments.of("/kasip_event.json", KinesisAnalyticsStreamsInputPreprocessingEvent.class), + Arguments.of("/custom_event.json", MyCustomEvent.class) + + ); + } @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); } + @ParameterizedTest + @ArgumentsSource(ResponseEventsArgumentsProvider.class) + void testValidateOutboundJsonSchemaWithExceptions(Object object) throws Throwable { + when(validation.schemaVersion()).thenReturn(SpecVersion.VersionFlag.V7); + when(pjp.getSignature()).thenReturn(signature); + when(pjp.getSignature().getDeclaringType()).thenReturn(RequestHandler.class); + Object[] args = {new Object(), context}; + when(pjp.getArgs()).thenReturn(args); + when(pjp.proceed(args)).thenReturn(object); + when(validation.inboundSchema()).thenReturn(""); + when(validation.outboundSchema()).thenReturn("classpath:/schema_v7.json"); + + assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> + { + validationAspect.around(pjp, validation); + }); + } + + @ParameterizedTest + @ArgumentsSource(HandledResponseEventsArgumentsProvider.class) + void testValidateOutboundJsonSchemaWithHandledExceptions(Object object) throws Throwable { + when(validation.schemaVersion()).thenReturn(SpecVersion.VersionFlag.V7); + when(pjp.getSignature()).thenReturn(signature); + when(pjp.getSignature().getDeclaringType()).thenReturn(RequestHandler.class); + Object[] args = {new Object(), context}; + when(pjp.getArgs()).thenReturn(args); + when(pjp.proceed(args)).thenReturn(object); + when(validation.inboundSchema()).thenReturn(""); + when(validation.outboundSchema()).thenReturn("classpath:/schema_v7.json"); + + Object response = validationAspect.around(pjp, validation); + assertThat(response).isInstanceOfAny(APIGatewayProxyResponseEvent.class, APIGatewayV2HTTPResponse.class); + + List<String> headerValues = new ArrayList<>(); + headerValues.add("value1"); + headerValues.add("value2"); + headerValues.add("value3"); + + if (response instanceof APIGatewayProxyResponseEvent) { + assertThat(response).isInstanceOfSatisfying(APIGatewayProxyResponseEvent.class, t -> { + assertThat(t.getStatusCode()).isEqualTo(400); + assertThat(t.getBody()).isNotBlank(); + assertThat(t.getIsBase64Encoded()).isFalse(); + assertThat(t.getHeaders()).containsEntry("header1", "value1,value2,value3"); + assertThat(t.getMultiValueHeaders()).containsEntry("header1", headerValues); + }); + } else if (response instanceof APIGatewayV2HTTPResponse) { + assertThat(response).isInstanceOfSatisfying(APIGatewayV2HTTPResponse.class, t -> { + assertThat(t.getStatusCode()).isEqualTo(400); + assertThat(t.getBody()).isNotBlank(); + assertThat(t.getIsBase64Encoded()).isFalse(); + assertThat(t.getHeaders()).containsEntry("header1", "value1,value2,value3"); + assertThat(t.getMultiValueHeaders()).containsEntry("header1", headerValues); + }); + } else { + fail(); + } + } + @Test - public void validate_inputOK_schemaInClasspath_shouldValidate() { - ValidationInboundClasspathHandler handler = new ValidationInboundClasspathHandler(); - APIGatewayProxyRequestEvent event = new APIGatewayProxyRequestEvent(); - event.setBody("{" + + void testValidateOutboundJsonSchema_APIGWV2() throws Throwable { + when(validation.schemaVersion()).thenReturn(SpecVersion.VersionFlag.V7); + when(pjp.getSignature()).thenReturn(signature); + when(pjp.getSignature().getDeclaringType()).thenReturn(RequestHandler.class); + Object[] args = {new Object(), context}; + when(pjp.getArgs()).thenReturn(args); + APIGatewayV2HTTPResponse apiGatewayV2HTTPResponse = new APIGatewayV2HTTPResponse(); + apiGatewayV2HTTPResponse.setBody("{" + " \"id\": 1," + " \"name\": \"Lampshade\"," + " \"price\": 42" + "}"); - assertThat(handler.handleRequest(event, context)).isEqualTo("OK"); + when(pjp.proceed(args)).thenReturn(apiGatewayV2HTTPResponse); + when(validation.inboundSchema()).thenReturn(""); + when(validation.outboundSchema()).thenReturn("classpath:/schema_v7.json"); + + assertThatNoException().isThrownBy(() -> validationAspect.around(pjp, validation)); } @Test - public void validate_inputKO_schemaInClasspath_shouldThrowValidationException() { - ValidationInboundClasspathHandler handler = new ValidationInboundClasspathHandler(); + void validate_inputOK_schemaInClasspath_shouldValidate() { + GenericSchemaV7APIGatewayProxyRequestEventHandler handler = new GenericSchemaV7APIGatewayProxyRequestEventHandler(); APIGatewayProxyRequestEvent event = new APIGatewayProxyRequestEvent(); event.setBody("{" + " \"id\": 1," + " \"name\": \"Lampshade\"," + - " \"price\": -2" + + " \"price\": 42" + "}"); + + + APIGatewayProxyResponseEvent response = handler.handleRequest(event, context); + assertThat(response.getBody()).isEqualTo("valid-test"); + assertThat(response.getStatusCode()).isEqualTo(200); + + } + + @Test + void validate_inputKO_schemaInClasspath_shouldThrowValidationException() { + GenericSchemaV7APIGatewayProxyRequestEventHandler handler = new GenericSchemaV7APIGatewayProxyRequestEventHandler(); + + Map<String, String> headers = new HashMap<>(); + headers.put("header1", "value1"); + Map<String, List<String>> headersList = new HashMap<>(); + List<String> headerValues = new ArrayList<>(); + headerValues.add("value1"); + headersList.put("header1", headerValues); + + APIGatewayProxyRequestEvent event = new APIGatewayProxyRequestEvent(); + event.setBody("{" + + " \"id\": 1," + + " \"name\": \"Lampshade\"," + + " \"price\": -2" + + "}"); + event.setHeaders(headers); + event.setMultiValueHeaders(headersList); + // price is negative - assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> handler.handleRequest(event, context)); + APIGatewayProxyResponseEvent response = handler.handleRequest(event, context); + assertThat(response.getBody()).isNotBlank(); + assertThat(response.getStatusCode()).isEqualTo(400); + assertThat(response.getHeaders()).isEmpty(); + assertThat(response.getMultiValueHeaders()).isEmpty(); } @Test - public void validate_inputOK_schemaInString_shouldValidate() { - ValidationInboundStringHandler handler = new ValidationInboundStringHandler(); + void validate_inputOK_schemaInString_shouldValidate() { + ValidationInboundAPIGatewayV2HTTPEventHandler handler = new ValidationInboundAPIGatewayV2HTTPEventHandler(); APIGatewayV2HTTPEvent event = new APIGatewayV2HTTPEvent(); event.setBody("{" + - " \"id\": 1," + - " \"name\": \"Lampshade\"," + - " \"price\": 42" + - "}"); - assertThat(handler.handleRequest(event, context)).isEqualTo("OK"); + " \"id\": 1," + + " \"name\": \"Lampshade\"," + + " \"price\": 42" + + "}"); + + APIGatewayV2HTTPResponse response = handler.handleRequest(event, context); + assertThat(response.getBody()).isEqualTo("valid-test"); + assertThat(response.getStatusCode()).isEqualTo(200); } + @Test - public void validate_inputKO_schemaInString_shouldThrowValidationException() { - ValidationInboundStringHandler handler = new ValidationInboundStringHandler(); + void validate_inputKO_schemaInString_shouldThrowValidationException() { + ValidationInboundAPIGatewayV2HTTPEventHandler handler = new ValidationInboundAPIGatewayV2HTTPEventHandler(); + + Map<String, String> headers = new HashMap<>(); + headers.put("header1", "value1"); + APIGatewayV2HTTPEvent event = new APIGatewayV2HTTPEvent(); event.setBody("{" + - " \"id\": 1," + - " \"name\": \"Lampshade\"" + - "}"); - assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> handler.handleRequest(event, context)); + " \"id\": 1," + + " \"name\": \"Lampshade\"" + + "}"); + event.setHeaders(headers); + + APIGatewayV2HTTPResponse response = handler.handleRequest(event, context); + assertThat(response.getBody()).isNotBlank(); + assertThat(response.getStatusCode()).isEqualTo(400); + assertThat(response.getHeaders()).isEmpty(); + assertThat(response.getMultiValueHeaders()).isEmpty(); } - @Test - public void validate_SQS() throws IOException { - SQSEvent event = ValidationConfig.get().getObjectMapper().readValue(this.getClass().getResourceAsStream("/sqs.json"), SQSEvent.class); + @ParameterizedTest + @Event(value = "sqs.json", type = SQSEvent.class) + void validate_SQS(SQSEvent event) { + GenericSchemaV7StringHandler<Object> handler = new GenericSchemaV7StringHandler<>(); + assertThat(handler.handleRequest(event, context)).isEqualTo("OK"); + } + + @ParameterizedTest + @Event(value = "sqs_invalid_messages.json", type = SQSEvent.class) + void validate_SQS_with_validation_partial_failure(SQSEvent event) { + StandardSQSHandler handler = new StandardSQSHandler(); + SQSBatchResponse response = handler.handleRequest(event, context); + assertThat(response.getBatchItemFailures()).hasSize(2); + assertThat(response.getBatchItemFailures().stream().map(SQSBatchResponse.BatchItemFailure::getItemIdentifier).collect( + Collectors.toList())).contains("d9144555-9a4f-4ec3-99a0-fc4e625a8db3", "d9144555-9a4f-4ec3-99a0-fc4e625a8db5"); + } + + @ParameterizedTest + @Event(value = "sqs_invalid_messages.json", type = SQSEvent.class) + void validate_SQS_with_partial_failure(SQSEvent event) { + SQSHandlerWithError handler = new SQSHandlerWithError(); + SQSBatchResponse response = handler.handleRequest(event, context); + assertThat(response.getBatchItemFailures()).hasSize(3); + assertThat(response.getBatchItemFailures().stream().map(SQSBatchResponse.BatchItemFailure::getItemIdentifier).collect( + Collectors.toList())).contains("d9144555-9a4f-4ec3-99a0-fc4e625a8db3", "d9144555-9a4f-4ec3-99a0-fc4e625a8db5", "1234"); + } - SQSHandler handler = new SQSHandler(); + @ParameterizedTest + @Event(value = "sqs_message.json", type = SQSEvent.class) + void validate_SQS_CustomEnvelopeTakePrecedence(SQSEvent event) { + SQSWithCustomEnvelopeHandler handler = new SQSWithCustomEnvelopeHandler(); assertThat(handler.handleRequest(event, context)).isEqualTo("OK"); } - @Test - public void validate_Kinesis() throws IOException { - KinesisEvent event = ValidationConfig.get().getObjectMapper().readValue(this.getClass().getResourceAsStream("/kinesis.json"), KinesisEvent.class); + @ParameterizedTest + @Event(value = "sqs_message.json", type = SQSEvent.class) + void validate_SQS_WrongEnvelope_shouldThrowValidationException(SQSEvent event) { + SQSWithWrongEnvelopeHandler handler = new SQSWithWrongEnvelopeHandler(); + assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> handler.handleRequest(event, context)); + } - KinesisHandler handler = new KinesisHandler(); + @ParameterizedTest + @Event(value = "kinesis.json", type = KinesisEvent.class) + void validate_Kinesis(KinesisEvent event) { + GenericSchemaV7StringHandler<Object> handler = new GenericSchemaV7StringHandler<>(); assertThat(handler.handleRequest(event, context)).isEqualTo("OK"); } - @Test - public void validate_CustomObject() throws IOException { - MyCustomEvent event = ValidationConfig.get().getObjectMapper().readValue(this.getClass().getResourceAsStream("/custom_event.json"), MyCustomEvent.class); + @ParameterizedTest + @Event(value = "kinesis_invalid_messages.json", type = KinesisEvent.class) + void validate_Kinesis_with_validation_partial_failure(KinesisEvent event) { + StandardKinesisHandler handler = new StandardKinesisHandler(); + StreamsEventResponse response = handler.handleRequest(event, context); + assertThat(response.getBatchItemFailures()).hasSize(2); + assertThat(response.getBatchItemFailures().stream().map(StreamsEventResponse.BatchItemFailure::getItemIdentifier).collect( + Collectors.toList())).contains("49545115243490985018280067714973144582180062593244200962", "49545115243490985018280067714973144582180062593244200964"); + } + + @ParameterizedTest + @Event(value = "kinesis_invalid_messages.json", type = KinesisEvent.class) + void validate_Kinesis_with_partial_failure(KinesisEvent event) { + KinesisHandlerWithError handler = new KinesisHandlerWithError(); + StreamsEventResponse response = handler.handleRequest(event, context); + assertThat(response.getBatchItemFailures()).hasSize(3); + assertThat(response.getBatchItemFailures().stream().map(StreamsEventResponse.BatchItemFailure::getItemIdentifier).collect( + Collectors.toList())).contains("49545115243490985018280067714973144582180062593244200962", "49545115243490985018280067714973144582180062593244200964", "1234"); + } + + @ParameterizedTest + @MethodSource("provideEventAndEventType") + void validateEEvent(String jsonResource, Class eventClass) throws IOException { + Object event = ValidationConfig.get().getObjectMapper() + .readValue(this.getClass().getResourceAsStream(jsonResource), eventClass); - MyCustomEventHandler handler = new MyCustomEventHandler(); + GenericSchemaV7StringHandler<Object> handler = new GenericSchemaV7StringHandler<>(); assertThat(handler.handleRequest(event, context)).isEqualTo("OK"); } } diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationUserAgentInterceptorTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationUserAgentInterceptorTest.java new file mode 100644 index 000000000..9bacf33f9 --- /dev/null +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationUserAgentInterceptorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 + * http://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. + * + */ +package software.amazon.lambda.powertools.validation.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import static org.assertj.core.api.Assertions.assertThat; + +class ValidationUserAgentInterceptorTest { + + @Test + void shouldConfigureUserAgentWhenCreatingAwsSdkClient() { + // WHEN creating an AWS SDK client, the interceptor should be loaded + // We use S3 client but it can be any arbitrary AWS SDK client + S3Client.builder().region(Region.US_EAST_1).build(); + + // THEN the user agent system property should be set + String userAgent = System.getProperty("sdk.ua.appId"); + assertThat(userAgent).contains("PT/VALIDATION/"); + } +} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java index 548ef4660..881090bdc 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation.model; import java.util.ArrayList; @@ -21,6 +22,9 @@ public class Basket { private String hiddenProduct; + public Basket() { + } + public List<Product> getProducts() { return products; } @@ -29,9 +33,6 @@ public void setProducts(List<Product> products) { this.products = products; } - public Basket() { - } - public void add(Product product) { products.add(product); } diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/MyCustomEvent.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/MyCustomEvent.java index 12f3f99ca..04c7c3a4a 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/MyCustomEvent.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/MyCustomEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation.model; public class MyCustomEvent { diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Product.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Product.java index fde888b76..93f5ab39f 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Product.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Product.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. + * Copyright 2023 Amazon.com, Inc. or its affiliates. * 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 @@ -11,6 +11,7 @@ * limitations under the License. * */ + package software.amazon.lambda.powertools.validation.model; public class Product { diff --git a/powertools-validation/src/test/resources/alb_event.json b/powertools-validation/src/test/resources/alb_event.json new file mode 100644 index 000000000..d2b0d3cda --- /dev/null +++ b/powertools-validation/src/test/resources/alb_event.json @@ -0,0 +1,28 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a" + } + }, + "httpMethod": "GET", + "path": "/lambda", + "queryStringParameters": { + "query": "1234ABCD" + }, + "headers": { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "accept-encoding": "gzip", + "accept-language": "en-US,en;q=0.9", + "connection": "keep-alive", + "host": "lambda-alb-123578498.us-east-1.elb.amazonaws.com", + "upgrade-insecure-requests": "1", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", + "x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476", + "x-forwarded-for": "72.12.164.125", + "x-forwarded-port": "80", + "x-forwarded-proto": "http", + "x-imforwards": "20" + }, + "body": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/amq_event.json b/powertools-validation/src/test/resources/amq_event.json new file mode 100644 index 000000000..2f29bdc9f --- /dev/null +++ b/powertools-validation/src/test/resources/amq_event.json @@ -0,0 +1,28 @@ +{ + "eventSource": "aws:mq", + "eventSourceArn": "arn:aws:mq:us-west-2:111122223333:broker:test:b-9bcfa592-423a-4942-879d-eb284b418fc8", + "messages": [ + { + "messageID": "ID:b-9bcfa592-423a-4942-879d-eb284b418fc8-1.mq.us-west-2.amazonaws.com-37557-1234520418293-4:1:1:1:1", + "messageType": "jms/text-message", + "deliveryMode": 1, + "replyTo": null, + "type": null, + "expiration": "60000", + "priority": 1, + "redelivered": false, + "destination": { + "physicalName": "testQueue" + }, + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==", + "timestamp": 1598827811958, + "brokerInTime": 1598827811958, + "brokerOutTime": 1598827811959, + "properties": { + "index": "1", + "doAlarm": "false", + "myCustomProperty": "value" + } + } + ] +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/cfcr_event.json b/powertools-validation/src/test/resources/cfcr_event.json new file mode 100644 index 000000000..98b4a9eba --- /dev/null +++ b/powertools-validation/src/test/resources/cfcr_event.json @@ -0,0 +1,20 @@ +{ + "requestType": "requestType", + "serviceToken": "serviceToken", + "responseUrl": "responseUrl", + "stackId": "stackId", + "requestId": "requestId", + "logicalResourceId": "logicalResourceId", + "resourceType": "resourceType", + "resourceProperties": { + "id": 1234, + "name": "product", + "price": 42 + }, + "oldResourceProperties": { + "id": 1234, + "name": "product", + "price": 40 + } +} + diff --git a/powertools-validation/src/test/resources/custom_event.json b/powertools-validation/src/test/resources/custom_event.json index 13103c434..918cad81f 100644 --- a/powertools-validation/src/test/resources/custom_event.json +++ b/powertools-validation/src/test/resources/custom_event.json @@ -1,6 +1,6 @@ { "basket": { - "products" : [ + "products": [ { "id": 43242, "name": "FooBar XY", diff --git a/powertools-validation/src/test/resources/custom_event_gzip.json b/powertools-validation/src/test/resources/custom_event_gzip.json index d212052d0..165db7071 100644 --- a/powertools-validation/src/test/resources/custom_event_gzip.json +++ b/powertools-validation/src/test/resources/custom_event_gzip.json @@ -1,6 +1,6 @@ { "basket": { - "products" : [ + "products": [ { "id": 43242, "name": "FooBar XY", diff --git a/powertools-validation/src/test/resources/cwl_event.json b/powertools-validation/src/test/resources/cwl_event.json new file mode 100644 index 000000000..5dd0f6657 --- /dev/null +++ b/powertools-validation/src/test/resources/cwl_event.json @@ -0,0 +1,5 @@ +{ + "awsLogs": { + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==" + } +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/kafip_event.json b/powertools-validation/src/test/resources/kafip_event.json new file mode 100644 index 000000000..01196256c --- /dev/null +++ b/powertools-validation/src/test/resources/kafip_event.json @@ -0,0 +1,14 @@ +{ + "invocationId": "arn:aws:iam::EXAMPLE", + "applicationArn": "arn:aws:kinesis:EXAMPLE", + "streamArn": "arn:aws:kinesis:EXAMPLE", + "records": [ + { + "kinesisFirehoseRecordMetadata": { + "approximateArrivalTimestamp": 1428537600 + }, + "recordId": "record-id", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=" + } + ] +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/kafka_event.json b/powertools-validation/src/test/resources/kafka_event.json new file mode 100644 index 000000000..cf1bad615 --- /dev/null +++ b/powertools-validation/src/test/resources/kafka_event.json @@ -0,0 +1,27 @@ +{ + "eventSource": "aws:kafka", + "eventSourceArn": "arn:aws:kafka:us-east-1:123456789012:cluster/vpc-3432434/4834-3547-3455-9872-7929", + "bootstrapServers": "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092", + "records": { + "mytopic-01": [ + { + "topic": "mytopic1", + "partition": 0, + "offset": 15, + "timestamp": 1596480920837, + "timestampType": "CREATE_TIME", + "value": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=" + } + ], + "mytopic-02": [ + { + "topic": "mytopic2", + "partition": 0, + "offset": 15, + "timestamp": 1596480920838, + "timestampType": "CREATE_TIME", + "value": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ==" + } + ] + } +} diff --git a/powertools-validation/src/test/resources/kasip_event.json b/powertools-validation/src/test/resources/kasip_event.json new file mode 100644 index 000000000..78bc9a3fb --- /dev/null +++ b/powertools-validation/src/test/resources/kasip_event.json @@ -0,0 +1,17 @@ +{ + "invocationId": "arn:aws:iam::EXAMPLE", + "applicationArn": "arn:aws:kinesis:EXAMPLE", + "streamArn": "arn:aws:kinesis:EXAMPLE", + "records": [ + { + "kinesisStreamRecordMetadata": { + "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", + "partitionKey": "partitionKey-03", + "shardId": "12", + "approximateArrivalTimestamp": 1428537600 + }, + "recordId": "record-id", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=" + } + ] +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/kf_event.json b/powertools-validation/src/test/resources/kf_event.json new file mode 100644 index 000000000..e36bc4c3f --- /dev/null +++ b/powertools-validation/src/test/resources/kf_event.json @@ -0,0 +1,12 @@ +{ + "invocationId": "invocationIdExample", + "deliveryStreamArn": "arn:aws:kinesis:EXAMPLE", + "region": "us-east-1", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "approximateArrivalTimestamp": 1495072949453, + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==" + } + ] +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/kinesis.json b/powertools-validation/src/test/resources/kinesis.json index 6d99be7e5..ad33ae456 100644 --- a/powertools-validation/src/test/resources/kinesis.json +++ b/powertools-validation/src/test/resources/kinesis.json @@ -1,5 +1,5 @@ { - "records": [ + "Records": [ { "kinesis": { "partitionKey": "partitionKey-03", diff --git a/powertools-validation/src/test/resources/kinesis_invalid_messages.json b/powertools-validation/src/test/resources/kinesis_invalid_messages.json new file mode 100644 index 000000000..3d805c4dd --- /dev/null +++ b/powertools-validation/src/test/resources/kinesis_invalid_messages.json @@ -0,0 +1,72 @@ +{ + "Records": [ + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "ewogICJpZCI6IDQzMjQyLAogICJuYW1lIjogIkZvb0JhciBYWSIsCiAgInByaWNlIjogMjU4Cn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-04", + "kinesisSchemaVersion": "1.0", + "data": "ewogICJpZCI6InN0cmluZ0lkIiwKICAibmFtZSI6ICJGb29CYXIgWFkiLAogICJwcmljZSI6IDI1OAp9", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200962", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-04", + "kinesisSchemaVersion": "1.0", + "data": "ewogICJpZCI6IDQyNSwKICAibmFtZSI6ICJCYXJGb28iLAogICJwcmljZSI6IDQzCn0=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200963", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + }, + { + "kinesis": { + "partitionKey": "partitionKey-04", + "kinesisSchemaVersion": "1.0", + "data": "ewogICJpZCI6MTIzNCwKICAibmFtZSI6ICJGb28iLAogICJwcmljZSI6IDI1OAp9", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200964", + "approximateArrivalTimestamp": 1428537600, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + } + ] +} diff --git a/powertools-validation/src/test/resources/rabbitmq_event.json b/powertools-validation/src/test/resources/rabbitmq_event.json new file mode 100644 index 000000000..698e37143 --- /dev/null +++ b/powertools-validation/src/test/resources/rabbitmq_event.json @@ -0,0 +1,51 @@ +{ + "eventSource": "aws:rmq", + "eventSourceArn": "arn:aws:mq:us-west-2:111122223333:broker:pizzaBroker:b-9bcfa592-423a-4942-879d-eb284b418fc8", + "rmqMessagesByQueue": { + "pizzaQueue::/": [ + { + "basicProperties": { + "contentType": "text/plain", + "contentEncoding": null, + "headers": { + "header1": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 49 + ] + }, + "header2": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 50 + ] + }, + "numberInHeader": 10 + }, + "deliveryMode": 1, + "priority": 34, + "correlationId": null, + "replyTo": null, + "expiration": "60000", + "messageId": null, + "timestamp": "Jan 1, 1970, 12:33:41 AM", + "type": null, + "userId": "AIDACKCEVSQ6C2EXAMPLE", + "appId": null, + "clusterId": null, + "bodySize": 80 + }, + "redelivered": false, + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==" + } + ] + } +} diff --git a/powertools-validation/src/test/resources/scheduled_event.json b/powertools-validation/src/test/resources/scheduled_event.json new file mode 100644 index 000000000..12d18ba5d --- /dev/null +++ b/powertools-validation/src/test/resources/scheduled_event.json @@ -0,0 +1,14 @@ +{ + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "source": "aws.events", + "account": "123456789012", + "region": "eu-central-1", + "resources": [ + "arn:aws:events:eu-central-1:123456789012:rule/my-schedule" + ], + "detail": { + "id": 1234, + "name": "product", + "price": 42 + } +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/schema_v4.json b/powertools-validation/src/test/resources/schema_v4.json index ae277d476..3cd310a53 100644 --- a/powertools-validation/src/test/resources/schema_v4.json +++ b/powertools-validation/src/test/resources/schema_v4.json @@ -18,5 +18,9 @@ "exclusiveMinimum": true } }, - "required": ["id", "name", "price"] + "required": [ + "id", + "name", + "price" + ] } \ No newline at end of file diff --git a/powertools-validation/src/test/resources/schema_v7.json b/powertools-validation/src/test/resources/schema_v7.json index f38272f2d..e382b8971 100644 --- a/powertools-validation/src/test/resources/schema_v7.json +++ b/powertools-validation/src/test/resources/schema_v7.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/product.json", "type": "object", "title": "Product schema", diff --git a/powertools-validation/src/test/resources/schema_v7_ko.json b/powertools-validation/src/test/resources/schema_v7_ko.json index f54bcb3c7..aed187c34 100644 --- a/powertools-validation/src/test/resources/schema_v7_ko.json +++ b/powertools-validation/src/test/resources/schema_v7_ko.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "Product schema", "description": "JSON schema to validate Products", diff --git a/powertools-validation/src/test/resources/sns_event.json b/powertools-validation/src/test/resources/sns_event.json new file mode 100644 index 000000000..81f6a4795 --- /dev/null +++ b/powertools-validation/src/test/resources/sns_event.json @@ -0,0 +1,26 @@ +{ + "records": [ + { + "eventSource": "aws:sns", + "eventVersion": "1.0", + "eventSubscriptionArn": "arn:aws:sns:eu-central-1:123456789012:TopicSendToMe:e3ddc7d5-2f86-40b8-a13d-3362f94fd8dd", + "sns": { + "type": "Notification", + "messageId": "dc918f50-80c6-56a2-ba33-d8a9bbf013ab", + "topicArn": "arn:aws:sns:eu-central-1:123456789012:TopicSendToMe", + "subject": "Test sns message", + "message": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "signatureVersion": "1", + "signature": "UWnPpkqPAphyr+6PXzUF9++4zJcw==", + "signingCertUrl": "https://sns.eu-central-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem", + "unsubscribeUrl": "https://sns.eu-central-1.amazonaws.com/?Action=Unsubscribe", + "messageAttributes": { + "name": { + "type": "String", + "value": "Bob" + } + } + } + } + ] +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/sqs.json b/powertools-validation/src/test/resources/sqs.json index 9180c5839..10a2bbbbf 100644 --- a/powertools-validation/src/test/resources/sqs.json +++ b/powertools-validation/src/test/resources/sqs.json @@ -1,5 +1,5 @@ { - "records": [ + "Records": [ { "messageId": "d9144555-9a4f-4ec3-99a0-fc4e625a8db2", "receiptHandle": "7kam5bfzbDsjtcjElvhSbxeLJbeey3A==", @@ -11,7 +11,6 @@ "ApproximateFirstReceiveTimestamp": "1601975709499" }, "messageAttributes": { - }, "md5OfBody": "0f96e88a291edb4429f2f7b9fdc3df96", "eventSource": "aws:sqs", diff --git a/powertools-validation/src/test/resources/sqs_invalid_messages.json b/powertools-validation/src/test/resources/sqs_invalid_messages.json new file mode 100644 index 000000000..aaec54bfd --- /dev/null +++ b/powertools-validation/src/test/resources/sqs_invalid_messages.json @@ -0,0 +1,72 @@ +{ + "Records": [ + { + "messageId": "d9144555-9a4f-4ec3-99a0-fc4e625a8db2", + "receiptHandle": "7kam5bfzbDsjtcjElvhSbxeLJbeey3A==", + "body": "{\n \"id\": 43242,\n \"name\": \"FooBar XY\",\n \"price\": 258\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975709495", + "SenderId": "AROAIFU457DVZ5L2J53F2", + "ApproximateFirstReceiveTimestamp": "1601975709499" + }, + "messageAttributes": { + }, + "md5OfBody": "0f96e88a291edb4429f2f7b9fdc3df96", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "d9144555-9a4f-4ec3-99a0-fc4e625a8db3", + "receiptHandle": "7kam5bfzbDsjtcjElvhSbxeLJbeey3A==", + "body": "{\n \"id\": 43245,\n \"name\": \"Foo\",\n \"price\": 258\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975709495", + "SenderId": "AROAIFU457DVZ5L2J53F2", + "ApproximateFirstReceiveTimestamp": "1601975709499" + }, + "messageAttributes": { + }, + "md5OfBody": "0f96e88a291edb4429f2f7b9fdc3df96", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "d9144555-9a4f-4ec3-99a0-fc4e625a8db4", + "receiptHandle": "7kam5bfzbDsjtcjElvhSbxeLJbeey3A==", + "body": "{\n \"id\": 43246,\n \"name\": \"FooBar XYZ\",\n \"price\": 258\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975709495", + "SenderId": "AROAIFU457DVZ5L2J53F2", + "ApproximateFirstReceiveTimestamp": "1601975709499" + }, + "messageAttributes": { + }, + "md5OfBody": "0f96e88a291edb4429f2f7b9fdc3df96", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + }, + { + "messageId": "d9144555-9a4f-4ec3-99a0-fc4e625a8db5", + "receiptHandle": "7kam5bfzbDsjtcjElvhSbxeLJbeey3A==", + "body": "{\n \"id\": \"stringId\",\n \"name\": \"FooBar XY\",\n \"price\": 258\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975709495", + "SenderId": "AROAIFU457DVZ5L2J53F2", + "ApproximateFirstReceiveTimestamp": "1601975709499" + }, + "messageAttributes": { + }, + "md5OfBody": "0f96e88a291edb4429f2f7b9fdc3df96", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/sqs_message.json b/powertools-validation/src/test/resources/sqs_message.json new file mode 100644 index 000000000..878b402df --- /dev/null +++ b/powertools-validation/src/test/resources/sqs_message.json @@ -0,0 +1,21 @@ +{ + "Records": [ + { + "messageId": "d9144555-9a4f-4ec3-99a0-fc4e625a8db2", + "receiptHandle": "7kam5bfzbDsjtcjElvhSbxeLJbeey3A==", + "body": "{\n \"Message\": \"{\\n \\\"id\\\": 43242,\\n \\\"name\\\": \\\"FooBar XY\\\",\\n \\\"price\\\": 258\\n}\"}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1601975709495", + "SenderId": "AROAIFU457DVZ5L2J53F2", + "ApproximateFirstReceiveTimestamp": "1601975709499" + }, + "messageAttributes": { + }, + "md5OfBody": "0f96e88a291edb4429f2f7b9fdc3df96", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index b36b180cb..53226e3c2 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -8,24 +8,116 @@ https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html --> <FindBugsFilter> + <!-- These open matches are new rules that we have not yet addressed. Let's block them generically, and come + back to them later. --> + <Match> + <Bug pattern="CT_CONSTRUCTOR_THROW" /> + </Match> + <Match> + <Bug pattern="PA_PUBLIC_PRIMITIVE_ATTRIBUTE" /> + </Match> + <Match> + <Bug pattern="SING_SINGLETON_GETTER_NOT_SYNCHRONIZED" /> + </Match> + <Match> + <Bug pattern="SING_SINGLETON_GETTER_NOT_SYNCHRONIZED" /> + </Match> + + <!-- Regular matches --> + <Match> + <Bug pattern="EI_EXPOSE_REP2"/> + <Or> + <And> + <Class name="software.amazon.lambda.powertools.parameters.BaseProvider"/> + <Field name="cacheManager"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.BaseProvider"/> + <Field name="transformationManager"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.transform.TransformationManager"/> + <Field name="transformation"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.ssm.SSMProviderBuilder"/> + <Field name="transformationManager"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.largemessages.LargeMessageConfig"/> + <Method name="getS3Client"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.secrets.SecretsProviderBuilder"/> + <Field name="cacheManager"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.secrets.SecretsProviderBuilder"/> + <Field name="transformationManager"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.secrets.SecretsProviderBuilder"/> + <Field name="client"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.ssm.SSMProviderBuilder"/> + <Field name="client"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.ssm.SSMProviderBuilder"/> + <Field name="cacheManager"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.dynamodb.DynamoDbProviderBuilder"/> + <Field name="cacheManager"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.dynamodb.DynamoDbProviderBuilder"/> + <Field name="client"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.dynamodb.DynamoDbProviderBuilder"/> + <Field name="transformationManager"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.appconfig.AppConfigProviderBuilder"/> + <Field name="cacheManager"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.parameters.appconfig.AppConfigProviderBuilder"/> + <Field name="transformationManager"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.logging.internal.JsonSerializer"/> + </And> + </Or> + </Match> <!-- Internals of Log event for apache log4j--> <Match> <Bug pattern="EI_EXPOSE_REP"/> <Or> <And> - <Class name="software.amazon.lambda.powertools.logging.internal.LambdaJsonLayout$LogEventWithAdditionalFields"/> - <Method name="getLogEvent"/> + <Class name="software.amazon.lambda.powertools.idempotency.Idempotency"/> + <Method name="getPersistenceStore"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.idempotency.Idempotency"/> + <Method name="getConfig"/> </And> <And> - <Class name="software.amazon.lambda.powertools.logging.internal.LambdaJsonLayout$LogEventWithAdditionalFields"/> + <Class name="software.amazon.lambda.powertools.largemessages.LargeMessageConfig"/> + <Method name="getS3Client"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.logging.internal.AbstractJacksonLayoutCopy$Builder" /> <Method name="getAdditionalFields"/> </And> <And> - <Class name="software.amazon.lambda.powertools.logging.internal.AbstractJacksonLayoutCopy$Builder"/> + <Class name="software.amazon.lambda.powertools.logging.internal.AbstractJacksonLayoutCopy$LogEventWithAdditionalFields" /> <Method name="getAdditionalFields"/> </And> <And> - <Class name="software.amazon.lambda.powertools.logging.internal.AbstractJacksonLayoutCopy$LogEventWithAdditionalFields"/> + <Class name="software.amazon.lambda.powertools.logging.internal.LambdaJsonLayout$LogEventWithAdditionalFields" /> <Method name="getAdditionalFields"/> </And> </Or> @@ -34,43 +126,88 @@ <Bug pattern="EI_EXPOSE_REP2"/> <Or> <And> - <Class name="software.amazon.lambda.powertools.logging.internal.LambdaJsonLayout$LogEventWithAdditionalFields"/> - <Field name="logEvent"/> - </And> - <And> - <Class name="software.amazon.lambda.powertools.logging.internal.LambdaJsonLayout$LogEventWithAdditionalFields"/> - <Field name="additionalFields"/> + <Class name="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder"/> + <Field name="throwableConverter"/> </And> <And> - <Class name="software.amazon.lambda.powertools.logging.internal.AbstractJacksonLayoutCopy$Builder"/> - <Field name="additionalFields"/> + <Class name="software.amazon.lambda.powertools.parameters.transform.TransformationManager"/> + <Field name="transformer"/> </And> <And> - <Class name="software.amazon.lambda.powertools.logging.internal.AbstractJacksonLayoutCopy$LogEventWithAdditionalFields"/> - <Field name="additionalFields"/> + <Class name="software.amazon.lambda.powertools.logging.logback.LambdaEcsEncoder"/> + <Field name="throwableConverter"/> </And> <And> <Class name="software.amazon.lambda.powertools.sqs.internal.BatchContext"/> <Field name="client"/> </And> <And> - <Class name="software.amazon.lambda.powertools.parameters.BaseProvider"/> - <Field name="cacheManager"/> + <Class name="software.amazon.lambda.powertools.idempotency.Idempotency$Config"/> + <Field name="store"/> </And> <And> - <Class name="software.amazon.lambda.powertools.parameters.transform.TransformationManager"/> - <Field name="transformer"/> + <Class name="software.amazon.lambda.powertools.idempotency.Idempotency$Config"/> + <Field name="config"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.idempotency.internal.IdempotencyHandler"/> + <Field name="pjp"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.idempotency.persistence.dynamodb.DynamoDBPersistenceStore$Builder"/> + <Field name="dynamoDbClient"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.utilities.EventDeserializer$EventPart"/> + <Field name="contentMap"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.utilities.EventDeserializer$EventPart"/> + <Field name="contentList"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.largemessages.LargeMessageConfig"/> + <Field name="s3Client"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.logging.internal.AbstractJacksonLayoutCopy" /> + <Method name="LogEventWithAdditionalFields"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.logging.internal.AbstractJacksonLayoutCopy$Builder" /> + <Method name="setAdditionalFields"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.logging.internal.LambdaJsonLayout" /> + <Method name="LogEventWithAdditionalFields"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.logging.LambdaEcsEncoder" /> + <Method name="setThrowableConverter"/> + </And> + <And> + <Class name="software.amazon.lambda.powertools.logging.LambdaJsonEncoder" /> + <Method name="setThrowableConverter"/> </And> </Or> </Match> + <Match> + <Bug pattern="RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT"/> + <Class name="software.amazon.lambda.powertools.validation.ValidationConfig"/> + <Method name="beforeCheckpoint"/> + </Match> <!--Functionally needed--> <Match> - <Bug pattern="EI_EXPOSE_STATIC_REP2"/> + <Bug pattern="MS_SHOULD_BE_FINAL"/> <Or> <And> - <Class name="software.amazon.lambda.powertools.logging.LoggingUtils"/> - <Method name="defaultObjectMapper"/> + <Class name="software.amazon.lambda.powertools.logging.internal.LoggingConstants"/> </And> + </Or> + </Match> + <Match> + <Bug pattern="EI_EXPOSE_STATIC_REP2"/> + <Or> <And> <Class name="software.amazon.lambda.powertools.tracing.TracingUtils"/> <Method name="defaultObjectMapper"/> @@ -88,10 +225,6 @@ <Match> <Bug pattern="MS_EXPOSE_REP"/> <Or> - <And> - <Class name="software.amazon.lambda.powertools.logging.LoggingUtils"/> - <Method name="objectMapper"/> - </And> <And> <Class name="software.amazon.lambda.powertools.tracing.TracingUtils"/> <Method name="objectMapper"/> @@ -100,17 +233,13 @@ <Class name="software.amazon.lambda.powertools.sqs.SqsUtils"/> <Method name="objectMapper"/> </And> - <And> - <Class name="software.amazon.lambda.powertools.parameters.ParamManager"/> - <Method name="getCacheManager"/> - </And> <And> <Class name="software.amazon.lambda.powertools.sqs.SqsUtils"/> <Method name="s3Client"/> </And> <And> - <Class name="software.amazon.lambda.powertools.parameters.ParamManager"/> - <Method name="getTransformationManager"/> + <Class name="software.amazon.lambda.powertools.metrics.MetricsFactory"/> + <Method name="getMetricsInstance"/> </And> </Or> </Match> @@ -120,4 +249,13 @@ <Class name="software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect"/> <Method name="setLogLevelBasedOnSamplingRate"/> </Match> -</FindBugsFilter> \ No newline at end of file + <Match> + <Bug pattern="DMI_THREAD_PASSED_WHERE_RUNNABLE_EXPECTED"/> + <Or> + <And> + <Class name="helloworld.App"/> + <Method name="threadOption1"/> + </And> + </Or> + </Match> +</FindBugsFilter>